Gelegentlich hat der Entwickler die Notwendigkeit einen Parameter-Set oder sogar eine gesamte Auswahl âals Eingabeâ zu ĂŒbergeben. Manchmal begegnen einem sehr seltsame Lösungen fĂŒr dieses Problem.

Lass uns âvom Gegenteilâ ausgehen und schauen, wie man es nicht machen sollte, warum und wie man es besser machen kann.
Direkte âEinschĂŒbeâ von Werten in den Anfragekörper
sehen normalerweise ungefÀhr so aus:
query = "SELECT * FROM tbl WHERE id = " + value⊠oder so:
query = "SELECT * FROM tbl WHERE id = :param".format(param=value)Ăber diese Methode wurde viel gesagt, geschrieben und es gibt mehr als genug:

Fast immer fĂŒhrt dies zu einem direkten Weg zu SQL-Injektionen und ĂŒbermĂ€Ăiger Belastung der GeschĂ€ftslogik, die gezwungen ist, die Zeichenfolge deiner Anfrage âzusammenzuklebenâ.
Ein solcher Ansatz kann nur teilweise gerechtfertigt sein, wenn es notwendig ist, Partitionierung in PostgreSQL-Versionen 10 und niedriger zu verwenden, um einen effizienteren Plan zu erhalten. In diesen Versionen wird die Liste der gescannten Abschnitte noch ohne BerĂŒcksichtigung der ĂŒbergebenen Parameter ausschlieĂlich auf Grundlage des Anfragekörpers festgelegt.
$n-Argumente
Verwendung Parameter sind gut, sie erlauben es, , wodurch die Belastung sowohl der GeschĂ€ftslogik (die Anfrage wird nur einmal erstellt und ĂŒbergeben) als auch des DB-Servers (eine wiederholte Analyse und Planung fĂŒr jede Instanz der Anfrage ist nicht erforderlich) reduziert wird.
Eine variable Anzahl von Argumenten
Probleme werden uns erwarten, wenn wir eine im Voraus unbekannte Anzahl von Argumenten ĂŒbergeben möchten:
... id IN ($1, $2, $3, ...) -- $1 : 2, $2 : 3, $3 : 5, ...Wenn wir die Anfrage in dieser Form belassen, wird sie uns zwar vor potenziellen Injektionen bewahren, aber trotzdem wird es notwendig sein, die Anfrage zusammenzukleben / zu analysieren fĂŒr jede Variante der Anzahl der Argumente.Das ist schon besser, als es jedes Mal zu tun, aber man könnte darauf verzichten.
Es reicht, nur einen Parameter zu ĂŒbergeben, der eine serialisierte Darstellung eines Arrays enthĂ€lt:
... id = ANY($1::integer[]) -- $1 : '{2,3,5,8,13}'Der einzige Unterschied ist die Notwendigkeit, das Argument explizit in den erforderlichen Arraytyp umzuwandeln. Aber das verursacht keine Probleme, da wir ohnehin im Voraus wissen, wohin wir gehen.
Ăbergabe eines Abfragesatzes (Matrix)
Normalerweise handelt es sich um verschiedene Möglichkeiten, DatensĂ€tze fĂŒr eine âeinzeilige Anfrageâ in die Datenbank einzufĂŒgen:
INSERT INTO tbl(k, v) VALUES($1,$2),($3,$4),...Neben den oben beschriebenen Problemen mit der âZusammenklebe-Anfrageâ kann dies auch zu Out of Memory fĂŒhren. und dem Absturz des Servers. Der Grund ist simpel â beim Argument PG wird zusĂ€tzlicher Speicher reserviert, und die Anzahl der DatensĂ€tze in der Menge wird nur durch die GeschĂ€ftslogik der Anwendung begrenzt. In besonders klinischen FĂ€llen musste ich sehen dass die »nummerischen« Argumente mehr als 9000 Dollar betragen â das mĂŒssen wir nicht so machen.
Lass uns die Abfrage umschreiben und dabei bereits die âzweistufigeâ Serialisierung anwenden:
INSERT INTO tbl
SELECT
unnest[1]::text k
, unnest[2]::integer v
FROM (
SELECT
unnest($1::text[])::text[] -- $1 : '{"{a,1}","{b,2}","{c,3}","{d,4}"}'
) T;
Ja, im Falle von âkomplexenâ Werten innerhalb des Arrays mĂŒssen diese in AnfĂŒhrungszeichen gesetzt werden.
Es ist klar, dass man auf diese Weise die Auswahl mit einer beliebigen Anzahl von Feldern âentfaltenâ kann.
unnest, unnest, âŠ
Gelegentlich gibt es Varianten, bei denen anstelle eines âArrays von Arraysâ mehrere âSpalten-Arraysâ ĂŒbergeben werden, auf die ich hingewiesen habe. :
SELECT
unnest($1::text[]) k
, unnest($2::integer[]) v;Bei dieser Methode ist es sehr einfach, unerwartete Ergebnisse zu erzielen, wenn man bei der Erstellung von Wertlisten fĂŒr verschiedene Spalten einen Fehler macht. die zudem von der Serverversion abhĂ€ngen:, abhĂ€ngig von der Serverversion:
-- $1 : '{a,b,c}', $2 : '{1,2}'
-- PostgreSQL 9.4
k | v
-----
a | 1
b | 2
c | 1
a | 2
b | 1
c | 2
-- PostgreSQL 11
k | v
-----
a | 1
b | 2
c |JSON
Seit Version 9.3 gibt es in PostgreSQL vollstĂ€ndige Funktionen zur Arbeit mit dem json-Typ. Daher können Sie, wenn die Definition der Eingabeparameter in Ihrem Browser erfolgt, direkt dort das json-Objekt fĂŒr die SQL-Abfrage formulieren:
SELECT
key k
, value v
FROM
json_each($1::json); -- '{"a":1,"b":2,"c":3,"d":4}'FĂŒr frĂŒhere Versionen kann dieselbe Methode fĂŒr each(hstore), aber eine korrekte âEinschmelzungâ mit der Escapierung komplexer Objekte in hstore kann Probleme verursachen.
json_populate_recordset
Wenn Sie im Voraus wissen, dass die Daten aus dem âeingehendenâ json-Array zum BefĂŒllen einer bestimmten Tabelle verwendet werden, können Sie erheblich an âEntdereferenzierungâ von Feldern und Typkonvertierung sparen, indem Sie die Funktion json_populate_recordset verwenden:
SELECT
*
FROM
json_populate_recordset(
NULL::pg_class
, $1::json -- $1 : '[{"relname":"pg_class","oid":1262},{"relname":"pg_namespace","oid":2615}]'
);json_to_recordset
Diese Funktion âentfaltetâ einfach das ĂŒbergebene Objektarray in die Auswahl, ohne sich auf das Tabellenformat zu stĂŒtzen:
SELECT
*
FROM
json_to_recordset($1::json) T(k text, v integer);
-- $1 : '[{"k":"a","v":1},{"k":"b","v":2}]'
k | v
-----
a | 1
b | 2TEMPORĂRE TABELLE
Wenn das Datenvolumen in der ĂŒbergebenen Auswahl jedoch sehr groĂ ist, ist es schwierig, es in einem einzigen serialisierten Parameter zu verarbeiten â manchmal sogar unmöglich, da dies eine einmalige Zuweisung eines groĂen Speicherumfangs erfordert.. Zum Beispiel möchten Sie einen groĂen Datensatz von Ereignissen aus einem externen System ĂŒber einen lĂ€ngeren Zeitraum sammeln und möchten ihn dann einmalig auf der Datenbankseite verarbeiten.
In diesem Fall wÀre die beste Lösung die Nutzung von :
CREATE TEMPORARY TABLE tbl(k text, v integer);
...
INSERT INTO tbl(k, v) VALUES($1, $2); -- viele Male wiederholen
...
-- hier machen wir etwas NĂŒtzliches mit der gesamten Tabelle
Die Methode ist besonders fĂŒr seltene Ăbertragungen groĂer Datenmengen geeignet. von Daten.
Aus Sicht der Beschreibung der Struktur Ihrer Daten unterscheidet sich eine temporĂ€re Tabelle von einer âgewöhnlichenâ nur durch ein einziges Merkmal. in der Systemtabelle pg_class, sondern in pg_type, pg_depend, pg_attribute, pg_attrdef, ⊠â so unterscheidet sie sich ĂŒberhaupt nicht.
Deshalb erzeugt eine solche Tabelle in Websystemen mit einer groĂen Anzahl von kurzlebigen Verbindungen bei jedem einzelnen eine neue systematische Aufzeichnung, die beim SchlieĂen der Verbindung zur Datenbank gelöscht wird. Infolgedessen fĂŒhrt die unkontrollierte Verwendung von TEMP TABLE zu einer âAufblĂ€hungâ der Tabellen im pg_catalog und verlangsamt viele Operationen, die sie verwenden.
NatĂŒrlich kann man dem mit einem regelmĂ€Ăigen VACUUM FULL des Systemkatalogs begegnen.
Sitzungsvariablen
Angenommen, die Verarbeitung von Daten aus dem vorherigen Fall ist fĂŒr eine SQL-Abfrage ziemlich komplex, aber wir möchten sie hĂ€ufig durchfĂŒhren. Das heiĂt, wir möchten die prozedurale Verarbeitung in , aber die Verwendung der Ăbertragung von Daten ĂŒber temporĂ€re Tabellen wĂ€re zu umstĂ€ndlich.
Wir können auch die $n-Parameter nicht verwenden, um sie in einen anonymen Block zu ĂŒbergeben. Eine Lösung fĂŒr unser Problem bietet uns die Verwendung von Sitzungsvariablen und der Funktion current_setting.
Bis zur Version 9.2 war es notwendig, ein spezielles Namensraum fĂŒr âeigeneâ Sitzungsvariablen im Voraus zu konfigurieren. In den aktuellen Versionen kann man etwa so schreiben: SET my.val = '{1,2,3}'; DO $$ DECLARE id integer; BEGIN FOR id IN (SELECT unnest(current_setting('my.val')::integer[])) LOOP RAISE NOTICE 'id : %', id; END LOOP; END; $$ LANGUAGE plpgsql; -- NOTICE: id : 1 -- NOTICE: id : 2 -- NOTICE: id : 3
In anderen unterstĂŒtzten prozeduralen Sprachen können auch andere Lösungen gefunden werden.Kennt ihr noch andere Methoden? Teilt sie in den Kommentaren!
Gelegentlich hat der Entwickler die Notwendigkeit, eine Menge von Parametern oder sogar eine gesamte Auswahl âzu ĂŒbergebenâ.
Quelle: habr.com
