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
,您可以直接返回动态查询的结果。例子:
请记住,在动态 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“for循环”+选择基本示例(“hello world”)
使用 PostgreSQL PL/pgSQL 在 For 循环中添加月份