Jeg er uenig i nogle af rådene i andre svar. Dette kan gøres med PL/pgSQL, og jeg synes, det for det meste er langt overlegent til at samle forespørgsler i en klientapplikation. Det er hurtigere og renere, og appen sender kun det absolutte minimum hen over ledningen i anmodninger. SQL-sætninger gemmes inde i databasen, hvilket gør det nemmere at vedligeholde - medmindre du ønsker at samle al forretningslogik i klientapplikationen, afhænger dette af den generelle arkitektur.
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 til:
SELECT * FROM func(1, '_ad_nr_extra', '_ad_info', '_ad_postcode', '_sname');
SELECT * FROM func(1, _pname := 'foo');
Da alle funktionsparametre har standardværdier, kan du bruge positionelle notation, navngivet notation eller blandet notation efter eget valg i funktionskaldet. Se:
- Funktioner med variabelt antal inputparametre
Mere forklaring på det grundlæggende i dynamisk SQL:
- Refaktorer en PL/pgSQL-funktion for at returnere output fra forskellige SELECT-forespørgsler
concat()
funktion er medvirkende til at bygge strengen. Det blev introduceret med Postgres 9.1.
ELSE
gren af en CASE
sætningen er som standard NULL
når de ikke er til stede. Forenkler koden.
USING
klausul for EXECUTE
gør SQL-injektion umulig, da værdier sendes som værdier og gør det muligt at bruge parameterværdier direkte, præcis som i forberedte udsagn.
NULL
værdier bruges til at ignorere parametre her. De bruges faktisk ikke til at søge.
Du behøver ikke parenteser omkring SELECT
med RETURN QUERY
.
Simpel SQL-funktion
Du kunne gør det med en almindelig SQL-funktion og undgå dynamisk SQL. I nogle tilfælde kan dette være hurtigere. Men jeg ville ikke forvente det i dette tilfælde . Planlægning af forespørgslen uden unødvendige joinforbindelser og prædikater giver typisk de bedste resultater. Planlægningsomkostninger for en simpel forespørgsel som denne er næsten ubetydelig.
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$;
Identisk opkald.
For effektivt at ignorere parametre med NULL
værdier :
($1 IS NULL OR a.ad_nr = $1)
For rent faktisk at bruge NULL-værdier som parametre , brug denne konstruktion i stedet:
($1 IS NULL AND a.ad_nr IS NULL OR a.ad_nr = $1) -- AND binds before OR
Dette giver også mulighed for indekser skal bruges.
For det aktuelle tilfælde skal du erstatte alle forekomster af LEFT JOIN
med JOIN
.
db<>spil her - med simpel demo for alle varianter.
Gamle sqlfiddle
Udover
-
Brug ikke
name
ogid
som kolonnenavne. De er ikke beskrivende, og når du tilslutter dig en masse tabeller (som du gør fora lot
i en relationsdatabase), ender du med flere kolonner, der alle heddername
ellerid
, og skal vedhæfte aliaser for at sortere rodet. -
Formatér venligst din SQL korrekt, i det mindste når du stiller offentlige spørgsmål. Men gør det også privat, for dit eget bedste.