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

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

Dynamisches SQL und RETURN eingeben


Sie möchten dynamisches SQL ausführen . Im Prinzip geht das in plpgsql einfach mit Hilfe von EXECUTE . Sie brauchen nicht ein Cursor. Tatsächlich sind Sie meistens ohne explizite Cursor besser dran.

Das Problem, auf das Sie stoßen:Sie möchten Datensätze eines noch nicht definierten Typs zurückgeben . Eine Funktion muss ihren Rückgabetyp in RETURNS deklarieren -Klausel (oder mit OUT oder INOUT Parameter). In Ihrem Fall müssten Sie auf anonyme Aufzeichnungen zurückgreifen, weil Anzahl , Namen und Typen der zurückgegebenen Spalten variieren. Wie:

CREATE FUNCTION data_of(integer)
  RETURNS SETOF record AS ...

Dies ist jedoch nicht besonders nützlich. Sie müssen bei jedem Aufruf eine Spaltendefinitionsliste mitliefern. Wie:

SELECT * FROM data_of(17)
AS foo (colum_name1 integer
      , colum_name2 text
      , colum_name3 real);

Aber wie würden Sie das überhaupt machen, wenn Sie die Spalten vorher nicht kennen?
Sie könnten weniger strukturierte Dokumentdatentypen wie json verwenden , jsonb , hstore oder xml . Siehe:

  • Wie speichere ich eine Datentabelle in der Datenbank?

Aber für diese Frage nehmen wir an, Sie möchten so viele einzelne, korrekt typisierte und benannte Spalten wie möglich zurückgeben.

Einfache Lösung mit festem Rückgabetyp

Die Spalte datahora scheint eine Selbstverständlichkeit zu sein, ich nehme den Datentyp timestamp an und dass es immer zwei weitere Spalten mit unterschiedlichem Namen und Datentyp gibt.

Namen wir verzichten zugunsten generischer Namen im Rückgabetyp.
Typen wir werden auch aufgeben und alles in text umwandeln seit alle Der Datentyp kann in text umgewandelt werden .

CREATE OR REPLACE FUNCTION data_of(_id integer)
  RETURNS TABLE (datahora timestamp, col2 text, col3 text)
  LANGUAGE plpgsql AS
$func$
DECLARE
   _sensors text := 'col1::text, col2::text';  -- cast each col to text
   _type    text := 'foo';
BEGIN
   RETURN QUERY EXECUTE '
      SELECT datahora, ' || _sensors || '
      FROM   ' || quote_ident(_type) || '
      WHERE  id = $1
      ORDER  BY datahora'
   USING  _id;
END
$func$;

Die Variablen _sensors und _type könnten stattdessen Eingabeparameter sein.

Beachten Sie die RETURNS TABLE Klausel.

Beachten Sie die Verwendung von RETURN QUERY EXECUTE . Das ist eine der elegantesten Möglichkeiten, Zeilen aus einer dynamischen Abfrage zurückzugeben.

Ich verwende einen Namen für den Funktionsparameter, nur um den USING zu machen -Klausel von RETURN QUERY EXECUTE weniger verwirrend. $1 im SQL-String bezieht sich nicht auf den Funktionsparameter, sondern auf den mit USING übergebenen Wert Klausel. (Beide sind zufällig $1 in ihrem jeweiligen Geltungsbereich in diesem einfachen Beispiel.)

Beachten Sie den Beispielwert für _sensors :Jede Spalte wird in den Typ text umgewandelt .

Diese Art von Code ist sehr anfällig für SQL-Injection . Ich verwende quote_ident() davor zu schützen. Ein paar Spaltennamen in der Variablen _sensors zusammenfassen verhindert die Verwendung von quote_ident() (und ist normalerweise eine schlechte Idee!). Stellen Sie sicher, dass dort keine schädlichen Dinge auf andere Weise enthalten sein können, indem Sie beispielsweise die Spaltennamen einzeln durch quote_ident() laufen lassen stattdessen. Ein VARIADIC Parameter fällt mir ein ...

Einfacher seit PostgreSQL 9.1

