计算每行 NULL 值

Edw*_*own 5 postgresql null isnull postgresql-9.6

我想在不枚举列名的情况下计算表中每行存在的空值数。例如:

WITH t as (VALUES
  (NULL ,'hi',2,NULL,'null'),
  ('' ,'hi',2,3,'test'),
  (NULL ,'hi',2,3,'null')
)
SELECT countnulls(t)
FROM t;
Run Code Online (Sandbox Code Playgroud)

会导致:

numnulls
2
0
1
Run Code Online (Sandbox Code Playgroud)

我能得到的最接近的是以下黑客row_to_json()

select 
(CHAR_LENGTH(row_to_json(t)::text)
 - CHAR_LENGTH(REPLACE(row_to_json(t)::text, 'null', '')))/4 from t;
Run Code Online (Sandbox Code Playgroud)

这是......相当的黑客(不是很好)。它可以工作,有点,但是当它出现在实际数据或列名中时,它会将字符串 'null' 计为 NULL。所以在上述情况下是不正确的。

Erw*_*ter 10

1. 你知道列名...

对于 Postgres 9.6或更高版本,请使用num_nulls()

WITH t(a, b, c, d, e) AS (
   VALUES
     (NULL ,'hi',2,NULL,'null')
   , ('' ,'hi',2,3,'test')
   , (NULL ,'hi',2,3,'null')
   )
SELECT num_nulls(a,b,c,d,e)
FROM   t;
Run Code Online (Sandbox Code Playgroud)

准确返回您想要的结果,适用于任何数据类型的组合。

手册:

num_nulls(VARIADIC "any") ...返回空参数的数量

对于 Postgres 9.5或更早版本,转换为text[]array_remove(arr, null)并使用剩余的数组长度进行精确计数:

SELECT 5 - cardinality(array_remove(ARRAY[a::text,b::text,c::text,d::text,e::text], null))
FROM   t;
Run Code Online (Sandbox Code Playgroud)

任何类型都可以转换为text. text当然,对于列来说,演员表是多余的。

array_remove()需要 Postgres 9.3 或更高版本。
cardinality()需要 Postgres 9.4 或更高版本。替换为array_length(arr, 1)旧版本。

2. 你不知道列名,但 Postgres 知道

在实际表(或其他注册对象,如视图或物化视图)上构建时,我们可以从系统目录中检索列名,pg_attribute以使用动态 SQL 完全自动化。喜欢:

CREATE OR REPLACE FUNCTION f_num_nulls(_tbl regclass)
  RETURNS SETOF int AS
$func$
BEGIN
   RETURN QUERY EXECUTE format(
      'SELECT num_nulls(%s) FROM %s'
    , (SELECT string_agg(quote_ident(attname), ', ')  -- column list
       FROM   pg_attribute
       WHERE  attrelid = _tbl
       AND    NOT attisdropped    -- no dropped (dead) columns
       AND    attnum > 0)         -- no system columns
    , _tbl
   );
END
$func$  LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)

称呼:

SELECT * FROM f_num_nulls('myschema.tbl');
Run Code Online (Sandbox Code Playgroud)

返回当前物理顺序中每一行的计数。没有别的,绝对是通用的。

有关的:

我们可以也通过每个行返回一个计数使用多态函数它。有关的:

3. 你什么都不知道:匿名记录

在不太可能的情况下,即使 Postgres 也不知道列名(例如来自VALUES您示例中的表达式),请转换为文档类型(json, jsonb, xml, hstore)以获取句柄,如 ypercube (现在已删除评论)和Evan 所示

但是匿名记录没有主键或每个定义的任何其他唯一属性。每个LATERAL子查询中计数以防止错误聚合。演示jsonb

SELECT *
FROM  (
   VALUES
      (NULL ,'hi',2,NULL,'null')
    , (NULL ,'hi',2,NULL,'null')       -- duplicate row !!!
    , ('' ,'hi',2,3,'test')
    , (NULL ,'hi',2,3,'null')
   ) t                                 -- column names unknown
, LATERAL (
   SELECT count(*) FILTER (WHERE j.value = jsonb 'null') AS num_nulls
   FROM   jsonb_each(to_jsonb(t)) j
   ) c;
Run Code Online (Sandbox Code Playgroud)

或者使用json: 可能会快一点,因为转换更便宜。
演示 3 种不同的方式:

SELECT *
FROM  (
   VALUES
      (NULL ,'hi',2,NULL,'null')
    , (NULL ,'hi',2,NULL,'null')
    , ('' ,'hi',2,3,'test')
    , (NULL ,'hi',2,3,'null')
   ) t   -- column names unknown
, to_json(t) j
, LATERAL (
   SELECT count(*) FILTER (WHERE j1.value::text = 'null') AS num_nulls1
   FROM   json_each(to_json(t)) j1
   ) c1
, LATERAL (
   SELECT count(*) FILTER (WHERE j->>k IS NULL) AS num_nulls2
   FROM   json_object_keys(j) k
   ) c2
, LATERAL (
   SELECT count(*) - count(j->>k ) AS num_nulls3
   FROM   json_object_keys(j) k
   ) c3;
Run Code Online (Sandbox Code Playgroud)

db<>在这里摆弄