Zaman zaman tərtibatçıya ehtiyac var sorğuya bir sıra parametrlər və ya hətta bütün seçimi ötürmək "girişdə". Bəzən bu problemin çox qəribə həll yolları var.
Gəlin "əks tərəfdən" gedək və bunu necə etməməyi, niyə və necə daha yaxşı edə biləcəyinizi görək.
Sorğu orqanına dəyərlərin birbaşa "daxil edilməsi"
Adətən belə görünür:
query = "SELECT * FROM tbl WHERE id = " + value
... və ya bu kimi:
query = "SELECT * FROM tbl WHERE id = :param".format(param=value)
Bu üsul haqqında deyilir, yazılır və
Demək olar ki, həmişə belədir SQL inyeksiyasına birbaşa yol və sorğu sətirinizi “yapışdırmağa” məcbur olan iş məntiqinə əlavə yük.
Bu yanaşma yalnız zəruri hallarda qismən əsaslandırıla bilər. bölmədən istifadə edin daha səmərəli plan üçün PostgreSQL 10 və aşağıda versiyalarında. Bu versiyalarda skan edilmiş bölmələrin siyahısı ötürülən parametrlər nəzərə alınmadan, yalnız sorğu orqanı əsasında müəyyən edilir.
$n arqumentlər
Istifadə
Arqumentlərin dəyişən sayı
Əvvəlcədən naməlum sayda arqument ötürmək istədikdə bizi problemlər gözləyəcək:
... id IN ($1, $2, $3, ...) -- $1 : 2, $2 : 3, $3 : 5, ...
Sorğunu bu formada tərk etsəniz, bu, bizi potensial inyeksiyalardan xilas etsə də, yenə də sorğunu yapışdırmaq / təhlil etmək ehtiyacına səbəb olacaq. arqumentlərin sayından hər bir seçim üçün. Onsuz da bunu hər dəfə etməkdən daha yaxşıdır, amma onsuz da edə bilərsiniz.
Yalnız bir parametrdən keçmək kifayətdir massivin seriyalı təsviri:
... id = ANY($1::integer[]) -- $1 : '{2,3,5,8,13}'
Yeganə fərq, arqumenti açıq şəkildə istədiyiniz massiv tipinə çevirmək ehtiyacıdır. Amma bu, problem yaratmır, çünki biz hara müraciət etdiyimizi əvvəlcədən bilirik.
Nümunə ötürülməsi (matris)
Adətən bunlar verilənlər bazasına "bir sorğuda" daxil etmək üçün məlumat dəstlərinin ötürülməsi üçün hər cür seçimdir:
INSERT INTO tbl(k, v) VALUES($1,$2),($3,$4),...
Sorğunun “yenidən yapışdırılması” ilə bağlı yuxarıda təsvir olunan problemlərə əlavə olaraq, bu da bizi buna gətirib çıxara bilər yaddaş dolub və server qəzası. Səbəb sadədir - PG arqumentlər üçün əlavə yaddaş saxlayır və dəstdəki qeydlərin sayı yalnız biznes məntiqi tətbiqi İstək siyahısı ilə məhdudlaşdırılır. Xüsusilə kliniki hallarda görmək lazım idi 9000 dollardan çox "nömrələnmiş" arqumentlər - belə etmə.
Artıq müraciət edərək sorğunu yenidən yazaq "iki səviyyəli" serializasiya:
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;
Bəli, massiv daxilindəki "mürəkkəb" dəyərlər vəziyyətində, onlar dırnaq işarələri ilə çərçivəyə salınmalıdır.
Aydındır ki, bu şəkildə seçimi ixtiyari sayda sahə ilə "genişlədə" bilərsiniz.
yıxmaq, yersizləşdirmək, …
Zaman zaman qeyd etdiyim bir neçə "massivlər massivi" əvəzinə keçid variantları var.
SELECT
unnest($1::text[]) k
, unnest($2::integer[]) v;
Bu üsulla, müxtəlif sütunlar üçün dəyər siyahıları yaradarkən səhv etsəniz, tamamilə əldə etmək çox asandır. gözlənilməz nəticələr, bu da server versiyasından asılıdır:
-- $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 versiyasından başlayaraq PostgreSQL json növü ilə işləmək üçün tam hüquqlu funksiyalara malikdir. Buna görə də, əgər sizin giriş parametrləriniz brauzerdə müəyyən edilibsə, siz elə oradan formaya sala bilərsiniz SQL sorğusu üçün json obyekti:
SELECT
key k
, value v
FROM
json_each($1::json); -- '{"a":1,"b":2,"c":3,"d":4}'
Əvvəlki versiyalar üçün eyni üsuldan istifadə edilə bilər hər (hstore), lakin hstore-da mürəkkəb obyektlərdən qaçmaqla düzgün "qatlama" problemlər yarada bilər.
json_populate_recordset
Əgər “input” json massivindən verilənlərin hansısa cədvəli doldurmağa gedəcəyini əvvəlcədən bilsəniz, json_populate_recordset funksiyasından istifadə edərək “referencing” sahələrində çox qənaət edə və istədiyiniz tiplərə köçürə bilərsiniz:
SELECT
*
FROM
json_populate_recordset(
NULL::pg_class
, $1::json -- $1 : '[{"relname":"pg_class","oid":1262},{"relname":"pg_namespace","oid":2615}]'
);
json_to_recordset
Və bu funksiya sadəcə olaraq cədvəl formatına etibar etmədən, ötürülən obyektlər massivini seçimə “genişləndirəcək”:
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
MÜVƏQƏTİ MASA
Ancaq ötürülən nümunədəki məlumatların miqdarı çox böyükdürsə, onu bir seriallaşdırılmış parametrə atmaq çətindir və bəzən qeyri-mümkündür, çünki birdəfəlik tələb olunur. böyük yaddaş ayrılması. Məsələn, siz uzun, uzun müddətə xarici sistemdən hadisə məlumatlarının böyük partiyasını toplamalısınız və sonra onu verilənlər bazası tərəfində birdəfəlik emal etmək istəyirsiniz.
Bu vəziyyətdə ən yaxşı həll istifadə etmək olardı
CREATE TEMPORARY TABLE tbl(k text, v integer);
...
INSERT INTO tbl(k, v) VALUES($1, $2); -- повторить много-много раз
...
-- тут делаем что-то полезное со всей этой таблицей целиком
Metod yaxşıdır böyük həcmlərin nadir hallarda ötürülməsi üçün məlumatlar.
Verilənlərin strukturunu təsvir etmək baxımından müvəqqəti cədvəl “adi” cədvəldən yalnız bir xüsusiyyətinə görə fərqlənir. pg_class sistem cədvəlində, içində isə pg_type, pg_depend, pg_atribut, pg_attrdef, ... - və ümumiyyətlə heç nə.
Buna görə də, onların hər biri üçün çoxlu sayda qısamüddətli əlaqələri olan veb sistemlərdə belə bir cədvəl hər dəfə yeni sistem qeydləri yaradacaq, verilənlər bazası ilə əlaqə bağlandıqda silinir. Nəhayət, TEMP TABLE-dən nəzarətsiz istifadə pg_catalog-da cədvəllərin "şişməsinə" gətirib çıxarır və onlardan istifadə edən bir çox əməliyyatları yavaşlatmaq.
Təbii ki, bununla mübarizə aparmaq olar dövri keçid VACUUM FULL sistem kataloqu cədvəllərinə uyğun olaraq.
Sessiya Dəyişənləri
Tutaq ki, əvvəlki halda olan məlumatların emalı bir SQL sorğusu üçün olduqca mürəkkəbdir, lakin siz bunu tez-tez etmək istəyirsiniz. Yəni prosessual emaldan istifadə etmək istəyirik
Biz həmçinin anonim bloka keçmək üçün $n-parametrlərdən istifadə edə bilmərik. Sessiya dəyişənləri və funksiya vəziyyətdən çıxmağımıza kömək edəcək. cari_ayar.
9.2 versiyasından əvvəl siz əvvəlcədən konfiqurasiya etməli idiniz
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
Digər dəstəklənən prosedur dillərində başqa həllər də mövcuddur.
Daha çox yol bilirsiniz? Şərhlərdə paylaşın!
Mənbə: www.habr.com