遍历列名以获取PL/pgSQL函数中的计数
我在我的Postgres数据库中有一个表,我试图确定填充率(也就是说,我试图了解数据丢失/未丢失的频率)。我需要做一个函数,对于每一列(在我选择的几十列的列表中),计算具有非空值的列的数量和百分比 问题是,我真的不知道如何以编程方式迭代列列表,因为我不知道如何从列名称的字符串引用列。我已经读过关于如何使用动态编写的SQL来运行,但是我还没有能够让它工作。以下是我当前的功能:遍历列名以获取PL/pgSQL函数中的计数,sql,postgresql,count,plpgsql,dynamic-sql,Sql,Postgresql,Count,Plpgsql,Dynamic Sql,我在我的Postgres数据库中有一个表,我试图确定填充率(也就是说,我试图了解数据丢失/未丢失的频率)。我需要做一个函数,对于每一列(在我选择的几十列的列表中),计算具有非空值的列的数量和百分比 问题是,我真的不知道如何以编程方式迭代列列表,因为我不知道如何从列名称的字符串引用列。我已经读过关于如何使用动态编写的SQL来运行,但是我还没有能够让它工作。以下是我当前的功能: CREATE OR REPLACE FUNCTION get_fill_rates() RETURNS TABLE (fi
CREATE OR REPLACE FUNCTION get_fill_rates() RETURNS TABLE (field_name text, fill_count integer, fill_percentage float) AS $$
DECLARE
fields text[] := array['column_a', 'column_b', 'column_c'];
total_rows integer;
BEGIN
SELECT reltuples INTO total_rows FROM pg_class WHERE relname = 'my_table';
FOR i IN array_lower(fields, 1) .. array_upper(fields, 1)
LOOP
field_name := fields[i];
EXECUTE 'SELECT COUNT(*) FROM my_table WHERE $1 IS NOT NULL' INTO fill_count USING field_name;
fill_percentage := fill_count::float / total_rows::float;
RETURN NEXT;
END LOOP;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM get_fill_rates() ORDER BY fill_count DESC;
正如所写的,这个函数返回的每个字段都有100%的填充率,我知道这是错误的。如何使这个函数工作?我在写完问题后,但在提交问题之前,找到了解决方案——因为我已经完成了写问题的工作,所以我将继续并分享答案。问题出在我的EXECUTE语句中,特别是在
使用field\u name
位时。我认为当我这样做时,它被视为一个字符串文本,这意味着查询正在评估“字符串文本”是否为NULL,当然,这总是正确的
我需要将列名直接注入查询字符串,而不是参数化列名。因此,我将执行行更改为以下内容:
EXECUTE 'SELECT COUNT(*) FROM my_table WHERE ' || field_name || ' IS NOT NULL' INTO fill_count;
我知道你已经解决了。但我建议您避免在动态查询中连接标识符,您可以使用带有标识符通配符的格式
:
CREATE OR REPLACE FUNCTION get_fill_rates() RETURNS TABLE (field_name text, fill_count integer, fill_percentage float) AS $$
DECLARE
fields text[] := array['column_a', 'column_b', 'column_c'];
table_name name := 'my_table';
total_rows integer;
BEGIN
SELECT reltuples INTO total_rows FROM pg_class WHERE relname = table_name;
FOREACH field_name IN ARRAY fields
LOOP
EXECUTE format('SELECT COUNT(*) FROM %I WHERE %I IS NOT NULL', table_name, field_name) INTO fill_count;
fill_percentage := fill_count::float / total_rows::float;
RETURN NEXT;
END LOOP;
END;
$$ LANGUAGE plpgsql;
这样做将有助于防止SQL注入攻击,并将稍微减少查询解析开销。更多信息。除了代码中的一些问题(见下文),在普通查询中对表进行单次扫描可以大大加快速度和简化:
SELECT v.*
FROM (
SELECT count(column_a) AS ct_column_a
, count(column_b) AS ct_column_b
, count(column_c) AS ct_column_c
, count(*)::numeric AS ct
FROM my_table
) sub
, LATERAL (
VALUES
(text 'column_a', ct_column_a, round(ct_column_a / ct, 3))
, (text 'column_b', ct_column_b, round(ct_column_b / ct, 3))
, (text 'column_c', ct_column_c, round(ct_column_c / ct, 3))
) v(field_name, fill_count, fill_percentage);
这里的关键“技巧”是count()
我将百分比四舍五入到3位小数,这是可选的。为此,我将其转换为numeric
使用VALUES
表达式取消打印结果,并为每个字段获取一行
为了重复使用,或者如果要处理的列列表很长,则可以动态生成和执行查询。但是,同样,不要对每一列进行单独的计数。只需动态构建上述查询:
CREATE OR REPLACE FUNCTION get_fill_rates(tbl regclass, fields text[])
RETURNS TABLE (field_name text, fill_count bigint, fill_percentage numeric) AS
$func$
BEGIN
RETURN QUERY EXECUTE (
-- RAISE NOTICE '%', ( -- to debug if needed
SELECT
'SELECT v.*
FROM (
SELECT count(*)::numeric AS ct
, ' || string_agg(format('count(%I) AS %I', fld, 'ct_' || fld), ', ') || '
FROM ' || tbl || '
) sub
, LATERAL (
VALUES
(text ' || string_agg(format('%L, %2$I, round(%2$I/ ct, 3))', fld, 'ct_' || fld), ', (') || '
) v(field_name, fill_count, fill_pct)
ORDER BY v.fill_count DESC'
FROM unnest(fields) fld
);
END
$func$ LANGUAGE plpgsql;
电话:
如您所见,这适用于任何给定的表和列列表。
所有标识符都会使用format()
或regclass
类型的内置优点自动正确引用
相关的:
<>你原来的查询可以像这样改进,但这只是猪的唇膏。strong>不要使用这种低效的方法。
另外,pg_class.reltuples
只是一个估计值。由于您仍在计数,请使用实际计数
相关的:
也许更好的办法是使用带有标识符通配符的格式。这将避免SQL注入。
SELECT * FROM get_fill_rates('my_table', '{column_a, column_b, column_c}');
CREATE OR REPLACE FUNCTION get_fill_rates()
RETURNS TABLE (field_name text, fill_count bigint, fill_percentage float) AS
$$
DECLARE
fields text[] := '{column_a, column_b, column_c}'; -- must be legal identifiers!
total_rows float; -- use float right away
BEGIN
SELECT reltuples INTO total_rows FROM pg_class WHERE relname = 'my_table';
FOREACH field_name IN ARRAY fields -- use FOREACH
LOOP
EXECUTE 'SELECT COUNT(*) FROM big WHERE ' || field_name || ' IS NOT NULL'
INTO fill_count;
fill_percentage := fill_count / total_rows; -- already type float
RETURN NEXT;
END LOOP;
END
$$ LANGUAGE plpgsql;