Då och då behöver utvecklaren skicka en uppsättning parametrar eller till och med ett helt urval till begäran "vid ingången". Ibland finns det väldigt konstiga lösningar på detta problem.
Låt oss gå "från motsatsen" och se hur man inte gör det, varför och hur du kan göra det bättre.
Direkt "infogning" av värden i förfrågningskroppen
Det brukar se ut ungefär så här:
query = "SELECT * FROM tbl WHERE id = " + value
... eller så här:
query = "SELECT * FROM tbl WHERE id = :param".format(param=value)
Om denna metod sägs, skrivs och
Det är det nästan alltid direkt väg till SQL-injektion och en extra belastning på affärslogiken, som tvingas "limma" din frågesträng.
Detta tillvägagångssätt kan endast delvis motiveras om det behövs. använda partitionering i PostgreSQL version 10 och nedan för en mer effektiv plan. I dessa versioner bestäms listan över skannade sektioner utan att ta hänsyn till de överförda parametrarna, endast på basis av förfrågningsorganet.
$n argument
Använd
Variabelt antal argument
Problem kommer att vänta oss när vi vill skicka ett okänt antal argument i förväg:
... id IN ($1, $2, $3, ...) -- $1 : 2, $2 : 3, $3 : 5, ...
Om du lämnar begäran i det här formuläret, kommer det fortfarande att leda till behovet av att limma / analysera begäran, även om det kommer att rädda oss från potentiella injektioner för varje alternativ från antalet argument. Redan bättre än att göra det varje gång, men du kan klara dig utan det.
Det räcker att bara skicka en parameter som innehåller serialiserad representation av en array:
... id = ANY($1::integer[]) -- $1 : '{2,3,5,8,13}'
Den enda skillnaden är behovet av att explicit konvertera argumentet till den önskade arraytypen. Men detta skapar inga problem, eftersom vi redan i förväg vet var vi vänder oss.
Provöverföring (matris)
Vanligtvis är dessa alla möjliga alternativ för att överföra datamängder för infogning i databasen "i en begäran":
INSERT INTO tbl(k, v) VALUES($1,$2),($3,$4),...
Utöver de ovan beskrivna problemen med "omlimningen" av förfrågan kan detta också leda oss till slut på minne och serverkrasch. Anledningen är enkel - PG reserverar ytterligare minne för argumenten, och antalet poster i uppsättningen begränsas endast av affärslogikapplikationens önskelista. I särskilt kliniska fall var det nödvändigt att se "numrerade" argument större än $9000 - gör inte på det här sättet.
Låt oss skriva om frågan och ansöka redan "tvånivås" 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 fallet med "komplexa" värden inuti en array måste de ramas in med citattecken.
Det är klart att man på så sätt kan "expandera" urvalet med ett godtyckligt antal fält.
olust, olust, …
Från tid till annan finns det alternativ för att skicka i stället för en "matris av matriser" flera "matriser av kolumner" som jag nämnde
SELECT
unnest($1::text[]) k
, unnest($2::integer[]) v;
Med den här metoden, om du gör ett misstag när du genererar värdelistor för olika kolumner, är det mycket lätt att få helt oväntade resultat, som också beror på 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
Från och med version 9.3 har PostgreSQL fullfjädrade funktioner för att arbeta med json-typen. Därför, om dina inmatningsparametrar är definierade i webbläsaren, kan du direkt där och bilda json-objekt för SQL-fråga:
SELECT
key k
, value v
FROM
json_each($1::json); -- '{"a":1,"b":2,"c":3,"d":4}'
För tidigare versioner kan samma metod användas för varje (hstore), men korrekt "vikning" med flyktande komplexa objekt i hstore kan orsaka problem.
json_populate_recordset
Om du i förväg vet att data från "input" json-arrayen kommer att fylla i någon tabell, kan du spara mycket i "dereferencing"-fält och casta till önskade typer med hjälp av 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
Och den här funktionen kommer helt enkelt att "expandera" den passerade arrayen av objekt till ett urval, utan att förlita sig på tabellformatet:
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
TILLFÄLLIGT BORD
Men om mängden data i det överförda provet är mycket stort, är det svårt, och ibland omöjligt, att kasta in det i en serialiserad parameter, eftersom det kräver en engångs stor minnesallokering. Till exempel behöver du samla in en stor batch händelsedata från ett externt system under lång, lång tid, och sedan vill du bearbeta det en gång på databassidan.
I det här fallet skulle den bästa lösningen vara att använda
CREATE TEMPORARY TABLE tbl(k text, v integer);
...
INSERT INTO tbl(k, v) VALUES($1, $2); -- повторить много-много раз
...
-- тут делаем что-то полезное со всей этой таблицей целиком
Metoden är bra för sällsynt överföring av stora volymer data.
När det gäller att beskriva strukturen för dess data, skiljer sig en temporär tabell från en "vanlig" tabell i endast en funktion. i systemtabellen pg_class, och i pg_type, pg_depend, pg_attribute, pg_attrdef, ... – och ingenting alls.
Därför, i webbsystem med ett stort antal kortlivade anslutningar för var och en av dem, kommer en sådan tabell att generera nya systemposter varje gång, som raderas när anslutningen till databasen stängs. Så småningom, okontrollerad användning av TEMP TABLE leder till "svällning" av tabeller i pg_catalog och saktar ner många operationer som använder dem.
Detta går naturligtvis att bekämpa periodiskt godkänt VAKUUM FULL enligt systemkatalogtabellerna.
Sessionsvariabler
Anta att behandlingen av data från föregående fall är ganska komplex för en enda SQL-fråga, men du vill göra det ganska ofta. Det vill säga vi vill använda procedurbehandling i
Vi kan inte heller använda $n-parametrar för att skicka till ett anonymt block. Sessionsvariablerna och funktionen hjälper oss att ta oss ur situationen. nuvarande inställning.
Före version 9.2 var du tvungen att förkonfigurera
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
Det finns andra lösningar tillgängliga på andra procedurspråk som stöds.
Vet du fler sätt? Dela i kommentarerna!
Källa: will.com