Одвреме-навреме има потреба од развивач помине сет на параметри или дури и цел избор на барањето „на влезот“. Понекогаш наидувате на многу чудни решенија за овој проблем.
Ајде да се вратиме наназад и да видиме што да не правиме, зошто и како можеме подобро.
Директно вметнување на вредности во телото на барањето
Обично изгледа вака:
query = "SELECT * FROM tbl WHERE id = " + value
...или вака:
query = "SELECT * FROM tbl WHERE id = :param".format(param=value)
Овој метод е кажано, напишано и
Речиси секогаш ова е директна патека до SQL инјекции и непотребно оптоварување на деловната логика, која е принудена да ја „лепи“ вашата линија за пребарување.
Овој пристап може делумно да се оправда само доколку е потребно користејќи партиција во PostgreSQL верзии 10 и подолу за да добиете поефикасен план. Во овие верзии, списокот на скенирани делови се одредува без да се земат предвид пренесените параметри, само врз основа на телото на барањето.
$n-аргументи
Користете
Променлив број на аргументи
Ќе не чекаат проблеми кога сакаме да поминеме непознат број аргументи:
... id IN ($1, $2, $3, ...) -- $1 : 2, $2 : 3, $3 : 5, ...
Ако го оставиме барањето во оваа форма, иако тоа ќе не заштити од потенцијални инјекции, сепак ќе доведе до потреба од спојување/паризирање на барањето за секоја опција во зависност од бројот на аргументи. Подобро е отколку да го правите секој пат, но можете и без него.
Доволно е да помине само еден параметар кој содржи сериско претставување на низа:
... id = ANY($1::integer[]) -- $1 : '{2,3,5,8,13}'
Единствената разлика е потребата експлицитно да се конвертира аргументот во саканиот тип на низа. Но, ова не предизвикува проблеми, бидејќи веќе однапред знаеме каде одиме.
Пренесување примерок (матрица)
Обично ова се секакви опции за пренос на збирки податоци за вметнување во базата на податоци „во едно барање“:
INSERT INTO tbl(k, v) VALUES($1,$2),($3,$4),...
Покрај проблемите опишани погоре со „повторното лепење“ на барањето, ова може да не доведе и до нема меморија и паѓање на серверот. Причината е едноставна - PG резервира дополнителна меморија за аргументи, а бројот на записи во множеството е ограничен само од потребите на апликацијата на деловната логика. Во особено клинички случаи морав да видам Аргументите за „број“ се повеќе од 9000 долари - не правете го тоа на овој начин.
Ајде да го преработиме барањето користејќи веќе серијализација на „две нивоа“.:
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;
Да, во случај на „комплексни“ вредности во низа, тие мора да бидат опкружени со наводници.
Јасно е дека на овој начин можете да „проширите“ избор со произволен број полиња.
негнездени, разгнездени,…
Одвреме-навреме има опции за полагање наместо „низа низи“ неколку „низи од колони“ што ги спомнав
SELECT
unnest($1::text[]) k
, unnest($2::integer[]) v;
Со овој метод, ако направите грешка при генерирање списоци со вредности за различни колони, многу е лесно да се неочекувани резултати, кои исто така зависат од верзијата на серверот:
-- $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
Од верзијата 9.3, PostgreSQL има полноправни функции за работа со типот json. Затоа, ако дефиницијата за влезните параметри се појави во вашиот прелистувач, можете да ја формирате токму таму json објект за SQL барање:
SELECT
key k
, value v
FROM
json_each($1::json); -- '{"a":1,"b":2,"c":3,"d":4}'
За претходните верзии, истиот метод може да се користи за секој (продавница), но правилното „свиткување“ со бегство од сложени објекти во hstore може да предизвика проблеми.
json_populate_recordset
Ако однапред знаете дека податоците од низата „влез“ json ќе се користат за пополнување на некоја табела, можете да заштедите многу во полињата „дереференцирање“ и да ги префрлите на потребните типови со користење на функцијата 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
И оваа функција едноставно ќе ја „прошири“ поминатата низа на објекти во избор, без да се потпира на форматот на табелата:
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
ПРИВРЕМЕНА ТАБЕЛА
Но, ако количината на податоци во пренесениот примерок е многу голема, тогаш нивното фрлање во еден сериски параметар е тешко, а понекогаш и невозможно, бидејќи бара еднократна одвои голема количина на меморија. На пример, треба да соберете голем пакет податоци за настани од надворешен систем долго, долго, а потоа сакате да го обработите еднократно на страната на базата на податоци.
Во овој случај, најдобро решение би било да се користи
CREATE TEMPORARY TABLE tbl(k text, v integer);
...
INSERT INTO tbl(k, v) VALUES($1, $2); -- повторить много-много раз
...
-- тут делаем что-то полезное со всей этой таблицей целиком
Методот е добар за повремени трансфери на големи количини податоци.
Од гледна точка на опишување на структурата на нејзините податоци, привремената табела се разликува од „редовната“ само на еден начин во системската табела pg_classи внатре pg_type, pg_depend, pg_attribute, pg_attrdef, ... - воопшто ништо.
Затоа, во веб-системи со голем број краткотрајни врски за секој од нив, таквата табела секој пат ќе генерира нови системски записи, кои се бришат кога врската со базата е затворена. На крајот, неконтролирана употреба на TEMP TABLE доведува до „отекување“ на табелите во pg_catalog и забавување на многу операции кои ги користат.
Се разбира, ова може да се справи со користење периодичен премин ВАКУУМ ПОЛН според табелите на системскиот каталог.
Променливи на сесија
Да претпоставиме дека обработката на податоците од претходниот случај е доста сложена за едно SQL барање, но сакате да го правите тоа доста често. Односно, сакаме да користиме процедурална обработка во
Исто така, нема да можеме да користиме $n-параметри за префрлање на анонимен блок. Променливите на сесијата и функцијата ќе ни помогнат да излеземе од оваа ситуација тековно_поставување.
Пред верзијата 9.2, беше неопходно претходно да се конфигурира
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
Други решенија може да се најдат на други поддржани процедурални јазици.
Дали знаете други начини? Споделете во коментари!
Извор: www.habr.com