Laiku pa laikam izstrādātājam ir nepieciešams nodod pieprasījumam parametru kopu vai pat visu atlasi "pie ieejas". Dažreiz šai problēmai ir ļoti dīvaini risinājumi.
Ejam "no pretējās puses" un redzēsim, kā to nedarīt, kāpēc un kā to var izdarīt labāk.
Tieša vērtību "ievietošana" pieprasījuma pamattekstā
Parasti tas izskatās apmēram šādi:
query = "SELECT * FROM tbl WHERE id = " + value
... vai šādi:
query = "SELECT * FROM tbl WHERE id = :param".format(param=value)
Par šo metodi ir teikts, rakstīts un
Gandrīz vienmēr tā ir tiešais ceļš uz SQL injekciju un papildu slodze biznesa loģikai, kas ir spiesta “pielīmēt” jūsu vaicājuma virkni.
Šo pieeju var daļēji attaisnot tikai nepieciešamības gadījumā. izmantot sadalīšanu PostgreSQL versijās 10 un jaunākās versijās, lai iegūtu efektīvāku plānu. Šajās versijās skenēto sadaļu saraksts tiek noteikts, neņemot vērā pārsūtītos parametrus, tikai pamatojoties uz pieprasījuma pamattekstu.
$n argumenti
Izmantot
Mainīgs argumentu skaits
Problēmas mūs gaidīs, kad vēlēsimies iepriekš nodot nezināmu skaitu argumentu:
... id IN ($1, $2, $3, ...) -- $1 : 2, $2 : 3, $3 : 5, ...
Ja atstājat pieprasījumu šajā veidlapā, lai gan tas mūs pasargās no iespējamām injekcijām, tas joprojām radīs nepieciešamību pielīmēt / parsēt pieprasījumu katrai opcijai no argumentu skaita. Jau labāk, nekā to darīt katru reizi, bet var iztikt arī bez tā.
Pietiek nodot tikai vienu parametru, kas satur masīva serializēts attēlojums:
... id = ANY($1::integer[]) -- $1 : '{2,3,5,8,13}'
Vienīgā atšķirība ir nepieciešamība nepārprotami pārvērst argumentu vēlamajā masīva veidā. Bet tas nerada problēmas, jo mēs jau iepriekš zinām, kur mēs risinām.
Parauga pārsūtīšana (matrica)
Parasti šīs ir visu veidu iespējas datu kopu pārsūtīšanai ievietošanai datu bāzē “vienā pieprasījumā”:
INSERT INTO tbl(k, v) VALUES($1,$2),($3,$4),...
Papildus iepriekš aprakstītajām problēmām saistībā ar pieprasījuma "pārlīmēšanu", tas var arī mūs novest pie pietrūkst atmiņas un servera avārija. Iemesls ir vienkāršs - PG argumentiem rezervē papildu atmiņu, un ierakstu skaitu komplektā ierobežo tikai biznesa loģikas lietojumprogramma Wishlist. Īpaši klīniskos gadījumos bija nepieciešams redzēt "numurētie" argumenti, kas lielāki par 9000 USD - nedari to šādā veidā.
Pārrakstīsim vaicājumu, jau piesakoties "divu līmeņu" serializācija:
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;
Jā, ja masīvā ir "sarežģītas" vērtības, tās jāierāmē ar pēdiņām.
Ir skaidrs, ka šādā veidā jūs varat "paplašināt" atlasi ar patvaļīgu skaitu lauku.
unnest, unnest,…
Ik pa laikam ir iespējas "masīvu masīva" vietā nodot vairākus manis pieminētos "kolonnu masīvus"
SELECT
unnest($1::text[]) k
, unnest($2::integer[]) v;
Izmantojot šo metodi, ja pieļaujat kļūdu, ģenerējot dažādu kolonnu vērtību sarakstus, to ir ļoti viegli iegūt pilnībā negaidīti rezultāti, kas ir atkarīgi arī no servera versijas:
-- $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
Sākot ar versiju 9.3, PostgreSQL ir pilnvērtīgas funkcijas darbam ar json tipu. Tāpēc, ja jūsu ievades parametri ir definēti pārlūkprogrammā, varat tur izveidot formu json objekts SQL vaicājumam:
SELECT
key k
, value v
FROM
json_each($1::json); -- '{"a":1,"b":2,"c":3,"d":4}'
Iepriekšējām versijām var izmantot to pašu metodi katrs(hveikals), taču pareiza "locīšana" ar sarežģītiem objektiem hstore var radīt problēmas.
json_populate_recordset
Ja jau iepriekš zināt, ka dati no “input” json masīva tiks aizpildīti, lai aizpildītu kādu tabulu, varat daudz ietaupīt “atsauces” laukos un apraidē uz vajadzīgajiem veidiem, izmantojot funkciju 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
Un šī funkcija vienkārši “paplašinās” nodoto objektu masīvu atlasē, nepaļaujoties uz tabulas formātu:
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
PAGAIDU GALDS
Bet, ja datu apjoms pārraidītajā izlasē ir ļoti liels, tad to iemest vienā serializētā parametrā ir grūti un dažreiz neiespējami, jo tas prasa vienreizēju liels atmiņas sadalījums. Piemēram, jums ir jāsavāc liela notikumu datu pakete no ārējās sistēmas ilgu, ilgu laiku un pēc tam vēlaties tos apstrādāt vienreiz datu bāzes pusē.
Šajā gadījumā labākais risinājums būtu izmantot
CREATE TEMPORARY TABLE tbl(k text, v integer);
...
INSERT INTO tbl(k, v) VALUES($1, $2); -- повторить много-много раз
...
-- тут делаем что-то полезное со всей этой таблицей целиком
Metode ir laba par retu lielu apjomu pārraidi datus.
No datu struktūras apraksta viedokļa pagaidu tabula no “parastās” atšķiras tikai ar vienu pazīmi. pg_class sistēmas tabulāun pg_type, pg_depend, pg_attribute, pg_attrdef, ... - un vispār nekā.
Tāpēc tīmekļa sistēmās ar lielu skaitu īslaicīgu savienojumu katram no tiem šāda tabula katru reizi ģenerēs jaunus sistēmas ierakstus, kas tiek dzēsti, kad savienojums ar datu bāzi tiek slēgts. Galu galā, nekontrolēta TEMP TABLE izmantošana izraisa pg_catalog tabulu "pietūkumu" un palēninot daudzas darbības, kurās tās tiek izmantotas.
Protams, ar to var cīnīties periodiskā caurlaide VAKUUMS PILNS saskaņā ar sistēmas kataloga tabulām.
Sesijas mainīgie
Pieņemsim, ka iepriekšējā gadījuma datu apstrāde vienam SQL vaicājumam ir diezgan sarežģīta, bet jūs vēlaties to darīt diezgan bieži. Tas ir, mēs vēlamies izmantot procesuālo apstrādi
Mēs arī nevaram izmantot $n-parametrus, lai pārietu uz anonīmu bloku. Sesijas mainīgie un funkcija palīdzēs mums izkļūt no situācijas. pašreizējais_iestatījums.
Pirms versijas 9.2 jums bija jāveic iepriekšēja konfigurācija
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
Citās atbalstītajās procesuālajās valodās ir pieejami arī citi risinājumi.
Vai zināt vairāk veidu? Dalies komentāros!
Avots: www.habr.com