Jag håller inte med om några av råden i andra svar. Detta kan göras med PL/pgSQL och jag tror att det mestadels är vida överlägset att sammanställa frågor i en klientapplikation. Det är snabbare och renare och appen skickar bara det minsta över tråden i förfrågningar. SQL-satser sparas inuti databasen, vilket gör det lättare att underhålla - om du inte vill samla all affärslogik i klientapplikationen beror detta på den allmänna arkitekturen.
PL/pgSQL-funktion med dynamisk 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$;
Ring:
SELECT * FROM func(1, '_ad_nr_extra', '_ad_info', '_ad_postcode', '_sname');
SELECT * FROM func(1, _pname := 'foo');
Eftersom alla funktionsparametrar har standardvärden kan du använda positionell notation, namngiven notation eller blandad notering som du väljer i funktionsanropet. Se:
- Funktioner med variabelt antal indataparametrar
Mer förklaring till grunderna för dynamisk SQL:
- Refaktorera en PL/pgSQL-funktion för att returnera utdata från olika SELECT-frågor
concat()
funktion är instrumentell för att bygga strängen. Det introducerades med Postgres 9.1.
ELSE
gren av en CASE
standardsatsen är NULL
när den inte är närvarande. Förenklar koden.
USING
sats för EXECUTE
gör SQL-injektion omöjlig eftersom värden skickas som värden och gör det möjligt att använda parametervärden direkt, precis som i förberedda satser.
NULL
värden används för att ignorera parametrar här. De används faktiskt inte för att söka.
Du behöver inte ha parenteser runt SELECT
med RETURN QUERY
.
Enkel SQL-funktion
Du kunde gör det med en vanlig SQL-funktion och undvik dynamisk SQL. I vissa fall kan detta vara snabbare. Men jag skulle inte förvänta mig det i det här fallet . Att planera frågan utan onödiga kopplingar och predikat ger vanligtvis bästa resultat. Planeringskostnaden för en enkel fråga som denna är nästan försumbar.
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$;
Identiskt samtal.
För att effektivt ignorera parametrar med NULL
värden :
($1 IS NULL OR a.ad_nr = $1)
Att faktiskt använda NULL-värden som parametrar , använd den här konstruktionen istället:
($1 IS NULL AND a.ad_nr IS NULL OR a.ad_nr = $1) -- AND binds before OR
Detta tillåter även index som ska användas.
För det aktuella fallet, ersätt alla instanser av LEFT JOIN
med JOIN
.
db<>spela här - med enkel demo för alla varianter.
Gammal sqlfiddle
Asides
-
Använd inte
name
ochid
som kolumnnamn. De är inte beskrivande och när du går med i ett gäng tabeller (som du gör föra lot
i en relationsdatabas) får du flera kolumner som alla hetername
ellerid
, och måste bifoga alias för att sortera röran. -
Vänligen formatera din SQL korrekt, åtminstone när du ställer allmänna frågor. Men gör det privat också, för ditt eget bästa.