Nie zgadzam się z niektórymi radami w innych odpowiedziach. Można to zrobić za pomocą PL/pgSQL i myślę, że jest to w większości znacznie lepsze do składania zapytań w aplikacji klienckiej. Jest szybszy i czystszy, a aplikacja wysyła tylko absolutne minimum w żądaniach. Wyrażenia SQL są zapisywane w bazie danych, co ułatwia utrzymanie - chyba że chcesz zebrać całą logikę biznesową w aplikacji klienckiej, zależy to od ogólnej architektury.
Funkcja PL/pgSQL z dynamicznym SQL
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$;
Zadzwoń:
SELECT * FROM func(1, '_ad_nr_extra', '_ad_info', '_ad_postcode', '_sname');
SELECT * FROM func(1, _pname := 'foo');
Ponieważ wszystkie parametry funkcji mają wartości domyślne, możesz użyć pozycyjnego notacja, nazwana notacja lub mieszana notacja według własnego wyboru w wywołaniu funkcji. Zobacz:
- Funkcje ze zmienną liczbą parametrów wejściowych
Więcej wyjaśnień podstaw dynamicznego SQL:
- Refaktoryzuj funkcję PL/pgSQL, aby zwrócić dane wyjściowe różnych zapytań SELECT
concat()
funkcja jest kluczowa dla budowania ciągu. Został wprowadzony w Postgresie 9.1.
ELSE
gałąź CASE
domyślna instrukcja to NULL
gdy nie jest obecny. Upraszcza kod.
USING
klauzula EXECUTE
uniemożliwia wstrzyknięcie SQL, ponieważ wartości są przekazywane jako wartości i pozwala na bezpośrednie użycie wartości parametrów, dokładnie tak jak w przygotowanych zestawieniach.
NULL
wartości są tutaj używane do ignorowania parametrów. W rzeczywistości nie są używane do wyszukiwania.
Nie potrzebujesz nawiasów wokół SELECT
z RETURN QUERY
.
Prosta funkcja SQL
możesz zrób to za pomocą zwykłej funkcji SQL i unikaj dynamicznego SQL. W niektórych przypadkach może to być szybsze. Ale nie spodziewałbym się tego w tym przypadku . Planowanie zapytania bez zbędnych sprzężeń i predykatów zazwyczaj daje najlepsze wyniki. Koszt planowania prostego zapytania, takiego jak to, jest prawie znikomy.
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$;
Identyczne połączenie.
Aby skutecznie ignorować parametry za pomocą NULL
wartości :
($1 IS NULL OR a.ad_nr = $1)
Aby faktycznie używać wartości NULL jako parametrów , zamiast tego użyj tej konstrukcji:
($1 IS NULL AND a.ad_nr IS NULL OR a.ad_nr = $1) -- AND binds before OR
Pozwala to również na indeksy do użycia.
W omawianym przypadku zastąp wszystkie wystąpienia LEFT JOIN
z JOIN
.
db<>graj tutaj - z prostym demo dla wszystkich wariantów.
Stary sqlfiddle
Na marginesie
-
Nie używaj
name
iid
jako nazwy kolumn. Nie są one opisowe i kiedy dołączasz do kilku stołów (tak jak robisz toa lot
w relacyjnej bazie danych), otrzymujesz kilka kolumn o nazwiename
lubid
i trzeba dołączyć aliasy, aby uporządkować bałagan. -
Proszę sformatować poprawnie swój kod SQL, przynajmniej podczas zadawania pytań publicznych. Ale rób to również prywatnie, dla własnego dobra.