Antipattern PostgreSQL: Nglewati Set lan Pilih menyang SQL

Saka wektu kanggo wektu pangembang perlu pass pesawat saka paramèter utawa malah kabeh pilihan kanggo request "ing lawang". Kadhangkala ana solusi sing aneh banget kanggo masalah iki.
Antipattern PostgreSQL: Nglewati Set lan Pilih menyang SQL
Ayo dadi "saka ngelawan" lan ndeleng carane ora nindakake, apa, lan carane sampeyan bisa nindakake iku luwih apik.

Langsung "sisipan" nilai ing awak panyuwunan

Biasane katon kaya iki:

query = "SELECT * FROM tbl WHERE id = " + value

... utawa kaya iki:

query = "SELECT * FROM tbl WHERE id = :param".format(param=value)

About cara iki ngandika, ditulis lan malah digambar cukup:

Antipattern PostgreSQL: Nglewati Set lan Pilih menyang SQL

Meh mesthi path langsung kanggo injeksi SQL lan beban ekstra ing logika bisnis, sing dipeksa kanggo "lem" senar pitakon sampeyan.

Pendekatan iki bisa ditrapake sebagian mung yen perlu. nggunakake partisi ing PostgreSQL versi 10 lan ngisor kanggo rencana sing luwih efisien. Ing versi kasebut, dhaptar bagean sing dipindai ditemtokake tanpa njupuk parameter sing dikirim, mung adhedhasar badan panyuwunan.

$n argumen

Gunakake panggonan panggonan paramèter apik, iku ngijini sampeyan kanggo nggunakake STATEMENTS disiapake, nyuda beban loro ing logika bisnis (string query dibentuk lan dikirim mung sapisan) lan ing server database (re-parsing lan planning ora dibutuhake kanggo saben Kayata panjalukan).

Nomer variabel argumen

Masalah bakal nunggu kita nalika kita pengin ngliwati nomer argumen sing ora dingerteni sadurunge:

... id IN ($1, $2, $3, ...) -- $1 : 2, $2 : 3, $3 : 5, ...

Yen sampeyan ninggalake panjaluk kasebut ing formulir iki, mula sanajan bakal nylametake kita saka injeksi potensial, nanging isih mbutuhake lem / ngurai panyuwunan kasebut. kanggo saben pilihan saka jumlah argumen. Wis luwih apik tinimbang nindakake saben wektu, nanging sampeyan bisa nindakake tanpa.

Iku cukup kanggo pass mung siji parameter ngemot perwakilan serial saka larik:

... id = ANY($1::integer[]) -- $1 : '{2,3,5,8,13}'

Bentenipun mung kudu ngonversi argumentasi kanthi jelas menyang jinis array sing dikarepake. Nanging iki ora nyebabake masalah, amarga kita wis ngerti luwih dhisik ing ngendi kita bakal ditangani.

Transfer sampel (matriks)

Biasane iki kabeh pilihan kanggo nransfer set data kanggo dilebokake menyang database "ing siji panyuwunan":

INSERT INTO tbl(k, v) VALUES($1,$2),($3,$4),...

Saliyane masalah kasebut ing ndhuwur karo "re-gluing" panjalukan, iki uga bisa mimpin kita kanggo kebak memorine lan kacilakan server. Alesané prasaja - PG cadangan memori tambahan kanggo bantahan, lan nomer cathetan ing pesawat diwatesi mung dening Wishlist aplikasi logika bisnis. Ing kasus utamané Clinical iku perlu kanggo ndeleng argumen "nomer" luwih saka $9000 - aja nglakoni cara iki.

Ayo nulis maneh pitakon, wis nglamar serialisasi "loro-tingkat".:

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;

Ya, ing kasus nilai "kompleks" ing array, kudu dibingkai nganggo kuotasi.
Cetha yen kanthi cara iki sampeyan bisa "ngembangake" pilihan kanthi jumlah lapangan sing sewenang-wenang.

unnest, unnest,…

