Ich bin mit einigen Ratschlägen in anderen Antworten nicht einverstanden. Dies kann mit PL/pgSQL erfolgen und ich denke, es ist meistens weit überlegen zum Zusammenstellen von Abfragen in einer Client-Anwendung. Es ist schneller und sauberer und die App sendet nur das Nötigste an Anfragen über das Kabel. SQL-Anweisungen werden in der Datenbank gespeichert, was die Wartung erleichtert - es sei denn, Sie möchten die gesamte Geschäftslogik in der Client-Anwendung sammeln, dies hängt von der allgemeinen Architektur ab.
PL/pgSQL-Funktion mit dynamischem 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$;
Aufruf:
SELECT * FROM func(1, '_ad_nr_extra', '_ad_info', '_ad_postcode', '_sname');
SELECT * FROM func(1, _pname := 'foo');
Da alle Funktionsparameter Standardwerte haben, können Sie positional verwenden Notation, benannt Notation oder gemischt Notation Ihrer Wahl im Funktionsaufruf. Siehe:
- Funktionen mit variabler Anzahl von Eingabeparametern
Weitere Erläuterungen zu den Grundlagen von dynamischem SQL:
- Refaktorisieren Sie eine PL/pgSQL-Funktion, um die Ausgabe verschiedener SELECT-Abfragen zurückzugeben
Der concat()
Die Funktion ist maßgeblich für den Aufbau der Zeichenfolge. Es wurde mit Postgres 9.1 eingeführt.
Das ELSE
Zweig eines CASE
-Anweisung ist standardmäßig NULL
wenn nicht vorhanden. Vereinfacht den Code.
Der USING
Klausel für EXECUTE
macht SQL-Injection unmöglich, da Werte als Werte übergeben werden und ermöglicht die direkte Verwendung von Parameterwerten, genau wie in vorbereiteten Anweisungen.
NULL
Werte werden verwendet, um Parameter hier zu ignorieren. Sie werden nicht wirklich für die Suche verwendet.
Sie brauchen keine Klammern um SELECT
mit RETURN QUERY
.
Einfache SQL-Funktion
Sie könnten tun Sie es mit einer einfachen SQL-Funktion und vermeiden Sie dynamisches SQL. In einigen Fällen kann dies schneller sein. Aber das würde ich in diesem Fall nicht erwarten . Das Planen der Abfrage ohne unnötige Verknüpfungen und Prädikate führt normalerweise zu den besten Ergebnissen. Die Planungskosten für eine einfache Abfrage wie diese sind nahezu vernachlässigbar.
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$;
Identischer Aufruf.
Um Parameter mit NULL
effektiv zu ignorieren Werte :
($1 IS NULL OR a.ad_nr = $1)
Um tatsächlich NULL-Werte als Parameter zu verwenden verwenden Sie stattdessen dieses Konstrukt:
($1 IS NULL AND a.ad_nr IS NULL OR a.ad_nr = $1) -- AND binds before OR
Dies ermöglicht auch Indizes verwendet werden.
Ersetzen Sie im vorliegenden Fall alle Instanzen von LEFT JOIN
mit JOIN
.
db<>hier fummeln - mit einfacher Demo für alle Varianten.
Altes sqlfiddle
Nebenbei
-
Verwenden Sie nicht
name
undid
als Spaltennamen. Sie sind nicht beschreibend und wenn Sie sich einer Reihe von Tischen anschließen (wie Sie es beia lot
tun in einer relationalen Datenbank), erhalten Sie am Ende mehrere Spalten mit dem Namenname
oderid
, und müssen Aliase anhängen, um das Durcheinander zu sortieren. -
Bitte formatieren Sie Ihr SQL richtig, zumindest wenn Sie öffentliche Fragen stellen. Aber tun Sie es auch privat, zu Ihrem eigenen Besten.