Fra tid til anden har udvikleren brug for videregive et sæt parametre eller endda et helt udvalg til anmodningen "ved indgangen". Nogle gange er der meget mærkelige løsninger på dette problem.
Lad os gå "fra det modsatte" og se, hvordan du ikke gør det, hvorfor, og hvordan du kan gøre det bedre.
Direkte "indsættelse" af værdier i forespørgselslegemet
Det ser normalt sådan ud:
query = "SELECT * FROM tbl WHERE id = " + value
... eller sådan her:
query = "SELECT * FROM tbl WHERE id = :param".format(param=value)
Om denne metode siges, skrives og
Det er det næsten altid direkte sti til SQL-injektion og en ekstra belastning af forretningslogikken, som er tvunget til at "lime" din forespørgselsstreng.
Denne tilgang kan kun delvist begrundes, hvis det er nødvendigt. bruge partitionering i PostgreSQL version 10 og derunder for en mere effektiv plan. I disse versioner bestemmes listen over scannede sektioner uden at tage hensyn til de transmitterede parametre, kun på grundlag af anmodningsorganet.
$n argumenter
Brug
Variabelt antal argumenter
Problemer vil afvente os, når vi ønsker at videregive et ukendt antal argumenter på forhånd:
... id IN ($1, $2, $3, ...) -- $1 : 2, $2 : 3, $3 : 5, ...
Hvis du efterlader anmodningen i denne formular, så selvom det vil redde os fra potentielle injektioner, vil det stadig føre til behovet for at lime / analysere anmodningen for hver mulighed fra antallet af argumenter. Allerede bedre end at gøre det hver gang, men du kan undvære det.
Det er nok kun at videregive én parameter, der indeholder serialiseret repræsentation af et array:
... id = ANY($1::integer[]) -- $1 : '{2,3,5,8,13}'
Den eneste forskel er behovet for eksplicit at konvertere argumentet til den ønskede matrixtype. Men det giver ikke problemer, da vi allerede på forhånd ved, hvor vi henvender os.
Prøveoverførsel (matrix)
Normalt er disse alle mulige muligheder for at overføre datasæt til indsættelse i databasen "i én anmodning":
INSERT INTO tbl(k, v) VALUES($1,$2),($3,$4),...
Ud over de ovenfor beskrevne problemer med "genlimningen" af forespørgslen, kan dette også føre os til ikke mere hukommelse og servernedbrud. Årsagen er enkel - PG reserverer yderligere hukommelse til argumenterne, og antallet af poster i sættet er kun begrænset af forretningslogikapplikationens ønskeliste. I især kliniske tilfælde var det nødvendigt at se "nummererede" argumenter større end $9000 - gør det ikke på denne måde.
Lad os omskrive forespørgslen og anvende allerede "to-niveau" serialisering:
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, i tilfælde af "komplekse" værdier inde i en matrix, skal de indrammes med anførselstegn.
Det er klart, at man på denne måde kan "udvide" udvalget med et vilkårligt antal felter.
uroligheder, uroligheder, …
Fra tid til anden er der muligheder for at passere i stedet for en "array af arrays" flere "arrays af kolonner", som jeg nævnte
SELECT
unnest($1::text[]) k
, unnest($2::integer[]) v;
Med denne metode, hvis du laver en fejl, når du genererer værdilister for forskellige kolonner, er det meget nemt at få helt uventede resultater, som også afhænger af serverversionen:
-- $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
Fra version 9.3 har PostgreSQL fuldgyldige funktioner til at arbejde med json-typen. Derfor, hvis dine inputparametre er defineret i browseren, kan du lige der og danne json-objekt til SQL-forespørgsel:
SELECT
key k
, value v
FROM
json_each($1::json); -- '{"a":1,"b":2,"c":3,"d":4}'
For tidligere versioner kan samme metode bruges til hver(hstore), men korrekt "foldning" med undslippende komplekse objekter i hstore kan forårsage problemer.
json_populate_recordset
Hvis du på forhånd ved, at dataene fra "input" json-arrayet vil gå til at udfylde en eller anden tabel, kan du spare meget i "dereferencing"-felter og caste til de ønskede typer ved hjælp af json_populate_recordset-funktionen:
SELECT
*
FROM
json_populate_recordset(
NULL::pg_class
, $1::json -- $1 : '[{"relname":"pg_class","oid":1262},{"relname":"pg_namespace","oid":2615}]'
);
json_to_recordset
Og denne funktion vil simpelthen "udvide" den beståede række af objekter til en markering uden at stole på tabelformatet:
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 | 2
MIDLERTIDIG TABEL
Men hvis mængden af data i den transmitterede prøve er meget stor, så er det svært og nogle gange umuligt at smide det ind i en serialiseret parameter, da det kræver en engangs stor hukommelsesallokering. For eksempel skal du indsamle et stort parti hændelsesdata fra et eksternt system i lang, lang tid, og så vil du behandle det en gang på databasesiden.
I dette tilfælde ville den bedste løsning være at bruge
CREATE TEMPORARY TABLE tbl(k text, v integer);
...
INSERT INTO tbl(k, v) VALUES($1, $2); -- повторить много-много раз
...
-- тут делаем что-то полезное со всей этой таблицей целиком
Metoden er god til sjælden transmission af store mængder data.
Fra synspunktet om at beskrive strukturen af dens data, adskiller en midlertidig tabel sig fra en "almindelig" tabel i kun én funktion. i pg_class systemtabel, og i pg_type, pg_depend, pg_attribute, pg_attrdef, ... - og slet ingenting.
I websystemer med et stort antal kortlivede forbindelser for hver af dem vil en sådan tabel derfor hver gang generere nye systemposter, som slettes, når forbindelsen til databasen lukkes. Til sidst, ukontrolleret brug af TEMP TABLE fører til "hævelse" af tabeller i pg_catalog og bremse mange operationer, der bruger dem.
Det kan man selvfølgelig bekæmpe periodisk bestået VAKUUM FULD i henhold til systemkatalogtabellerne.
Sessionsvariabler
Antag, at behandlingen af dataene fra den foregående sag er ret kompleks for en enkelt SQL-forespørgsel, men du vil gøre det ret ofte. Det vil sige, at vi vil bruge proceduremæssig behandling i
Vi kan heller ikke bruge $n-parametre til at overføre til en anonym blok. Sessionsvariablerne og funktionen hjælper os med at komme ud af situationen. aktuelle_indstilling.
Før version 9.2 skulle du forudkonfigurere
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
Der findes andre løsninger på andre understøttede proceduresprog.
Kender du flere måder? Del i kommentarerne!
Kilde: www.habr.com