Saya tidak setuju dengan beberapa saran dalam jawaban lain. Ini dapat dilakukan dengan PL/pgSQL dan menurut saya sebagian besar jauh lebih unggul untuk merakit kueri dalam aplikasi klien. Ini lebih cepat dan lebih bersih dan aplikasi hanya mengirimkan permintaan minimum melalui kabel. Pernyataan SQL disimpan di dalam database, yang membuatnya lebih mudah untuk dipelihara - kecuali jika Anda ingin mengumpulkan semua logika bisnis dalam aplikasi klien, ini tergantung pada arsitektur umum.
Fungsi PL/pgSQL dengan SQL dinamis
CREATE OR REPLACE FUNCTION func(
_ad_nr int = NULL
, _ad_nr_extra text = NULL
, _ad_info text = NULL
, _ad_postcode text = NULL
, _sname text = NULL
, _pname text = NULL
, _cname text = NULL)
RETURNS TABLE(id int, match text, score int, nr int, nr_extra text
, info text, postcode text, street text, place text
, country text, the_geom geometry)
LANGUAGE plpgsql AS
$func$
BEGIN
-- RAISE NOTICE '%', -- for debugging
RETURN QUERY EXECUTE concat(
$$SELECT a.id, 'address'::text, 1 AS score, a.ad_nr, a.ad_nr_extra
, a.ad_info, a.ad_postcode$$
, CASE WHEN (_sname, _pname, _cname) IS NULL THEN ', NULL::text' ELSE ', s.name' END -- street
, CASE WHEN (_pname, _cname) IS NULL THEN ', NULL::text' ELSE ', p.name' END -- place
, CASE WHEN _cname IS NULL THEN ', NULL::text' ELSE ', c.name' END -- country
, ', a.wkb_geometry'
, concat_ws('
JOIN '
, '
FROM "Addresses" a'
, CASE WHEN NOT (_sname, _pname, _cname) IS NULL THEN '"Streets" s ON s.id = a.street_id' END
, CASE WHEN NOT (_pname, _cname) IS NULL THEN '"Places" p ON p.id = s.place_id' END
, CASE WHEN _cname IS NOT NULL THEN '"Countries" c ON c.id = p.country_id' END
)
, concat_ws('
AND '
, '
WHERE TRUE'
, CASE WHEN $1 IS NOT NULL THEN 'a.ad_nr = $1' END
, CASE WHEN $2 IS NOT NULL THEN 'a.ad_nr_extra = $2' END
, CASE WHEN $3 IS NOT NULL THEN 'a.ad_info = $3' END
, CASE WHEN $4 IS NOT NULL THEN 'a.ad_postcode = $4' END
, CASE WHEN $5 IS NOT NULL THEN 's.name = $5' END
, CASE WHEN $6 IS NOT NULL THEN 'p.name = $6' END
, CASE WHEN $7 IS NOT NULL THEN 'c.name = $7' END
)
)
USING $1, $2, $3, $4, $5, $6, $7;
END
$func$;
Telepon:
SELECT * FROM func(1, '_ad_nr_extra', '_ad_info', '_ad_postcode', '_sname');
SELECT * FROM func(1, _pname := 'foo');
Karena semua parameter fungsi memiliki nilai default, Anda dapat menggunakan posisional notasi, bernama notasi atau campuran notasi yang Anda pilih dalam panggilan fungsi. Lihat:
- Fungsi dengan jumlah variabel parameter input
Penjelasan lebih lanjut untuk dasar-dasar SQL dinamis:
- Memfaktorkan ulang fungsi PL/pgSQL untuk mengembalikan output dari berbagai kueri SELECT
concat()
fungsi adalah instrumental untuk membangun string. Itu diperkenalkan dengan Postgres 9.1.
ELSE
cabang dari CASE
pernyataan default ke NULL
ketika tidak hadir. Menyederhanakan kode.
USING
klausa untuk EXECUTE
membuat injeksi SQL tidak mungkin karena nilai dilewatkan sebagai nilai dan memungkinkan untuk menggunakan nilai parameter secara langsung, persis seperti dalam pernyataan yang disiapkan.
NULL
nilai digunakan untuk mengabaikan parameter di sini. Mereka sebenarnya tidak digunakan untuk mencari.
Anda tidak perlu tanda kurung di sekitar SELECT
dengan RETURN QUERY
.
Fungsi SQL sederhana
Anda bisa lakukan dengan fungsi SQL biasa dan hindari SQL dinamis. Untuk beberapa kasus ini mungkin lebih cepat. Tapi saya tidak mengharapkannya dalam kasus ini . Merencanakan kueri tanpa bergabung dan predikat yang tidak perlu biasanya menghasilkan hasil terbaik. Biaya perencanaan untuk kueri sederhana seperti ini hampir dapat diabaikan.
CREATE OR REPLACE FUNCTION func_sql(
_ad_nr int = NULL
, _ad_nr_extra text = NULL
, _ad_info text = NULL
, _ad_postcode text = NULL
, _sname text = NULL
, _pname text = NULL
, _cname text = NULL)
RETURNS TABLE(id int, match text, score int, nr int, nr_extra text
, info text, postcode text, street text, place text
, country text, the_geom geometry)
LANGUAGE sql AS
$func$
SELECT a.id, 'address' AS match, 1 AS score, a.ad_nr, a.ad_nr_extra
, a.ad_info, a.ad_postcode
, s.name AS street, p.name AS place
, c.name AS country, a.wkb_geometry
FROM "Addresses" a
LEFT JOIN "Streets" s ON s.id = a.street_id
LEFT JOIN "Places" p ON p.id = s.place_id
LEFT JOIN "Countries" c ON c.id = p.country_id
WHERE ($1 IS NULL OR a.ad_nr = $1)
AND ($2 IS NULL OR a.ad_nr_extra = $2)
AND ($3 IS NULL OR a.ad_info = $3)
AND ($4 IS NULL OR a.ad_postcode = $4)
AND ($5 IS NULL OR s.name = $5)
AND ($6 IS NULL OR p.name = $6)
AND ($7 IS NULL OR c.name = $7)
$func$;
Panggilan yang sama.
Untuk secara efektif mengabaikan parameter dengan NULL
nilai :
($1 IS NULL OR a.ad_nr = $1)
Untuk benar-benar menggunakan nilai NULL sebagai parameter , gunakan konstruksi ini sebagai gantinya:
($1 IS NULL AND a.ad_nr IS NULL OR a.ad_nr = $1) -- AND binds before OR
Ini juga memungkinkan indeks untuk digunakan.
Untuk kasus yang ada, ganti semua instance LEFT JOIN
dengan JOIN
.
db<>main biola di sini - dengan demo sederhana untuk semua varian.
sqlfiddle lama
Selain
-
Jangan gunakan
name
danid
sebagai nama kolom. Mereka tidak deskriptif dan ketika Anda bergabung dengan sekelompok tabel (seperti yang Anda lakukan untuka lot
dalam database relasional), Anda berakhir dengan beberapa kolom yang semuanya bernamaname
atauid
, dan harus melampirkan alias untuk menyortir kekacauan. -
Harap format SQL Anda dengan benar, setidaknya saat mengajukan pertanyaan publik. Tapi lakukan juga secara pribadi, untuk kebaikan Anda sendiri.