【问题标题】:Check if NULL exists in Postgres array检查 Postgres 数组中是否存在 NULL
【发布时间】:2016-04-23 05:27:34
【问题描述】:

类似于this question,如何判断数组中是否存在NULL值?

这里有一些尝试。

SELECT num, ar, expected,
  ar @> ARRAY[NULL]::int[] AS test1,
  NULL = ANY (ar) AS test2,
  array_to_string(ar, ', ') <> array_to_string(ar, ', ', '(null)') AS test3
FROM (
  SELECT 1 AS num, '{1,2,NULL}'::int[] AS ar, true AS expected
  UNION SELECT 2, '{1,2,3}'::int[], false
) td ORDER BY num;

 num |     ar     | expected | test1 | test2 | test3
-----+------------+----------+-------+-------+-------
   1 | {1,2,NULL} | t        | f     |       | t
   2 | {1,2,3}    | f        | f     |       | f
(2 rows)

只有array_to_string 的技巧会显示预期值。有没有更好的测试方法?

【问题讨论】:

标签: sql arrays postgresql null postgresql-9.1


【解决方案1】:

Postgres 9.5 或更高版本

或使用array_position()。基本上:

SELECT array_position(arr, NULL) IS NOT NULL AS array_has_null

请看下面的演示。

Postgres 9.3 或更高版本

您可以使用内置函数array_remove()array_replace() 进行测试。

Postgres 9.1 或任何版本

如果您知道一个永远不会存在于您的数组中的元素,您可以使用这个 fast 表达式。比如说,你有一个正数数组,-1 永远不可能在里面:

-1 = ANY(arr) IS NULL

相关答案及详细解释:

如果您不能绝对确定,您可以使用unnest() 回退到一种昂贵但安全的方法。喜欢:

(SELECT bool_or(x IS NULL) FROM unnest(arr) x)

或:

EXISTS (SELECT 1 FROM unnest(arr) x WHERE x IS NULL)

但是您可以使用CASE 表达式快速安全。使用一个不太可能的数字,如果它应该存在,则回退到安全方法。您可能需要单独处理案例arr IS NULL。请参阅下面的演示。

演示

SELECT num, arr, expect
     , -1 = ANY(arr) IS NULL                                    AS t_1   --  50 ms
     , (SELECT bool_or(x IS NULL) FROM unnest(arr) x)           AS t_2   -- 754 ms
     , EXISTS (SELECT 1 FROM unnest(arr) x WHERE x IS NULL)     AS t_3   -- 521 ms
     , CASE -1 = ANY(arr)
         WHEN FALSE THEN FALSE
         WHEN TRUE THEN EXISTS (SELECT 1 FROM unnest(arr) x WHERE x IS NULL)
         ELSE NULLIF(arr IS NOT NULL, FALSE)  -- catch arr IS NULL       --  55 ms
      -- ELSE TRUE  -- simpler for columns defined NOT NULL              --  51 ms
       END                                                      AS t_91
     , array_replace(arr, NULL, 0) <> arr                       AS t_93a --  99 ms
     , array_remove(arr, NULL) <> arr                           AS t_93b --  96 ms
     , cardinality(array_remove(arr, NULL)) <> cardinality(arr) AS t_94  --  81 ms
     , COALESCE(array_position(arr, NULL::int), 0) > 0          AS t_95a --  49 ms
     , array_position(arr, NULL) IS NOT NULL                    AS t_95b --  45 ms
     , CASE WHEN arr IS NOT NULL
            THEN array_position(arr, NULL) IS NOT NULL END      AS t_95c --  48 ms
FROM  (
   VALUES (1, '{1,2,NULL}'::int[], true)     -- extended test case
        , (2, '{-1,NULL,2}'      , true)
        , (3, '{NULL}'           , true)
        , (4, '{1,2,3}'          , false)
        , (5, '{-1,2,3}'         , false)
        , (6, NULL               , null)
   ) t(num, arr, expect);

结果:

 数字 | arr |期待 | t_1 | t_2 | t_3 | t_91 | t_93a | t_93b | t_94 | t_95a | t_95b | t_95c
-----+-------------+--------+--------+------+----- +-----+-------+-------+------+-------+-------+--- ----
   1 | {1,2,NULL} |吨 |吨 |吨 |吨 |吨 |吨 |吨 |吨 |吨 |吨 |吨
   2 | {-1,NULL,2} |吨 | f --!! |吨 |吨 |吨 |吨 |吨 |吨 |吨 |吨 |吨
   3 | {NULL} |吨 |吨 |吨 |吨 |吨 |吨 |吨 |吨 |吨 |吨 |吨
   4 | {1,2,3} | f | f | f | f | f | f | f | f | f | f | F
   5 | {-1,2,3} | f | f | f | f | f | f | f | f | f | f | F
   6 |空 |空 | t --!! |空 | f |空 |空 |空 |空 | f | f |空