Ab Version 9.1 können Sie format() verwenden zur weiteren Vereinfachung:

RETURN QUERY EXECUTE format('
   SELECT datahora, %s  -- identifier passed as unescaped string
   FROM   %I            -- assuming the name is provided by user
   WHERE  id = $1
   ORDER  BY datahora'
  ,_sensors, _type)
USING  _id;

Auch hier könnten einzelne Spaltennamen korrekt maskiert werden und wären der saubere Weg.

Variable Anzahl von Spalten, die den gleichen Typ haben

Nachdem Ihre Frage aktualisiert wurde, sieht es so aus, als hätte Ihr Rückgabetyp

  • eine variable Zahl Spalten
  • aber alle Spalten vom selben Typ double precision (alias float8 )

Verwenden Sie ein ARRAY geben Sie in diesem Fall ein, um eine variable Anzahl von Werten zu verschachteln. Zusätzlich gebe ich ein Array mit Spaltennamen zurück:

CREATE OR REPLACE FUNCTION data_of(_id integer)
  RETURNS TABLE (datahora timestamp, names text[], values float8[])
  LANGUAGE plpgsql AS
$func$
DECLARE
   _sensors text := 'col1, col2, col3';  -- plain list of column names
   _type    text := 'foo';
BEGIN
   RETURN QUERY EXECUTE format('
      SELECT datahora
           , string_to_array($1)  -- AS names
           , ARRAY[%s]            -- AS values
      FROM   %s
      WHERE  id = $2
      ORDER  BY datahora'
    , _sensors, _type)
   USING  _sensors, _id;
END
$func$;

Verschiedene vollständige Tabellentypen

Um tatsächlich alle Spalten einer Tabelle zurückzugeben , gibt es eine einfache, leistungsstarke Lösung mit einem polymorphen Typ :

CREATE OR REPLACE FUNCTION data_of(_tbl_type anyelement, _id int)
  RETURNS SETOF anyelement
  LANGUAGE plpgsql AS
$func$
BEGIN
   RETURN QUERY EXECUTE format('
      SELECT *
      FROM   %s  -- pg_typeof returns regtype, quoted automatically
      WHERE  id = $1
      ORDER  BY datahora'
    , pg_typeof(_tbl_type))
   USING  _id;
END
$func$;

Rufen Sie an (wichtig!):

SELECT * FROM data_of(NULL::pcdmet, 17);

Ersetzen Sie pcdmet im Aufruf mit einem beliebigen anderen Tabellennamen.

Wie funktioniert das?

anyelement ist ein Pseudo-Datentyp, ein polymorpher Typ, ein Platzhalter für jeden Nicht-Array-Datentyp. Alle Vorkommen von anyelement in der Funktion auf denselben Typ auswerten, der zur Laufzeit bereitgestellt wird. Indem wir der Funktion einen Wert eines definierten Typs als Argument liefern, definieren wir implizit den Rückgabetyp.

PostgreSQL definiert automatisch einen Zeilentyp (einen zusammengesetzten Datentyp) für jede erstellte Tabelle, sodass es für jede Tabelle einen wohldefinierten Typ gibt. Dazu gehören temporäre Tabellen, was für die Ad-hoc-Nutzung praktisch ist.

Jeder Typ kann NULL sein . Geben Sie eine NULL ein Wert, umgewandelt in den Tabellentyp:NULL::pcdmet .

Jetzt gibt die Funktion einen wohldefinierten Zeilentyp zurück und wir können SELECT * FROM data_of() verwenden um die Zeile zu zerlegen und einzelne Spalten zu erhalten.

pg_typeof(_tbl_type) gibt den Namen der Tabelle als Objektbezeichner vom Typ regtype zurück . Bei automatischer Umwandlung in text werden Bezeichner automatisch in doppelte Anführungszeichen gesetzt und schemaqualifiziert bei Bedarf automatischer Schutz vor SQL-Injection. Dies kann sogar mit Schema-qualifizierten Tabellennamen umgehen, wobei quote_ident() würde versagen. Siehe:

  • Tabellenname als PostgreSQL-Funktionsparameter