Z času na čas vývojár potrebuje odovzdať do požiadavky súbor parametrov alebo dokonca celý výber "Pri vchode". Niekedy narazíte na veľmi zvláštne riešenia tohto problému.
Poďme späť a uvidíme, čo nerobiť, prečo a ako to môžeme urobiť lepšie.
Priame vloženie hodnôt do tela požiadavky
Zvyčajne to vyzerá nejako takto:
query = "SELECT * FROM tbl WHERE id = " + value
...alebo takto:
query = "SELECT * FROM tbl WHERE id = :param".format(param=value)
Táto metóda bola povedané, napísaná a
Takmer vždy to tak je priama cesta k SQL injekciám a zbytočné zaťaženie obchodnej logiky, ktorá je nútená „zlepiť“ váš riadok dopytu.
Tento prístup možno čiastočne odôvodniť iba v prípade potreby pomocou delenia v PostgreSQL verzie 10 a nižšej, aby ste získali efektívnejší plán. V týchto verziách sa zoznam naskenovaných úsekov určuje bez zohľadnenia prenášaných parametrov, len na základe tela požiadavky.
$n-argumentov
Použitie
Variabilný počet argumentov
Problémy nás čakajú, keď budeme chcieť odovzdať neznámy počet argumentov:
... id IN ($1, $2, $3, ...) -- $1 : 2, $2 : 3, $3 : 5, ...
Ak ponecháme žiadosť v tejto forme, aj keď nás to ochráni pred prípadnými injekciami, bude to stále viesť k potrebe zlúčiť/spracovať žiadosť pre každú možnosť v závislosti od počtu argumentov. Je to lepšie ako to robiť zakaždým, ale dá sa to zvládnuť aj bez toho.
Stačí odovzdať len jeden parameter obsahujúci serializovaná reprezentácia poľa:
... id = ANY($1::integer[]) -- $1 : '{2,3,5,8,13}'
Jediným rozdielom je potreba explicitne previesť argument na požadovaný typ poľa. To však nespôsobuje problémy, pretože už vopred vieme, kam ideme.
Prenos vzorky (matice)
Zvyčajne ide o najrôznejšie možnosti prenosu súborov údajov na vloženie do databázy „v jednej žiadosti“:
INSERT INTO tbl(k, v) VALUES($1,$2),($3,$4),...
Okrem vyššie popísaných problémov s „prelepovaním“ požiadavky nás môže viesť aj k tomu nedostatok pamäte a pád servera. Dôvod je jednoduchý – PG rezervuje dodatočnú pamäť pre argumenty a počet záznamov v sade je obmedzený len aplikačnými potrebami obchodnej logiky. V obzvlášť klinických prípadoch som musel vidieť Argumenty „číslo“ sú viac ako 9000 XNUMX USD - nerobte to týmto spôsobom.
Prepíšme požiadavku pomocou už „dvojúrovňovej“ serializácie:
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;
Áno, v prípade „komplexných“ hodnôt v poli musia byť ohraničené úvodzovkami.
Je jasné, že týmto spôsobom môžete „rozšíriť“ výber o ľubovoľný počet polí.
nehniezdiť, nehniezdiť,…
Z času na čas existujú možnosti, ako namiesto „pola polí“ odovzdať niekoľko „polí stĺpcov“, ktoré som spomenul
SELECT
unnest($1::text[]) k
, unnest($2::integer[]) v;
S touto metódou, ak urobíte chybu pri generovaní zoznamov hodnôt pre rôzne stĺpce, je veľmi ľahké získať neočakávané výsledky, ktoré tiež závisia od verzie servera:
-- $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
Od verzie 9.3 má PostgreSQL plnohodnotné funkcie pre prácu s typom json. Preto, ak sa definícia vstupných parametrov vyskytuje vo vašom prehliadači, môžete ju vytvoriť priamo tam json objekt pre SQL dotaz:
SELECT
key k
, value v
FROM
json_each($1::json); -- '{"a":1,"b":2,"c":3,"d":4}'
Pre predchádzajúce verzie je možné použiť rovnakú metódu každý(hstore), ale správna "konvolúcia" s únikom zložitých objektov v hstore môže spôsobiť problémy.
json_populate_recordset
Ak vopred viete, že údaje zo „vstupného“ poľa json sa použijú na vyplnenie nejakej tabuľky, môžete veľa ušetriť v poliach „dereferencovania“ a ich pretypovaní na požadované typy pomocou funkcie json_populate_recordset:
SELECT
*
FROM
json_populate_recordset(
NULL::pg_class
, $1::json -- $1 : '[{"relname":"pg_class","oid":1262},{"relname":"pg_namespace","oid":2615}]'
);
json_to_recordset
A táto funkcia jednoducho „rozšíri“ odovzdané pole objektov do výberu bez toho, aby sa spoliehala na formát tabuľky:
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
DOČASNÁ TABUĽKA
Ak je však množstvo údajov v prenesenej vzorke veľmi veľké, potom je ich vloženie do jedného serializovaného parametra ťažké a niekedy nemožné, pretože to vyžaduje jednorazový alokovať veľké množstvo pamäte. Potrebujete napríklad zbierať veľký balík dát o udalostiach z externého systému dlho, dlho a potom ich chcete jednorazovo spracovať na strane databázy.
V tomto prípade by bolo najlepším riešením použiť
CREATE TEMPORARY TABLE tbl(k text, v integer);
...
INSERT INTO tbl(k, v) VALUES($1, $2); -- повторить много-много раз
...
-- тут делаем что-то полезное со всей этой таблицей целиком
Metóda je dobrá na príležitostné presuny veľkých objemov údaje.
Z hľadiska popisu štruktúry svojich údajov sa dočasná tabuľka líši od „bežnej“ iba jedným spôsobom. v systémovej tabuľke pg_classA pg_type, pg_depend, pg_attribute, pg_attrdef, ... - vôbec nič.
Preto vo webových systémoch s veľkým počtom krátkodobých spojení pre každý z nich takáto tabuľka vygeneruje zakaždým nové systémové záznamy, ktoré sa po ukončení spojenia s databázou vymažú. nakoniec nekontrolované používanie TEMP TABLE vedie k „nafúknutiu“ tabuliek v pg_catalog a spomalenie mnohých operácií, ktoré ich využívajú.
Samozrejme, že sa to dá riešiť používaním periodický prechod VACUUM FULL podľa katalógových tabuliek systému.
Premenné relácie
Predpokladajme, že spracovanie údajov z predchádzajúceho prípadu je pomerne zložité na jeden SQL dotaz, ale chcete to robiť pomerne často. To znamená, že chceme použiť procesné spracovanie v
Taktiež nebudeme môcť použiť $n-parameters na prechod do anonymného bloku. Premenné relácie a funkcia nám pomôžu dostať sa z tejto situácie aktuálne_nastavenie.
Pred verziou 9.2 bolo potrebné predkonfigurovať
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é riešenia možno nájsť v iných podporovaných procedurálnych jazykoch.
Poznáte nejaké iné spôsoby? Podeľte sa v komentároch!
Zdroj: hab.com