我不同意其他答案中的一些建议。这可以通过 PL/pgSQL 来完成,我认为它远远优于在客户端应用程序中组装查询。它更快、更干净,并且该应用程序仅在请求中通过网络发送最低限度的请求。SQL 语句保存在数据库中,这使得维护更容易——除非你想收集客户端应用程序中的所有业务逻辑,这取决于一般架构。
带有动态 SQL 的 PL/pgSQL 函数
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$;
称呼:
SELECT * FROM func(1, '_ad_nr_extra', '_ad_info', '_ad_postcode', '_sname');
SELECT * FROM func(1, _pname := 'foo');
由于所有函数参数都有默认值,因此您可以在函数调用中选择使用位置表示法、命名表示法或混合表示法。看:
动态 SQL 基础知识的更多解释:
该concat()
函数有助于构建字符串。它是在 Postgres 9.1 中引入的。
语句的ELSE
分支默认为不存在时。简化代码。CASE
NULL
USING
for 子句使 SQL 注入成为不可能,EXECUTE
因为值作为值传递并允许直接使用参数值,就像在准备好的语句中一样。
NULL
values 用于忽略此处的参数。它们实际上并不用于搜索。
你不需要在 with 周围SELECT
加上括号RETURN QUERY
。
简单的 SQL 函数
您可以使用普通的 SQL 函数来完成它并避免使用动态 SQL。在某些情况下,这可能会更快。但在这种情况下我不会期望它。在没有不必要的连接和谓词的情况下规划查询通常会产生最佳结果。像这样的简单查询的计划成本几乎可以忽略不计。
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$;
一模一样的叫法。
要有效地忽略带NULL
值的参数:
($1 IS NULL OR a.ad_nr = $1)
要实际使用NULL 值作为参数,请改用以下构造:
($1 IS NULL AND a.ad_nr IS NULL OR a.ad_nr = $1) -- AND binds before OR
这也允许使用索引。
对于手头的情况,用 替换所有LEFT JOIN
实例JOIN
。
db<>fiddle here -为所有变体提供简单的演示。
老sqlfiddle
旁白