Geliştiricinin zaman zaman ihtiyaç duyduğu isteğe bir dizi parametre veya hatta tüm bir seçimi iletin "girişte". Bazen bu soruna çok garip çözümler vardır.
"Tersten" gidelim ve bunu nasıl, neden ve nasıl daha iyi yapabileceğinizi görelim.
İstek gövdesine değerlerin doğrudan "eklenmesi"
Genellikle şöyle bir şeye benziyor:
query = "SELECT * FROM tbl WHERE id = " + value
... veya bunun gibi:
query = "SELECT * FROM tbl WHERE id = :param".format(param=value)
Bu yöntem hakkında söylenmiş, yazılmış ve
Neredeyse her zaman SQL enjeksiyonuna doğrudan yol ve sorgu dizginizi "yapıştırmak" zorunda kalan iş mantığı üzerinde fazladan bir yük.
Bu yaklaşım ancak gerekliyse kısmen haklı çıkarılabilir. bölümleme kullan Daha verimli bir plan için PostgreSQL sürüm 10 ve altı. Bu sürümlerde, taranan bölümlerin listesi, iletilen parametreler dikkate alınmadan, yalnızca istek gövdesi bazında belirlenir.
$n bağımsız değişken
kullanımı
Değişken bağımsız değişken sayısı
Önceden bilinmeyen sayıda argüman iletmek istediğimizde sorunlar bizi bekliyor olacak:
... id IN ($1, $2, $3, ...) -- $1 : 2, $2 : 3, $3 : 5, ...
Talebi bu formda bırakırsanız, bizi olası enjeksiyonlardan kurtaracak olsa da, yine de talebi yapıştırma / ayrıştırma ihtiyacına yol açacaktır. argüman sayısından her seçenek için. Zaten her seferinde yapmaktan daha iyidir, ama onsuz da yapabilirsiniz.
içeren tek bir parametreyi geçmek yeterlidir. bir dizinin serileştirilmiş gösterimi:
... id = ANY($1::integer[]) -- $1 : '{2,3,5,8,13}'
Tek fark, bağımsız değişkeni açıkça istenen dizi türüne dönüştürme ihtiyacıdır. Ancak bu, nereye hitap ettiğimizi önceden bildiğimiz için sorun yaratmaz.
Numune transferi (matris)
Genellikle bunlar, veri setlerini "tek bir istekte" veritabanına eklemek üzere aktarmak için her türlü seçenektir:
INSERT INTO tbl(k, v) VALUES($1,$2),($3,$4),...
Talebin "yeniden yapıştırılması" ile ilgili yukarıda açıklanan sorunlara ek olarak, bu da bizi şu sonuçlara götürebilir: bellek yetersiz ve sunucu çökmesi. Nedeni basit - PG bağımsız değişkenler için ek bellek ayırır ve kümedeki kayıt sayısı yalnızca iş mantığı uygulaması Wishlist ile sınırlıdır. Özellikle klinik vakalarda görmek gerekliydi. 9000$'dan büyük "numaralı" bağımsız değişkenler - bu şekilde yapma.
Şimdiden uygulayarak sorguyu yeniden yazalım "iki seviyeli" serileştirme:
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;
Evet, bir dizi içindeki "karmaşık" değerler söz konusu olduğunda, bunların tırnak işaretleri ile çerçevelenmesi gerekir.
Bu şekilde seçimi istediğiniz sayıda alanla "genişletebileceğiniz" açıktır.
huzursuzluk, huzursuzluk,…
Zaman zaman bir "dizi dizisi" yerine, bahsettiğim birkaç "sütun dizisi" yerine geçmek için seçenekler vardır.
SELECT
unnest($1::text[]) k
, unnest($2::integer[]) v;
Bu metot ile farklı kolonlar için değer listeleri oluştururken bir hata yaparsanız tamamen elde etmek çok kolaydır. beklenmedik sonuçlar, ayrıca sunucu sürümüne de bağ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 sürümünden başlayarak, PostgreSQL, json türüyle çalışmak için tam teşekküllü işlevlere sahiptir. Bu nedenle, giriş parametreleriniz tarayıcıda tanımlıysa, hemen orada ve form oluşturabilirsiniz. SQL sorgusu için json nesnesi:
SELECT
key k
, value v
FROM
json_each($1::json); -- '{"a":1,"b":2,"c":3,"d":4}'
Önceki sürümler için, aynı yöntem aşağıdakiler için kullanılabilir: her(mağaza), ancak hstore'da kaçan karmaşık nesnelerle doğru "katlama" sorunlara neden olabilir.
json_populate_recordset
"Giriş" json dizisinden gelen verilerin bir tabloyu doldurmaya gideceğini önceden biliyorsanız, json_populate_recordset işlevini kullanarak "referanslama" alanlarında ve istenen türlere atamada çok şey kaydedebilirsiniz:
SELECT
*
FROM
json_populate_recordset(
NULL::pg_class
, $1::json -- $1 : '[{"relname":"pg_class","oid":1262},{"relname":"pg_namespace","oid":2615}]'
);
json_to_recordset
Ve bu işlev, tablo biçimine güvenmeden, iletilen nesne dizisini basitçe bir seçime "genişletecektir":
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
GEÇİCİ TABLO
Ancak iletilen örnekteki veri miktarı çok büyükse, bunu tek bir seri hale getirilmiş parametreye atmak zordur ve bazen imkansızdır, çünkü bir kerelik büyük bellek ayırma. Örneğin, harici bir sistemden çok uzun bir süre büyük miktarda olay verisi toplamanız gerekiyor ve ardından bunu veritabanı tarafında tek seferlik işlemek istiyorsunuz.
Bu durumda en iyi çözüm kullanmak olacaktır.
CREATE TEMPORARY TABLE tbl(k text, v integer);
...
INSERT INTO tbl(k, v) VALUES($1, $2); -- повторить много-много раз
...
-- тут делаем что-то полезное со всей этой таблицей целиком
yöntem iyi büyük hacimlerin nadiren iletilmesi için verileri.
Verilerinin yapısını açıklama açısından, geçici bir tablo "normal" bir tablodan yalnızca bir özellikte farklılık gösterir. pg_class sistem tablosundaVe içinde pg_type, pg_depend, pg_attribute, pg_attrdef, ... - ve hiçbir şey.
Bu nedenle, her biri için çok sayıda kısa ömürlü bağlantıya sahip web sistemlerinde, böyle bir tablo her seferinde yeni sistem kayıtları üretecek ve veritabanına bağlantı kapatıldığında silinecektir. Sonunda, TEMP TABLE'ın kontrolsüz kullanımı, pg_catalog'daki tabloların "şişmesine" yol açar ve bunları kullanan birçok işlemi yavaşlatmak.
Elbette bununla mücadele edilebilir. periyodik geçiş VAKUM TAM sistem katalog tablolarına göre.
Oturum Değişkenleri
Önceki durumdaki verilerin işlenmesinin tek bir SQL sorgusu için oldukça karmaşık olduğunu, ancak bunu oldukça sık yapmak istediğinizi varsayalım. Yani, prosedürel işlemeyi kullanmak istiyoruz.
Ayrıca anonim bir bloğa geçmek için $n-parameters kullanamayız. Oturum değişkenleri ve işlev, durumdan çıkmamıza yardımcı olacaktır. şimdiki ayar.
9.2 sürümünden önce, önceden yapılandırmanız gerekiyordu
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
Desteklenen diğer yordamsal dillerde kullanılabilen başka çözümler de vardır.
Daha fazla yol biliyor musun? Yorumlarda paylaşın!
Kaynak: habr.com