Ik ben het niet eens met sommige adviezen in andere antwoorden. Dit kan met PL/pgSQL en ik denk dat het meestal veel beter is voor het samenstellen van query's in een clienttoepassing. Het is sneller en schoner en de app stuurt alleen het absolute minimum over de draad in verzoeken. SQL-instructies worden opgeslagen in de database, wat het onderhoud gemakkelijker maakt - tenzij u alle bedrijfslogica in de clienttoepassing wilt verzamelen, hangt dit af van de algemene architectuur.
PL/pgSQL-functie met dynamische 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$;
Bel:
SELECT * FROM func(1, '_ad_nr_extra', '_ad_info', '_ad_postcode', '_sname');
SELECT * FROM func(1, _pname := 'foo');
Aangezien alle functieparameters standaardwaarden hebben, kunt u positional . gebruiken notatie, genoemd notatie of gemengd notatie naar keuze in de functieaanroep. Zie:
- Functies met variabel aantal invoerparameters
Meer uitleg voor de basis van dynamische SQL:
- Refactor een PL/pgSQL-functie om de uitvoer van verschillende SELECT-query's te retourneren
De concat()
functie is instrumenteel voor het bouwen van de string. Het werd geïntroduceerd met Postgres 9.1.
De ELSE
tak van een CASE
instructie is standaard NULL
wanneer niet aanwezig. Vereenvoudigt de code.
De USING
clausule voor EXECUTE
maakt SQL-injectie onmogelijk omdat waarden worden doorgegeven als waarden en maakt het mogelijk om parameterwaarden direct te gebruiken, precies zoals in voorbereide instructies.
NULL
waarden worden hier gebruikt om parameters te negeren. Ze worden niet echt gebruikt om te zoeken.
U hebt geen haakjes nodig rond de SELECT
met RETURN QUERY
.
Eenvoudige SQL-functie
Je zou doe het met een gewone SQL-functie en vermijd dynamische SQL. In sommige gevallen kan dit sneller. Maar ik zou het niet verwachten in dit geval . Het plannen van de query zonder onnodige joins en predikaten levert doorgaans de beste resultaten op. De planningskosten voor een eenvoudige zoekopdracht als deze zijn bijna te verwaarlozen.
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$;
Identieke oproep.
Om effectief parameters te negeren met NULL
waarden :
($1 IS NULL OR a.ad_nr = $1)
Om daadwerkelijk NULL-waarden als parameters te gebruiken , gebruik in plaats daarvan deze constructie:
($1 IS NULL AND a.ad_nr IS NULL OR a.ad_nr = $1) -- AND binds before OR
Dit zorgt ook voor indexen te gebruiken.
Vervang in dit geval alle instanties van LEFT JOIN
met JOIN
.
db<>viool hier - met eenvoudige demo voor alle varianten.
Oude sqlfiddle
Terzijdes
-
Gebruik geen
name
enid
als kolomnamen. Ze zijn niet beschrijvend en wanneer u zich bij een aantal tabellen voegt (zoals u doet bija lot
in een relationele database), krijg je verschillende kolommen met de naamname
ofid
, en moet aliassen toevoegen om de rommel te sorteren. -
Formatteer uw SQL op de juiste manier, in ieder geval wanneer u openbare vragen stelt. Maar doe het ook privé, voor je eigen bestwil.