PL/pgSQL 循环遍历多个模式、表和行

Posted

技术标签:

【中文标题】PL/pgSQL 循环遍历多个模式、表和行【英文标题】:PL/pgSQL Looping through multiple schema, tables and rows 【发布时间】:2015-03-02 22:49:57 【问题描述】:

我有一个包含多个相同架构的数据库。每个模式中都有许多名为“tran_...”的表。我想遍历所有模式中的所有“tran_”表并提取特定日期范围内的记录。这是我到目前为止的代码:

CREATE OR REPLACE FUNCTION public."configChanges"(starttime timestamp, endtime timestamp)
  RETURNS SETOF character varying AS
$BODY$DECLARE
    tbl_row RECORD;
    tbl_name VARCHAR(50);
    tran_row RECORD;
    out_record VARCHAR(200);
BEGIN
    FOR tbl_row IN
       SELECT * FROM pg_tables WHERE schemaname LIKE 'ivr%' AND tablename LIKE 'tran_%'
    LOOP
        tbl_name := tbl_row.schemaname || '.' || tbl_row.tablename;
         FOR tran_row IN
            SELECT * FROM tbl_name
            WHERE ch_edit_date >= starttime AND ch_edit_date <= endtime
         LOOP
            out_record := tbl_name || '    ' || tran_row.ch_field_name;
                        RETURN NEXT out_record;
         END LOOP;
    END LOOP;
    RETURN;
END;
$BODY$
LANGUAGE plpgsql;

当我尝试运行它时,我得到:

ERROR:  relation "tbl_name" does not exist
LINE 1: SELECT * FROM tbl_name WHERE ch_edit_date >= starttime AND c...

【问题讨论】:

总是提及您的 Postgres 版本。可能很重要。 【参考方案1】:

@Pavel 已经为您的基本错误提供了修复。

但是,由于您的 tbl_name 实际上是模式限定的(schema.table 中的两个 单独 标识符),它不能作为一个整体与 format() 中的 %I 一起转义。您必须单独转义每个标识符。

除此之外,我建议采用不同的方法。外层循环是必要的,但内层循环可以替换为更简单、更高效的基于集合的方法

CREATE OR REPLACE FUNCTION public.config_changes(_start timestamp, _end timestamp)
  RETURNS SETOF text AS
$func$
DECLARE
   _tbl text;
BEGIN
   FOR _tbl IN
      SELECT quote_ident(schemaname) || '.' || quote_ident(tablename)
      FROM   pg_tables
      WHERE  schemaname LIKE 'ivr%'
      AND    tablename LIKE 'tran_%'
   LOOP
      RETURN QUERY EXECUTE format (
         $$
         SELECT %1$L || '    ' || ch_field_name
         FROM   %1$s
         WHERE  ch_edit_date BETWEEN $1 AND $2
         $$, _tbl
         )
      USING _start, _end;
   END LOOP;
   RETURN;
END
$func$  LANGUAGE plpgsql;

您必须使用动态 SQL 来参数化标识符(或代码),就像 @Pavel 已经告诉您的那样。使用RETURN QUERY EXECUTE,您可以直接返回动态查询的结果。例子:

Return SETOF rows from PostgreSQL function Refactor a PL/pgSQL function to return the output of various SELECT queries

请记住,在动态 SQL 中,标识符必须被视为不安全的用户输入,并且必须始终进行清理以避免语法错误和 SQL 注入

Table name as a PostgreSQL function parameter

注意我如何分别转义表和架构:

 quote_ident(schemaname) || '.' || quote_ident(tablename)

因此,我只使用%s 在后面的查询中插入已经转义的表名。而%L 则将其转义为字符串文字以供输出。

我喜欢在参数和变量名称前加上_,以避免与列名发生命名冲突。没有其他特殊含义。

与您的原始功能相比略有不同。这个返回一个转义标识符(仅在必要时双引号)作为表名,例如:

"WeIRD name"

而不是

WeIRD name

简单得多

如果可能,请使用inheritance 来完全消除对上述功能的需求。完整示例:

Select (retrieve) all records from multiple schemas using Postgres

【讨论】:

我尝试了上述并得到了以下ERROR: missing FROM-clause entry for table "ivr27258"LINE 2: SELECT ivr27258.tran_control_record ...QUERY:SELECT ivr27258.tran_control_record || ' ' || ch_field_name FROM ivr27258.tran_control_record WHERE ch_edit_date BETWEEN starttime AND endtime @Deric:抱歉,字符串文字没有正确转义。现在应该修好了。 这让我成功了 99%。我必须做的唯一更改是将查询更改为RETURN QUERY EXECUTE format( $$ SELECT %1$L || ' ' || ch_field_name FROM %1$s WHERE ch_edit_date BETWEEN '%2$s' AND '%3$s' $$, tbl_name, starttime, endtime @Deric:啊,对。我忘记了函数参数。使用USING 子句传递,这比连接它们要好得多。请参阅上面的更新【参考方案2】:

您不能将 plpgsql 变量用作 SQL 表名或 SQL 列名。在这种情况下,您必须使用动态 SQL:

FOR tran_row IN
   EXECUTE format('SELECT * FROM %I
                   WHERE ch_edit_date >= starttime AND ch_edit_date <= endtime', tbl_name)
LOOP
   out_record := tbl_name || '    ' || tran_row.ch_field_name;
   RETURN NEXT out_record;
END LOOP;

【讨论】:

以上是关于PL/pgSQL 循环遍历多个模式、表和行的主要内容,如果未能解决你的问题,请参考以下文章

循环通过 PL/PGSQL 中的 CURSOR 而不锁定表

PL/pgSQL“for循环”+选择基本示例(“hello world”)

使用 PostgreSQL PL/pgSQL 在 For 循环中添加月份

Postgres 9.2 PL/pgSQL 循环简单更新

在 Pl/pgSQL 中使用 FOR 循环时,它在 Postgres 11.8 中不起作用

关于 Pl/pgsql 中游标的一些错误