计算一行中为 NULL 的属性数
Count the number of attributes that are NULL for a row
我想在table中添加一个新列来记录每个元组(行)的值为空的属性的数量。如何使用 SQL 获取号码?
例如,如果元组是这样的:
Name | Age | Sex
-----+-----+-----
Blice| 100 | null
我想像这样更新元组:
Name | Age | Sex | nNULL
-----+-----+-----+--------
Blice| 100 | null| 1
此外,因为我正在编写一个 PL/pgSQL 函数并且 table 名称是从参数中获得的,所以我事先不知道 table 的架构。这意味着我需要用输入的 table 名称更新 table。有人知道怎么做吗?
既然空计数是派生数据,simple/cheap在查询时确定,为什么不创建视图:
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
只需使用视图即可。
这样做的好处是不必编写 triggers/code 来维护物理空计数列,这将比这种方法更令人头疼。
在 Postgres 中,您可以将其表示为:
select t.*,
((name is null)::int +
(age is null)::int +
(sex is null)::int
) as numnulls
from table t;
为了在未知 table 上实施此操作,您将需要使用动态 SQL 并获取列列表(比如来自 information_schema.columns)
)。
可能无需拼写列。将列转为行并计数。
聚合函数 count(<expression>)
only counts non-null values, while count(*)
计算 所有 行。计算多个列的 NULL 值的最短和最快的方法是 count(*) - count(col)
...
适用于 any table,any 列数 any
数据类型。
在 Postgres 9.3+ 中内置 JSON functions:
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()
returns 一组包含两列的行。默认列名是 key
和 value
,如 manual where I linked 中所示。我提供了 table 和列别名,因此我们不必依赖默认名称。第二列名为 v
.
或者,在至少 8.3 之后的任何 Postgres 版本中安装了附加模块 hstore
,甚至更短更快:
SELECT *, (SELECT count(*) - count(v) FROM svals(hstore(t)) v) AS ct_nulls
FROM tbl t;
这个更简单的版本只有 returns 一组单一值。我只提供了一个简单的别名v
,它被自动认为是table和列别名
- Best way to install hstore on multiple schemas in a Postgres database?
由于附加列 功能相关 我会考虑 而不是 将其保留在 table 中。而是像上面演示的那样动态计算它,或者创建一个带有 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
...
db<>fiddle here - 展示全部
旧sqlfiddle
这也应该回答你的第二个问题:
... the table name is obtained from argument, I don't know the schema of a table beforehand. That means I need to update the table with the input table name.
我刚刚创建了一个函数来执行 OP 的要求,方法是使用 以及以下 table 和数据:
Table 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')
自动添加列的功能
这是根据请求 的审核版本。
该函数将具有给定名称的列添加到任何 existing table 调用角色具有以下权限的必要权限:
CREATE OR REPLACE FUNCTION f_add_null_count(<b>_tbl regclass</b>, _newcol text)
RETURNS void AS
$func$
BEGIN
-- add new col
EXECUTE format('ALTER TABLE <b>%s</b> ADD COLUMN <b>%I</b> smallint', _tbl, _newcol);
-- update new col with dynamic count of nulls
EXECUTE (
SELECT format('UPDATE <b>%s</b> SET <b>%I</b> = (', _tbl, _newcol) -- regclass used as text
|| string_agg(<b>quote_ident(attname)</b>, ' IS NULL)::int + (')
|| ' IS NULL)::int'
FROM pg_catalog.pg_attribute
WHERE attnum > 0
AND NOT attisdropped
AND <b>attrelid = _tbl</b> -- regclass used as OID
AND <b>attname <> _newcol</b> -- no escaping here, it's the *text*!
);
END
$func$ LANGUAGE plpgsql;
如何正确对待标识符
- 通过强制转换为
regclass
、format()
和 %I
或 quote_ident()
来清理标识符。
我在示例中使用了所有三种技术,每种技术恰好都是使用它们的最佳选择。更多信息:
- Table name as a PostgreSQL function parameter
我把相关代码片段用粗体格式化了
其他点
我的查询基于 pg_catalog.pg_attribute
,但这是一个有利有弊的 可选 决定。使我的查询更简单和更快,因为我可以使用 table 的 OID。相关:
- How to check if a table exists in a given schema
- Select columns with particular column names in PostgreSQL
您必须从计数中排除新添加的列,否则计数将减一。
使用数据类型 smallint
进行计数,因为 table.[=24 中的列数不能超过 1600 =]
我没有使用变量而是直接执行SELECT
语句的结果。 plpgsql 中的分配相对昂贵。不过没什么大不了的。也是品味和风格的问题。
我养成了在参数和变量前加上下划线 (_tbl
) 的习惯,以排除变量和列名之间的歧义。
我想在table中添加一个新列来记录每个元组(行)的值为空的属性的数量。如何使用 SQL 获取号码?
例如,如果元组是这样的:
Name | Age | Sex
-----+-----+-----
Blice| 100 | null
我想像这样更新元组:
Name | Age | Sex | nNULL
-----+-----+-----+--------
Blice| 100 | null| 1
此外,因为我正在编写一个 PL/pgSQL 函数并且 table 名称是从参数中获得的,所以我事先不知道 table 的架构。这意味着我需要用输入的 table 名称更新 table。有人知道怎么做吗?
既然空计数是派生数据,simple/cheap在查询时确定,为什么不创建视图:
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
只需使用视图即可。
这样做的好处是不必编写 triggers/code 来维护物理空计数列,这将比这种方法更令人头疼。
在 Postgres 中,您可以将其表示为:
select t.*,
((name is null)::int +
(age is null)::int +
(sex is null)::int
) as numnulls
from table t;
为了在未知 table 上实施此操作,您将需要使用动态 SQL 并获取列列表(比如来自 information_schema.columns)
)。
可能无需拼写列。将列转为行并计数。
聚合函数 count(<expression>)
only counts non-null values, while count(*)
计算 所有 行。计算多个列的 NULL 值的最短和最快的方法是 count(*) - count(col)
...
适用于 any table,any 列数 any
数据类型。
在 Postgres 9.3+ 中内置 JSON functions:
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()
returns 一组包含两列的行。默认列名是 key
和 value
,如 manual where I linked 中所示。我提供了 table 和列别名,因此我们不必依赖默认名称。第二列名为 v
.
或者,在至少 8.3 之后的任何 Postgres 版本中安装了附加模块 hstore
,甚至更短更快:
SELECT *, (SELECT count(*) - count(v) FROM svals(hstore(t)) v) AS ct_nulls
FROM tbl t;
这个更简单的版本只有 returns 一组单一值。我只提供了一个简单的别名v
,它被自动认为是table和列别名
- Best way to install hstore on multiple schemas in a Postgres database?
由于附加列 功能相关 我会考虑 而不是 将其保留在 table 中。而是像上面演示的那样动态计算它,或者创建一个带有 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
...
db<>fiddle here - 展示全部
旧sqlfiddle
这也应该回答你的第二个问题:
... the table name is obtained from argument, I don't know the schema of a table beforehand. That means I need to update the table with the input table name.
我刚刚创建了一个函数来执行 OP 的要求,方法是使用
Table 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')
自动添加列的功能
这是根据请求
该函数将具有给定名称的列添加到任何 existing table 调用角色具有以下权限的必要权限:
CREATE OR REPLACE FUNCTION f_add_null_count(<b>_tbl regclass</b>, _newcol text)
RETURNS void AS
$func$
BEGIN
-- add new col
EXECUTE format('ALTER TABLE <b>%s</b> ADD COLUMN <b>%I</b> smallint', _tbl, _newcol);
-- update new col with dynamic count of nulls
EXECUTE (
SELECT format('UPDATE <b>%s</b> SET <b>%I</b> = (', _tbl, _newcol) -- regclass used as text
|| string_agg(<b>quote_ident(attname)</b>, ' IS NULL)::int + (')
|| ' IS NULL)::int'
FROM pg_catalog.pg_attribute
WHERE attnum > 0
AND NOT attisdropped
AND <b>attrelid = _tbl</b> -- regclass used as OID
AND <b>attname <> _newcol</b> -- no escaping here, it's the *text*!
);
END
$func$ LANGUAGE plpgsql;
如何正确对待标识符
- 通过强制转换为
regclass
、format()
和%I
或quote_ident()
来清理标识符。 我在示例中使用了所有三种技术,每种技术恰好都是使用它们的最佳选择。更多信息:- Table name as a PostgreSQL function parameter
我把相关代码片段用粗体格式化了
其他点
我的查询基于
pg_catalog.pg_attribute
,但这是一个有利有弊的 可选 决定。使我的查询更简单和更快,因为我可以使用 table 的 OID。相关:- How to check if a table exists in a given schema
- Select columns with particular column names in PostgreSQL
您必须从计数中排除新添加的列,否则计数将减一。
使用数据类型
smallint
进行计数,因为 table.[=24 中的列数不能超过 1600 =]我没有使用变量而是直接执行
SELECT
语句的结果。 plpgsql 中的分配相对昂贵。不过没什么大不了的。也是品味和风格的问题。我养成了在参数和变量前加上下划线 (
_tbl
) 的习惯,以排除变量和列名之间的歧义。