Не съм съгласен с някои от съветите в други отговори. Това може да се направи с PL/pgSQL и мисля, че най-вече е далеч по-добро за сглобяване на заявки в клиентско приложение. Той е по-бърз и по-чист и приложението изпраща само минимума по проводника в заявки. SQL изразите се записват в базата данни, което я прави по-лесна за поддръжка - освен ако не искате да съберете цялата бизнес логика в клиентското приложение, това зависи от общата архитектура.
PL/pgSQL функция с динамичен 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$;
Обадете се:
SELECT * FROM func(1, '_ad_nr_extra', '_ad_info', '_ad_postcode', '_sname');
SELECT * FROM func(1, _pname := 'foo');
Тъй като всички параметри на функцията имат стойности по подразбиране, можете да използвате позиционен нотация,именна нотация илисмесено нотация по ваш избор в извикването на функция. Вижте:
- Функции с променлив брой входни параметри
Още обяснение за основите на динамичния SQL:
- Рефакторирайте функция PL/pgSQL, за да върнете резултатите от различни SELECT заявки
concat()
функцията е важна за изграждането на низа. Той беше представен с Postgres 9.1.
ELSE
клон на CASE
операторът по подразбиране е NULL
когато не присъства. Опростява кода.
USING
клауза за EXECUTE
прави SQL инжектирането невъзможно, тъй като стойностите се предават като стойности и позволява директно използване на стойностите на параметрите, точно както в подготвените оператори.
NULL
стойностите се използват за игнориране на параметрите тук. Те всъщност не се използват за търсене.
Нямате нужда от скоби около SELECT
с RETURN QUERY
.
Проста SQL функция
Вие можете направете го с обикновена SQL функция и избягвайте динамичния SQL. В някои случаи това може да е по-бързо. Но не бих го очаквал в този случай . Планирането на заявката без ненужни съединения и предикати обикновено дава най-добри резултати. Разходите за планиране за проста заявка като тази са почти незначителни.
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$;
Идентично обаждане.
За ефективно игнориране на параметри с NULL
ценностите :
($1 IS NULL OR a.ad_nr = $1)
За действително използване на NULL стойности като параметри , вместо това използвайте тази конструкция:
($1 IS NULL AND a.ad_nr IS NULL OR a.ad_nr = $1) -- AND binds before OR
Това също позволява индекси да се използва.
За конкретния случай заменете всички екземпляри на LEFT JOIN
с JOIN
.
db<>цигулка тук - спроста демонстрация за всички варианти.
Стар sqlfiddle
Отстрани
-
Не използвайте
name
иid
като имена на колони. Те не са описателни и когато се присъедините към куп таблици (както правите сa lot
в релационна база данни), получавате няколко колони, всички с имеname
илиid
, и трябва да прикачите псевдоними, за да сортирате бъркотията. -
Моля, форматирайте правилно своя SQL, поне когато задавате обществени въпроси. Но направете го и насаме, за ваше добро.