请注意,多维数组不允许使用array_remove()array_position()t_93a 右侧的所有表达式仅适用于一维数组。

dbfiddle here - Postgres 13,有更多测试
sqlfiddle

基准设置

增加的时间来自 Postgres 9.5 中 200k 行的基准测试。这是我的设置:

CREATE TABLE t AS
SELECT row_number() OVER() AS num
     , array_agg(elem) AS arr
     , bool_or(elem IS NULL) AS expected
FROM  (
   SELECT CASE WHEN random() > .95 THEN NULL ELSE g END AS elem  -- 5% NULL VALUES
        , count(*) FILTER (WHERE random() > .8)
                   OVER (ORDER BY g) AS grp  -- avg 5 element per array
   FROM   generate_series (1, 1000000) g  -- increase for big test case
   ) sub
GROUP  BY grp;

函数封装

对于重复使用,我会在 Postgres 9.5 中创建一个函数,如下所示:

CREATE OR REPLACE FUNCTION f_array_has_null (anyarray)
  RETURNS bool
  LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
 'SELECT array_position($1, NULL) IS NOT NULL';

PARALLEL SAFE 仅适用于 Postgres 9.6 或更高版本。

使用polymorphic 输入类型,这适用于任何 数组类型,而不仅仅是int[]

设为IMMUTABLE 以允许性能优化和索引表达式。

但不要设为STRICT,这会禁用“函数内联”并影响性能,因为array_position() 本身不是STRICT。见:

如需抓案arr IS NULL

CREATE OR REPLACE FUNCTION f_array_has_null (anyarray)
  RETURNS bool
  LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
 'SELECT CASE WHEN $1 IS NOT NULL
              THEN array_position($1, NULL) IS NOT NULL END';

对于 Postgres 9.1,使用上面的 t_91 表达式。其余部分不变。

密切相关:

【讨论】:

    【解决方案2】:

    PostgreSQL 9.5(我知道你指定了 9.1,但无论如何)有 array_position() 函数来做你想做的事,而不必使用效率极低的 unnest() 来做这样微不足道的事情(参见 test4):

    patrick@puny:~$ psql -d test
    psql (9.5.0)
    Type "help" for help.
    
    test=# SELECT num, ar, expected,
      ar @> ARRAY[NULL]::int[] AS test1,
      NULL = ANY (ar) AS test2,
      array_to_string(ar, ', ') <> array_to_string(ar, ', ', '(null)') AS test3,
      coalesce(array_position(ar, NULL::int), 0) > 0 AS test4
    FROM (
      SELECT 1 AS num, '{1,2,NULL}'::int[] AS ar, true AS expected
      UNION SELECT 2, '{1,2,3}'::int[], false
    ) td ORDER BY num;
     num |     ar     | expected | test1 | test2 | test3 | test4
    -----+------------+----------+-------+-------+-------+-------
       1 | {1,2,NULL} | t        | f     |       | t     | t
       2 | {1,2,3}    | f        | f     |       | f     | f
    (2 rows)
    

    【讨论】:

      【解决方案3】:

      PostgreSQL 的UNNEST() 函数是更好的选择。您可以编写如下所示的简单函数来检查数组中的 NULL 值。

      create or replace function NULL_EXISTS(val anyelement) returns boolean as
      $$
      select exists (
          select 1 from unnest(val) arr(el) where el is null
      );
      $$
      language sql 
      

      例如,

      SELECT NULL_EXISTS(array [1,2,NULL])
            ,NULL_EXISTS(array [1,2,3]);
      

      结果:

      null_exists null_exists 
      ----------- -------------- 
      t           f     
      

      因此,您可以在查询中使用NULL_EXISTS() 函数,如下所示。

      SELECT num, ar, expected,NULL_EXISTS(ar)
      FROM (
        SELECT 1 AS num, '{1,2,NULL}'::int[] AS ar, true AS expected
        UNION SELECT 2, '{1,2,3}'::int[], false
      ) td ORDER BY num;
      

      【讨论】:

        【解决方案4】:

        我用这个

        select 
            array_position(array[1,null], null) is not null
        

        array_position - 返回数组中第二个参数第一次出现的下标,从第三个参数指示的元素或第一个元素开始(数组必须是一维的)

        【讨论】:

        • 多一点解释会很有帮助。
        • 这似乎没有添加任何新内容。
        猜你喜欢
        • 2012-06-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-09-29
        • 2012-08-08
        • 1970-01-01
        • 2020-10-10
        • 2011-06-20
        相关资源
        最近更新 更多