Saka wektu kanggo wektu ana opsi kanggo pass tinimbang "array of arrays" sawetara "arrays of columns" sing dakkandhakake ing artikel pungkasan:

SELECT
  unnest($1::text[]) k
, unnest($2::integer[]) v;

Kanthi metode iki, yen sampeyan salah nalika nggawe dhaptar nilai kanggo kolom sing beda-beda, gampang banget kanggo ngrampungake. asil sing ora dikarepke, sing uga gumantung ing versi server:

-- $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

Miwiti saka versi 9.3, PostgreSQL nduweni fungsi lengkap kanggo nggarap jinis json. Mulane, yen paramèter input ditetepake ing browser, sampeyan bisa langsung lan mbentuk obyek json kanggo query SQL:

SELECT
  key k
, value v
FROM
  json_each($1::json); -- '{"a":1,"b":2,"c":3,"d":4}'

Kanggo versi sadurungé, cara sing padha bisa digunakake kanggo saben (hstore), nanging bener "lempitan" karo uwal obyek Komplek ing hstore bisa nimbulaké masalah.

json_populate_recordset

Yen sampeyan ngerti sadurunge yen data saka "input" array json bakal ngisi sawetara tabel, sampeyan bisa nyimpen akeh ing kolom "dereferencing" lan casting menyang jinis sing dikarepake nggunakake fungsi 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

Lan fungsi iki mung bakal "ngembangake" macem-macem obyek menyang pilihan, tanpa gumantung ing format tabel:

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

TABEL SEMENTARA

Nanging yen jumlah data ing sampel sing ditularaké gedhe banget, banjur uncalan menyang siji parameter serialized angel, lan kadhangkala mokal, amarga iku mbutuhake siji-wektu. alokasi memori gedhe. Contone, sampeyan kudu ngumpulake kumpulan gedhe saka data acara saka sistem external kanggo dangu, lan banjur pengin proses siji-wektu ing sisih database.

Ing kasus iki, solusi sing paling apik bakal digunakake tabel sauntara:

CREATE TEMPORARY TABLE tbl(k text, v integer);
...
INSERT INTO tbl(k, v) VALUES($1, $2); -- повторить много-много раз
...
-- тут делаем что-то полезное со всей этой таблицей целиком

Cara kasebut apik kanggo transmisi arang volume gedhe data.
Saka sudut pandang njlèntrèhaké struktur data, tabel sauntara beda karo tabel "biasa" mung ing siji fitur. ing tabel sistem pg_class, lan ing pg_type, pg_depend, pg_attribute, pg_attrdef, ... - lan ora ana apa-apa.

Mulane, ing sistem web kanthi jumlah sambungan sing cendhak kanggo saben wong, tabel kasebut bakal ngasilake cathetan sistem anyar saben wektu, sing bakal dibusak nalika sambungan menyang database ditutup. Pungkasane, nggunakake uncontrolled TEMP TABLE ndadékaké kanggo "abuh" saka tabel ing pg_catalog lan kalem akeh operasi sing digunakake.
Mesthi, iki bisa dilawan pass periodik VACUUM FULL miturut tabel katalog sistem.

Variabel Sesi

Upaminipun ngolah data saka kasus sadurunge cukup rumit kanggo query SQL siji, nanging sampeyan pengin nindakake cukup kerep. Yaiku, kita pengin nggunakake pangolahan prosedural ing Blok DO, nanging nggunakake transfer data liwat tabel sementara bakal larang banget.

Kita uga ora bisa nggunakake $n-parameter kanggo pass menyang pemblokiran anonim. Variabel sesi lan fungsi bakal mbantu kita metu saka kahanan kasebut. saiki_setting.

Sadurunge versi 9.2, sampeyan kudu pra-konfigurasi papan jeneng khusus custom_variable_classes kanggo variabel sesi "dheweke". Ing versi saiki, sampeyan bisa nulis kaya iki:

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

Ana solusi liyane sing kasedhiya ing basa prosedural liyane sing didhukung.

Ngerti cara liyane? Nuduhake ing komentar!

Source: www.habr.com

Add a comment