9

我有一个 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

4 回答 4

25

我不同意其他答案中的一些建议。这可以通过 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分支默认为不存在时。简化代码。CASENULL

USINGfor 子句使 SQL 注入成为不可能,EXECUTE因为值作为值传递允许直接使用参数值,就像在准备好的语句中一样。

NULLvalues 用于忽略此处的参数。它们实际上并不用于搜索。

你不需要在 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

旁白

  • 不要使用nameandid作为列名。它们不是描述性的,当你加入一堆表时(就像你a lot在关系数据库中做的那样),你最终会得到几个名为nameor的列id,并且必须附加别名来对混乱进行排序。

  • 请至少在提出公共问题时正确格式化您的 SQL。但也可以私下做,为了你自己好。

于 2013-06-28T01:12:10.017 回答
4

如果您可以修改查询,您可以执行类似的操作

and (ad_postcode = $4 OR $4 IS NULL)
于 2013-06-27T21:37:35.990 回答
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为空,它将返回。

于 2013-06-27T21:36:53.803 回答
2

Several things...

First, as side note: the semantics of your query might need a revisit. Some of the stuff in your where clauses might actually belong in your join clauses, like:

from ...
left join ... on ... and ...
left join ... on ... and ...

When they don't, you most should probably be using an inner join, rather than a left join.

Second, there is a is not distinct from operator, which can occasionally be handy in place of =. a is not distinct from b is basically equivalent to a = b or a is null and b is null.

Note, however, that is not distinct from does NOT use an index, whereas = and is null actually do. You could use (field = $i or $i is null) instead in your particular case, and it will yield the optimal plan if you're using the latest version of Postgres:

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

于 2013-06-27T21:43:20.280 回答