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
),它不能作为一个整体转义%I
在 format()
中。您必须单独转义每个标识符。
除此之外,我建议采用不同的方法。外循环是必要的,但内循环可以用更简单、更高效的基于集合的方法:
代替
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
我有一个包含多个相同模式的数据库。每个模式中都有许多名为 '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
),它不能作为一个整体转义%I
在 format()
中。您必须单独转义每个标识符。
除此之外,我建议采用不同的方法。外循环是必要的,但内循环可以用更简单、更高效的基于集合的方法:
代替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