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

PL/pgSQL Looping through multiple schema, tables and rows

我有一个包含多个相同模式的数据库。每个模式中都有许多名为 '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...

您不能将 plpgsql 变量用作 SQL table 名称或 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;

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

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

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

代替
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  AND 
         $$, _tbl
         )
      USING _start, _end;
   END LOOP;
   RETURN;
END
$func$  LANGUAGE plpgsql;
  • 您必须使用动态 SQL 来参数化标识符(或代码),就像@Pavel 已经告诉您的那样。使用 RETURN QUERY EXECUTE 可以直接 return 动态查询的结果。示例:

    • 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
  • 请注意我是如何分别转义 table 和模式的:

     quote_ident(schemaname) || '.' || quote_ident(tablename)
    
  • 因此我只是使用 %s 在后面的查询中插入已经转义的 table 名称。并且 %L 将其转义为输出的字符串文字。

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

与您的原始功能相比,略有不同。这个 return 是一个转义标识符(仅在必要时用双引号引起来)作为 table 名称,例如:

"WeIRD name"

而不是

WeIRD name

更简单

如果可能,请使用inheritance来完全避免对上述功能的需要。完整示例:

  • Select (retrieve) all records from multiple schemas using Postgres