PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Führen Sie eine dynamische Kreuztabellenabfrage aus

Was Sie verlangen, ist unmöglich . SQL ist eine streng typisierte Sprache. PostgreSQL-Funktionen müssen einen Rückgabetyp deklarieren (RETURNS .. ) zum Zeitpunkt der Erstellung .

Ein begrenzter Weg, dies zu umgehen, sind polymorphe Funktionen. Wenn Sie den Rückgabetyp zum Zeitpunkt des Aufrufs der Funktion bereitstellen können . Aber das geht aus deiner Frage nicht hervor.

  • Refaktorisieren Sie eine PL/pgSQL-Funktion, um die Ausgabe verschiedener SELECT-Abfragen zurückzugeben

Sie können geben Sie ein vollständig dynamisches Ergebnis mit anonymen Datensätzen zurück. Dann müssen Sie aber bei jedem Aufruf eine Spaltendefinitionsliste mitliefern. Und woher wissen Sie von den zurückgegebenen Spalten? Fang 22.

Es gibt verschiedene Problemumgehungen, je nachdem, was Sie brauchen oder womit Sie arbeiten können. Da alle Ihre Datenspalten denselben Datentyp zu teilen scheinen, schlage ich vor, ein Array zurückzugeben :text[] . Oder Sie könnten einen Dokumenttyp wie hstore zurückgeben oder json . Verwandte:

  • Dynamische Alternative zum Pivot mit CASE und GROUP BY

  • Konvertieren Sie hstore-Schlüssel dynamisch in Spalten für einen unbekannten Satz von Schlüsseln

Aber es könnte einfacher sein, nur zwei Aufrufe zu verwenden:1:Lassen Sie Postgres die Abfrage erstellen. 2:Zurückgegebene Zeilen ausführen und abrufen.

  • Mehrere max()-Werte mit einer einzigen SQL-Anweisung auswählen

Ich würde die in Ihrer Frage vorgestellte Funktion von Eric Minikel überhaupt nicht verwenden . Es ist nicht sicher gegen SQL-Injection durch böswillig falsch formatierte Bezeichner. Verwenden Sie format() zum Erstellen von Abfragezeichenfolgen, es sei denn, Sie verwenden eine veraltete Version, die älter als Postgres 9.1 ist.

Eine kürzere und sauberere Implementierung könnte so aussehen:

CREATE OR REPLACE FUNCTION xtab(_tbl regclass, _row text, _cat text
                              , _expr text  -- still vulnerable to SQL injection!
                              , _type regtype)
  RETURNS text AS
$func$
DECLARE
   _cat_list text;
   _col_list text;
BEGIN

-- generate categories for xtab param and col definition list    
EXECUTE format(
 $$SELECT string_agg(quote_literal(x.cat), '), (')
        , string_agg(quote_ident  (x.cat), %L)
   FROM  (SELECT DISTINCT %I AS cat FROM %s ORDER BY 1) x$$
 , ' ' || _type || ', ', _cat, _tbl)
INTO  _cat_list, _col_list;

-- generate query string
RETURN format(
'SELECT * FROM crosstab(
   $q$SELECT %I, %I, %s
      FROM   %I
      GROUP  BY 1, 2  -- only works if the 3rd column is an aggregate expression
      ORDER  BY 1, 2$q$
 , $c$VALUES (%5$s)$c$
   ) ct(%1$I text, %6$s %7$s)'
, _row, _cat, _expr  -- expr must be an aggregate expression!
, _tbl, _cat_list, _col_list, _type
);

END
$func$ LANGUAGE plpgsql;

Gleicher Funktionsaufruf wie Ihre Originalversion. Die Funktion crosstab() wird durch das Zusatzmodul tablefunc bereitgestellt was installiert werden muss. Grundlagen:

  • PostgreSQL-Kreuztabellenabfrage

Dies behandelt Spalten- und Tabellennamen sicher. Beachten Sie die Verwendung von Objektbezeichnertypen regclass und regtype . Funktioniert auch für Schema-qualifizierte Namen.

  • Tabellenname als PostgreSQL-Funktionsparameter

Es ist jedoch nicht ganz sicher während Sie einen auszuführenden String als Ausdruck übergeben (_expr - cellc in Ihrer ursprünglichen Anfrage). Diese Art von Eingabe ist von Natur aus unsicher gegen SQL-Injection und sollte niemals der Öffentlichkeit zugänglich gemacht werden.

  • SQL-Injection in Postgres-Funktionen im Vergleich zu vorbereiteten Abfragen

Scannt die Tabelle nur einmal für beide Kategorienlisten und sollte etwas schneller sein.

Es können immer noch keine vollständig dynamischen Zeilentypen zurückgegeben werden, da dies streng genommen nicht möglich ist.