我有Postgres功能:
create function myfunction(integer, text, text, text, text, text, text) RETURNS
table(id int, match text, score int, nr int, nr_extra character varying, info character varying, postcode character varying,
street character varying, place character varying, country character varying, the_geom geometry)
AS $$
BEGIN
return query (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 place , c.name country, a.wkb_geometry as wkb_geometry from "Addresses" a
left join "Streets" s on a.street_id = s.id
left join "Places" p on s.place_id = p.id
left join "Countries" c on p.country_id = c.id
where c.name = $7
and p.name = $6
and s.name = $5
and a.ad_nr = $1
and a.ad_nr_extra = $2
and a.ad_info = $3
and ad_postcode = $4);
END;
$$
LANGUAGE plpgsql;
如果输入的一个或多个变量为NULL,则此函数无法给出正确的结果,因为ad_postcode = NULL
将失败。
如何在查询中测试NULL?
答案 0 :(得分:18)
我不同意其他答案中的一些建议。这可以通过plpgsql完成,我认为在客户端应用程序中组装查询主要是远优于。它更快更干净,应用程序只在请求中发送最小的线路。 SQL语句保存在数据库中,这使得维护更容易 - 除非您想要收集客户端应用程序中的所有业务逻辑,这取决于一般架构。
SELECT
RETURN QUERY
从不使用name
和id
作为列名。它们不是描述性的,当您加入一堆表(就像在关系数据库中必须a lot
一样)时,最终会得到几个名为name
或id
的列。咄。
请至少在提出公开问题时正确格式化您的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) 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$ LANGUAGE plpgsql;
呼叫:
SELECT * FROM func(1, '_ad_nr_extra', '_ad_info', '_ad_postcode', '_sname');
SELECT * FROM func(1, _pname := 'foo');
由于我为所有功能参数提供了默认值,因此您可以使用 位置 表示法, 名为 函数调用中的表示法或 混合 表示法。更多相关答案:
Functions with variable number of input parameters
有关动态SQL基础知识的更多说明,请参阅以下相关答案:
Refactor a PL/pgSQL function to return the output of various SELECT queries
concat()
函数有助于构建字符串。它是在Postgres 9.1中引入的。
ELSE
语句的CASE
分支在不存在时默认为NULL
。简化代码。
EXECUTE
的{{3}}子句使SQL注入无法进行,并允许直接使用参数值,与预处理语句完全相同。
NULL
值用于忽略参数。它们实际上并不用于搜索。
您可以使用纯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) 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$ LANGUAGE sql;
相同的电话。
要有效忽略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
。
USING
,包含所有变体的简化演示。
答案 1 :(得分:3)
您可以使用
c.name IS NOT DISTINCT FROM $7
如果true
和c.name
相等或两者都是$7
,它将返回null
。
或者您可以使用
(c.name = $7 or $7 is null )
如果true
和c.name
相等或$7
为空,则会返回$7
。
答案 2 :(得分:2)
如果您可以修改查询,则可以执行类似
的操作and (ad_postcode = $4 OR $4 IS NULL)
答案 3 :(得分:2)
有几件事......
首先,作为旁注:您的查询的语义可能需要重新访问。您where
条款中的某些内容可能实际属于您的join
条款,例如:
from ...
left join ... on ... and ...
left join ... on ... and ...
如果他们不这样做,你最应该使用的是inner join
,而不是left join
。
其次,有一个is not distinct from
运算符,偶尔可以代替=
。 a is not distinct from b
基本上等同于a = b or a is null and b is null
。
但请注意,is not distinct from
NOT 使用索引,而=
和is null
实际上是这样做的。您可以在特定情况下使用(field = $i or $i is null)
,如果您使用的是最新版本的Postgres,它将产生最佳计划: