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
(aliasfloat8
)
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