mil*_*den 7 postgresql null dynamic-sql plpgsql postgresql-9.1
我有一个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;
Run Code Online (Sandbox Code Playgroud)
当输入的一个或多个变量为NULL时,此函数无法给出正确的结果,因为ad_postcode = NULL
它将失败.
我该怎么做才能在查询中测试NULL?
Erw*_*ter 19
我不同意其他答案中的一些建议.这可以通过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;
Run Code Online (Sandbox Code Playgroud)
呼叫:
SELECT * FROM func(1, '_ad_nr_extra', '_ad_info', '_ad_postcode', '_sname');
SELECT * FROM func(1, _pname := 'foo');
Run Code Online (Sandbox Code Playgroud)
由于我为所有函数参数提供了默认值,因此您可以在函数调用中使用位置表示法,命名表示法或混合表示法.更多相关答案:
具有可变数量输入参数的函数
有关动态SQL基础知识的更多说明,请参考以下相关答案:
重构PL/pgSQL函数以返回各种SELECT查询的输出
该concat()
函数有助于构建字符串.它与Postgres 9.1一起推出.
语句的ELSE
分支CASE
默认为NULL
不存在时.简化代码.
这个USING
子句EXECUTE
使得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;
Run Code Online (Sandbox Code Playgroud)
相同的电话.
要有效地忽略带有NULL
值的参数:
($1 IS NULL OR a.ad_nr = $1)
Run Code Online (Sandbox Code Playgroud)
如果您确实想使用NULL值作为参数,请改用此构造:
($1 IS NULL AND a.ad_nr IS NULL OR a.ad_nr = $1) -- AND binds before OR
Run Code Online (Sandbox Code Playgroud)
这也允许使用索引.
同时更换的所有实例LEFT JOIN
有JOIN
.
SQL Fiddle与所有变体的简化演示.