在 PL/pgSQL 中动态执行查询

Executing queries dynamically in PL/pgSQL

我已经找到(我认为)解决我将要在 Oracle 和 SQL 服务器上提出的问题的解决方案,但似乎无法将其转化为 Postgres 解决方案。我正在使用 Postgres 9.3.6。

我们的想法是能够生成 "metadata" 关于 table 内容以进行分析。这只能通过对每一列进行查询 运行 来完成(AFAIK),以便找出,比如说... min/max/count 值等。为了使过程自动化,最好让数据库生成查询,然后执行。

举个例子 salesdata table,我可以为每一列生成一个 select 查询,返回 min() 值,使用以下代码片段:

SELECT 'SELECT min('||column_name||') as minval_'||column_name||' from salesdata '  
FROM information_schema.columns 
WHERE table_name = 'salesdata'

优点是无论列数如何,数据库都会生成代码。 现在我想到了无数个地方来存储这些查询,可以是某种变量,也可以是 table 列,然后执行这些查询。 我想将生成的查询存储在一个变量中,然后使用 EXECUTE(或 EXECUTE IMMEDIATE)语句执行它们,这是采用的方法 here(见右窗格),但 Postgres 不会让我在函数外部声明一个变量,我一直在绞尽脑汁想知道这将如何组合在一起,这是否是要遵循的方向,也许还有更简单的方法。

你有什么建议吗,我目前正在尝试这样的事情,受此启发 other question 但不知道我是否朝着正确的方向前进:

CREATE OR REPLACE FUNCTION foo()
RETURNS void AS
$$
DECLARE
    dyn_sql text; 
BEGIN            
dyn_sql := SELECT 'SELECT min('||column_name||') from salesdata'    
    FROM information_schema.columns 
    WHERE table_name = 'salesdata';
execute dyn_sql
END
$$ LANGUAGE PLPGSQL;    

系统统计

在你自己动手之前,先看看系统 table pg_statistic or the view pg_stats:

This view allows access only to rows of pg_statistic that correspond to tables the user has permission to read, and therefore it is safe to allow public read access to this view.

它可能已经有了您要计算的一些统计数据。它由 ANALYZE 填充,因此您可以 运行 在检查之前为新的(或任何)tables。

-- ANALYZE tbl;  -- optionally, to init / refresh
SELECT * FROM pg_stats
WHERE tablename = 'tbl'
AND   schemaname = 'public';

通用动态 plpgsql 函数

您想要 return 给定 table 中每一列的 最小值。这不是一项简单的任务,因为一个函数(通常像 SQL)需要在创建时知道 return 类型——或者至少在调用时借助多态数据类型。

此功能自动且安全地完成所有工作。适用于 any table,只要每一列都允许聚合函数 min()。但是您 需要 了解 PL/pgSQL.

CREATE OR REPLACE FUNCTION f_min_of(_tbl anyelement)
  RETURNS SETOF anyelement
  LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE (
   SELECT format('SELECT (t::%2$s).* FROM (SELECT min(%1$s) FROM %2$s) t'
                , string_agg(quote_ident(attname), '), min(' ORDER BY attnum)
                , pg_typeof(_tbl)::text)
   FROM   pg_attribute
   WHERE  attrelid = pg_typeof(_tbl)::text::regclass
   AND    NOT attisdropped  -- no dropped (dead) columns
   AND    attnum > 0        -- no system columns
   );
END
$func$;

致电(重要!):

SELECT * FROM f_min_of(NULL::tbl);  -- tbl being the table name

db<>fiddle here
sqlfiddle

你需要了解这些概念:

  • plpgsql 中的动态 SQL 与 EXECUTE
  • 多态类型
  • Postgres 中的行类型和 table 类型
  • 如何防御SQL注入
  • 聚合函数
  • 系统目录

详细解释的相关回答:

  • Table name as a PostgreSQL function parameter
  • Refactor a PL/pgSQL function to return the output of various SELECT queries
  • Postgres data type cast
  • How to set value of composite variable field using dynamic SQL
  • How to check if a table exists in a given schema
  • Select columns with particular column names in PostgreSQL
  • Generate series of dates - using date type as input

类型不匹配的特殊困难

我正在利用 Postgres 为每个现有 table 定义行类型。使用多态类型的概念,我能够创建适用于任何 table.

one 函数

但是,一些聚合函数 return 相关但与基础列相比不同的数据类型。例如,min(varchar_column) returns text,它是位兼容的,但不完全相同的数据类型。 PL/pgSQL 函数在这里有一个弱点,它坚持在 RETURNS 子句中声明的数据类型 完全 。没有尝试转换,甚至没有隐式转换,更不用说赋值转换了。

应该改进。使用 Postgres 9.3 测试。没有用 9.4 重新测试,但我很确定,这方面没有任何变化。

这就是这个构造作为 解决方法:

出现的地方
SELECT (t::tbl).* FROM (SELECT ... FROM tbl) t;

通过将整行强制转换为底层 table 的行类型,我们强制强制转换以获取每一列的原始数据类型。

对于某些聚合函数,这可能会失败。 sum() returns numeric 用于 sum(bigint_column) 以容纳溢出基本数据类型的总和。转换回 bigint 可能会失败 ...

@Erwin B运行dstetter,非常感谢您的广泛回答。 pg_stats 确实提供了一些东西,但我真正需要绘制一个完整的配置文件的是各种各样的东西,最小值、最大值、计数、空值计数、平均值等......所以一堆查询有每列为 运行,有些列带有 GROUP BY 等。

此外,感谢您强调数据类型的重要性,我有点期待这会在某个时候在工作中引发麻烦,我主要关心的是如何自动生成查询及其执行,这最后一点是我最关心的。

我已经尝试了您提供的功能(我可能需要开始学习一些 plpgsql)但是在 SELECT (t::tbl) 处出现错误:

ERROR: type "tbl" does not exist

顺便说一句,(t::abc) 符号指的是什么,在 python 中这将是一个列表切片,但在 PLPGSQL

中可能不是这种情况