【问题标题】:Count the number of attributes that are NULL for a row计算一行为 NULL 的属性数
【发布时间】:2015-10-05 07:57:02
【问题描述】:

我想在表中添加一个新列来记录每个元组(行)值为空的属性的数量。如何使用 SQL 获取号码?

例如,如果一个元组是这样的:

Name | Age | Sex
-----+-----+-----
Blice| 100 | null

我想这样更新元组:

Name | Age | Sex | nNULL
-----+-----+-----+--------
Blice| 100 | null|  1

另外,因为我正在编写一个 PL/pgSQL 函数并且表名是从参数中获取的,所以我事先不知道表的架构。这意味着我需要使用输入表名更新表。有人知道怎么做吗?

【问题讨论】:

    标签: sql postgresql count null plpgsql


    【解决方案1】:

    可能不拼出列。将列反转为行和计数。

    聚合函数count(<expression>) 只计算非空值,而count(*) 计算所有行。计算多个列的 NULL 值的最短和最快方法是 count(*) - count(col) ...

    适用于 any 表,其中 any 列的列数为 any 数据类型。

    在带有内置 JSON functions 的 Postgres 9.3+ 中:

    SELECT *, (SELECT count(*) - count(v)
               FROM json_each_text(row_to_json(t)) x(k,v)) AS ct_nulls
    FROM   tbl t;
    

    x(k,v) 是什么?

    json_each_text() 返回一组包含两列的行。默认列名称为keyvalue,如manual where I linked 所示。我提供了表和列别名,因此我们不必依赖默认名称。第二列名为v

    或者,在至少 8.3 之后的任何 Postgres 版本中,安装了附加模块 hstore,甚至更短、更快:

    SELECT *,  (SELECT count(*) - count(v) FROM svals(hstore(t)) v) AS ct_nulls
    FROM   tbl t;
    

    这个更简单的版本只返回一组单个值。我只提供了一个简单的别名v,自动取为表列别名。

    由于附加列是功能相关的,我会考虑将其保留在表中。而是像上面演示的那样即时计算它,或者为此目的创建一个具有polymorphic 输入类型的小函数:

    CREATE OR REPLACE FUNCTION f_ct_nulls(_row anyelement)
      RETURNS int  LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
    'SELECT (count(*) - count(v))::int FROM svals(hstore(_row)) v';
    

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

    然后:

    SELECT *, f_ct_nulls(t) AS ct_nulls
    FROM   tbl t;
    

    你可以把它包装成VIEW ...

    dbfiddle here - 演示所有
    sqlfiddle

    这也应该回答你的第二个问题:

    ...表名是从参数中获得的,我事先不知道表的架构。这意味着我需要使用输入表名更新表。

    【讨论】:

    • 谢谢!我喜欢这个解决方案!
    • 对了,请问x(k,v)是什么?
    • @KingstonChan:我在答案中添加了解释。
    【解决方案2】:

    在 Postgres 中,你可以这样表达:

    select t.*,
           ((name is null)::int +
            (age is null)::int +
            (sex is null)::int
           ) as numnulls
    from table t;
    

    为了在未知表上实现这一点,您需要使用动态 SQL 并获取列列表(例如来自information_schema.columns))。

    【讨论】:

    • 在这种情况下,我们可以让它在没有动态 SQL 或访问目录表的情况下工作。
    【解决方案3】:

    自动添加列功能

    这是 @winged panther posted 的审核版本,每个请求。

    该函数将具有给定名称的列添加到调用角色具有必要权限的任何现有表:

    CREATE OR REPLACE FUNCTION f_add_null_count(_tbl regclass, _newcol text)
      RETURNS void AS
    $func$
    BEGIN
       -- add new col
       EXECUTE format('ALTER TABLE %s ADD COLUMN %I smallint', _tbl, _newcol);
    
       -- update new col with dynamic count of nulls
       EXECUTE (
          SELECT format('UPDATE %s SET %I = (', _tbl, _newcol)  -- regclass used as text
              || string_agg(quote_ident(attname), ' IS NULL)::int + (')
              || ' IS NULL)::int'
          FROM   pg_catalog.pg_attribute
          WHERE  attnum > 0
          AND    NOT attisdropped
          AND    attrelid = _tbl  -- regclass used as OID
          AND    attname <> _newcol  -- no escaping here, it's the *text*!
          );
    END
    $func$  LANGUAGE plpgsql;

    SQL Fiddle demo.

    如何正确对待标识符

    • 使用regclassformat()%Iquote_ident() 清理标识符。 我在示例中使用了所有三种技术,每种技术都恰好是使用它们的最佳选择。更多在这里:

    我用粗体格式化了相关的代码片段。

    其他要点

    • 我的查询基于pg_catalog.pg_attribute,但这是一个可选决定,有利有弊。使我的查询更简单、更快,因为我可以使用表的 OID。相关:

    • 您必须从计数中排除新添加的列,否则计数会减一。

    • 使用数据类型 smallint 进行计数,因为一个表中的列不能超过 1600 列。

    • 我没有使用变量而是直接执行SELECT语句的结果。 plpgsql 中的赋值相对昂贵。不过也没什么大不了的。也是品味和风格的问题。

    • 我习惯于在参数和变量前加上下划线 (_tbl),以排除变量和列名之间的歧义。

    【讨论】:

      【解决方案4】:

      我刚刚创建了一个函数来执行 OP 的要求,使用 Gordon Linoff's answer 和下表和数据:

      det

      CREATE TABLE det (
        name text,
        age integer,
        sex text
      );
      

      数据:

      insert into det (name,age,sex) values
        ('Blice',100,NULL),
        ('Glizz',NULL,NULL),
        (NULL,NULL,NULL);
      

      功能:

      create or replace function fn_alter_nulls(tbl text,new_col text)  returns void as 
      $$
      declare vals text;
      begin
         -- dynamically getting list of columns *
      select string_agg(format('(%s is null)::int',column_name),'+') into vals
      from information_schema.columns 
      where table_schema='public' and table_name=''||tbl||''  and table_catalog='yourDB_Name';
      -- adds new column
      execute format('alter table %s add column "%s" int',tbl,new_col);
      --updates new column
      execute format('update det set %s =(%s)',new_col,vals);
      end;
      $$
      language plpgsql
      

      函数调用:

      select fn_alter_nulls('det','nnulls')
      

      【讨论】:

      • 不错的自动化。一个缺陷:用户输入永远不应该被连接到动态 SQL 中而不逃避可能的无效语法或 SQL 注入。同样,所有现有标识符都应视为用户输入。
      • @ErwinBrandstetter 谢谢,你能详细说明一下吗?
      • 这不适合评论。我加了another answer with the audited function.
      【解决方案5】:

      既然空计数是派生数据并且在查询时确定简单/便宜,为什么不创建一个视图:

      create view MyTableWithNullCount as
      select
        *, 
        case when nullableColumn1 is null then 1 else 0 end +
        case when nullableColumn2 is null then 1 else 0 end +
        ...
        case when nullableColumnn is null then 1 else 0 end as nNull
      from myTable
      

      而只是使用视图。

      这样做的好处是不必编写触发器/代码来维护物理空计数列,这将比这种方法更令人头疼。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-05-17
        • 2020-03-21
        • 1970-01-01
        • 2018-07-29
        • 1970-01-01
        • 2016-07-21
        • 2012-11-26
        相关资源
        最近更新 更多