使用不同参数测试函数中的null

时间:2013-06-27 21:22:29

标签: postgresql null postgresql-9.1 plpgsql dynamic-sql

我有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?

4 个答案:

答案 0 :(得分:18)

我不同意其他答案中的一些建议。这可以通过plpgsql完成,我认为在客户端应用程序中组装查询主要是远优于。它更快更干净,应用程序只在请求中发送最小的线路。 SQL语句保存在数据库中,这使得维护更容易 - 除非您想要收集客户端应用程序中的所有业务逻辑,这取决于一般架构。

一般建议

  • SELECT RETURN QUERY

  • 周围不需要括号
  • 从不使用nameid作为列名。它们不是描述性的,当您加入一堆表(就像在关系数据库中必须a lot一样)时,最终会得到几个名为nameid的列。咄。

  • 请至少在提出公开问题时正确格式化您的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) 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函数执行此操作并避免使用动态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

如果truec.name相等或两者都是$7,它将返回null

或者您可以使用

(c.name = $7 or $7 is null )

如果truec.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,它将产生最佳计划:

https://gist.github.com/ddebernardy/5884267