Non sono d'accordo con alcuni dei consigli in altre risposte. Questo può essere fatto con PL/pgSQL e penso che sia per lo più di gran lunga superiore per assemblare query in un'applicazione client. È più veloce e più pulito e l'app invia solo il minimo indispensabile attraverso il cavo nelle richieste. Le istruzioni SQL vengono salvate all'interno del database, il che ne semplifica la manutenzione, a meno che non si desideri raccogliere tutta la logica aziendale nell'applicazione client, ciò dipende dall'architettura generale.
Funzione PL/pgSQL con SQL dinamico
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$;
Chiama:
SELECT * FROM func(1, '_ad_nr_extra', '_ad_info', '_ad_postcode', '_sname');
SELECT * FROM func(1, _pname := 'foo');
Poiché tutti i parametri di funzione hanno valori predefiniti, puoi utilizzare posizionale notazione, nome notazione o mista notazione a tua scelta nella chiamata di funzione. Vedi:
- Funzioni con numero variabile di parametri di input
Ulteriori spiegazioni sulle basi dell'SQL dinamico:
- Refactoring di una funzione PL/pgSQL per restituire l'output di varie query SELECT
Il concat()
la funzione è strumentale per costruire la stringa. È stato introdotto con Postgres 9.1.
Il ELSE
ramo di un CASE
l'istruzione predefinita è NULL
quando non presente. Semplifica il codice.
Il USING
clausola per EXECUTE
rende impossibile l'iniezione SQL poiché i valori vengono passati come valori e consente di utilizzare direttamente i valori dei parametri, esattamente come nelle istruzioni preparate.
NULL
i valori vengono utilizzati per ignorare i parametri qui. In realtà non vengono utilizzati per la ricerca.
Non hai bisogno di parentesi attorno a SELECT
con RETURN QUERY
.
Semplice funzione SQL
Potresti fallo con una semplice funzione SQL ed evita SQL dinamico. In alcuni casi questo potrebbe essere più veloce. Ma non me lo aspetterei in questo caso . La pianificazione della query senza join e predicati non necessari in genere produce risultati migliori. Il costo di pianificazione per una query semplice come questa è quasi trascurabile.
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$;
Chiamata identica.
Per ignorare efficacemente i parametri con NULL
valori :
($1 IS NULL OR a.ad_nr = $1)
Per utilizzare effettivamente valori NULL come parametri , usa invece questo costrutto:
($1 IS NULL AND a.ad_nr IS NULL OR a.ad_nr = $1) -- AND binds before OR
Ciò consente anche indici da utilizzare.
Per il caso in questione, sostituisci tutte le istanze di LEFT JOIN
con JOIN
.
db<>gioca qui - con semplice demo per tutte le varianti.
Sqlfiddle vecchio
A parte
-
Non utilizzare
name
eid
come nomi di colonna. Non sono descrittivi e quando ti unisci a un gruppo di tavoli (come fai pera lot
in un database relazionale), ti ritroverai con diverse colonne tutte denominatename
oid
e devi allegare alias per sistemare il pasticcio. -
Si prega di formattare correttamente il proprio SQL, almeno quando si pongono domande pubbliche. Ma fallo anche in privato, per il tuo bene.