在 EXECUTE 上传递 ROWTYPE 参数
Passing ROWTYPE parameter on EXECUTE
我正在 Postgres 中开发一个函数,旨在为查询的每条记录恢复一组函数中包含的检查结果的值。这些函数中只有一个会 return 正确的值。这些函数有一个公共前缀 'fn_condicao_' 并接收一个类型为 'my_table'.
的对象作为参数
由于进行检查的函数数量未知,我决定查阅 Postgres 目录,从 table pg_catalog.pg_proc
搜索前缀为 'fn_condicao_' 的函数并动态使用 EXECUTE 执行它们。
我的问题是如何为 EXECUTE.
传递正确的形状参数
create or replace function test_conditions()
returns void as
$$
declare
v_record my_table%rowtype;
v_function pg_proc%rowtype;
begin
set search_path = 'pg_catalog';
for v_record in (select * from my_table where id in (1,2,3)) loop
for v_function in (
SELECT p.proname
FROM pg_namespace n
JOIN pg_proc p
ON p.pronamespace = n.oid
WHERE n.nspname = 'operacional'
and p.proname like ('fn_condition\_%')
order by p.proname)
loop
--execute 'select ' || v_function.proname || '(' || v_record || ')';
end loop;
end loop;
end;
$$
language plpgsql;
如何在上面函数的注释 EXECUTE
命令中正确传递 v_record
?
execute 'select ' || v_function.proname || '(' || v_record || ')'; -- ???
示例函数:
create or replace function fn_condition_1(p_record my_table)
returns bigint as
$$
begin
if (.atributo1 > .atributo2) then
return 1;
end if;
return null;
end;
$$
language plpgsql;
我相信你的问题是函数中的 execute
命令试图插入 v_record
的值,这实际上将它变成了一个离散的参数列表,而不是本机行类型功能正常。
如果您愿意更改每个函数的参数类型,这可能是处理此问题的最简单方法。如果不是,那么您需要某种方式将本机行类型传递给动态函数调用。尽管看起来很糟糕,但我认为这样的方法可行:
create or replace function test_conditions()
returns void as
$$
declare
v_record my_table%rowtype;
v_function pg_proc%rowtype;
begin
set search_path = 'pg_catalog';
for v_record in (select * from my_table where id in (1,2,3)) loop
for v_function in (
SELECT p.proname
FROM pg_namespace n
JOIN pg_proc p
ON p.pronamespace = n.oid
WHERE n.nspname = 'operacional'
and p.proname like ('fn_condition_%')
order by p.proname)
loop
execute '
do $ZOOM$
declare
v_rec my_table%rowtype;
begin
select *
into v_rec
from my_table
where id = ' || v_record.id || ';
perform ' || func_name || '(v_rec);
end;
$ZOOM$
';
end loop;
end loop;
end;
$$
此外,我认为您需要将 select
更改为 perform
(如上所述)...要么这样做,要么执行 select into
.
此问题已在 DataBase Administrators by Erwin Brandstetter 中得到解答。所以,我想和你分享解决方案。
数据库管理员回答
在 Postgres 8.4 或更高版本中,您可以使用 EXECUTE
的 USING
子句来安全有效地传递值。这在您的 8.3 版中尚不可用。在您的版本中,它可以像这样工作:
CREATE OR REPLACE FUNCTION test_conditions()
RETURNS SETOF bigint AS
$func$
DECLARE
_rec record;
_func text;
_result bigint;
BEGIN
FOR _func in
SELECT p.proname
FROM pg_catalog.pg_namespace n
JOIN pg_catalog.pg_proc p ON p.pronamespace = n.oid
WHERE n.nspname = 'operacional'
AND p.proname LIKE E'fn\_condition\_%' -- no parens, proper string
ORDER BY p.proname -- no parens
LOOP
FOR _rec in
SELECT * FROM my_table WHERE id IN (1,2,3) -- no parens needed
LOOP
EXECUTE 'SELECT ' || quote_ident(_func) || '(' || quote_literal(_rec) || ')'
INTO _result;
RETURN NEXT _result;
END LOOP;
END LOOP;
END
$func$ LANGUAGE plpgsql SET search_path = 'public';
致电:
SELECT * FROM test_conditions();
如果您在函数体中使用 set search_path = 'pg_catalog';
,那么您在 public
架构中的 table 将不再可见。全局 SET
搜索路径将是一个非常糟糕的主意。效果在设置期间保持不变。您可以使用 SET LOCAL
将其包含在事务中,但这仍然不是一个好主意。相反,如果你真的需要,只设置函数的环境,就像演示的那样。
更多关于 Postgres 中的搜索路径:
- How does the search_path influence identifier resolution and the “current schema”
只是执行一个 SELECT
而不分配或返回结果是没有意义的。使用 EXECUTE
的 INTO
子句,然后使用 RETURN NEXT
。在现代 Postgres 中,您将用 RETURN QUERY EXECUTE
.
替换内部循环
在构建动态查询字符串时使用quote_ident()
和quote_literal()
正确转义标识符和文字。在现代 Postgres 中,您将使用 format()
.
将整行转换为它的字符串表示形式、转义和转换回来的效率不是很高。这种替代方法必须重复从 table 读取,但在其他方面更清晰(该行直接作为值传递):
FOR i IN
VALUES (1), (2), (3)
LOOP
EXECUTE 'SELECT ' || quote_ident(_func) || '(t) FROM my_table t WHERE id = ' || i
INTO _result;
RETURN NEXT _result;
END LOOP;
示例函数
您还可以使用此 SQL 函数从根本上简化您的示例函数:
CREATE OR REPLACE FUNCTION fn_condition_1(p_record my_table)
RETURNS bigint AS
$func$
SELECT CASE WHEN .atributo1 > .atributo2 THEN bigint '1' END
$func$ LANGUAGE sql;
我正在 Postgres 中开发一个函数,旨在为查询的每条记录恢复一组函数中包含的检查结果的值。这些函数中只有一个会 return 正确的值。这些函数有一个公共前缀 'fn_condicao_' 并接收一个类型为 'my_table'.
的对象作为参数由于进行检查的函数数量未知,我决定查阅 Postgres 目录,从 table pg_catalog.pg_proc
搜索前缀为 'fn_condicao_' 的函数并动态使用 EXECUTE 执行它们。
我的问题是如何为 EXECUTE.
传递正确的形状参数create or replace function test_conditions()
returns void as
$$
declare
v_record my_table%rowtype;
v_function pg_proc%rowtype;
begin
set search_path = 'pg_catalog';
for v_record in (select * from my_table where id in (1,2,3)) loop
for v_function in (
SELECT p.proname
FROM pg_namespace n
JOIN pg_proc p
ON p.pronamespace = n.oid
WHERE n.nspname = 'operacional'
and p.proname like ('fn_condition\_%')
order by p.proname)
loop
--execute 'select ' || v_function.proname || '(' || v_record || ')';
end loop;
end loop;
end;
$$
language plpgsql;
如何在上面函数的注释 EXECUTE
命令中正确传递 v_record
?
execute 'select ' || v_function.proname || '(' || v_record || ')'; -- ???
示例函数:
create or replace function fn_condition_1(p_record my_table)
returns bigint as
$$
begin
if (.atributo1 > .atributo2) then
return 1;
end if;
return null;
end;
$$
language plpgsql;
我相信你的问题是函数中的 execute
命令试图插入 v_record
的值,这实际上将它变成了一个离散的参数列表,而不是本机行类型功能正常。
如果您愿意更改每个函数的参数类型,这可能是处理此问题的最简单方法。如果不是,那么您需要某种方式将本机行类型传递给动态函数调用。尽管看起来很糟糕,但我认为这样的方法可行:
create or replace function test_conditions()
returns void as
$$
declare
v_record my_table%rowtype;
v_function pg_proc%rowtype;
begin
set search_path = 'pg_catalog';
for v_record in (select * from my_table where id in (1,2,3)) loop
for v_function in (
SELECT p.proname
FROM pg_namespace n
JOIN pg_proc p
ON p.pronamespace = n.oid
WHERE n.nspname = 'operacional'
and p.proname like ('fn_condition_%')
order by p.proname)
loop
execute '
do $ZOOM$
declare
v_rec my_table%rowtype;
begin
select *
into v_rec
from my_table
where id = ' || v_record.id || ';
perform ' || func_name || '(v_rec);
end;
$ZOOM$
';
end loop;
end loop;
end;
$$
此外,我认为您需要将 select
更改为 perform
(如上所述)...要么这样做,要么执行 select into
.
此问题已在 DataBase Administrators by Erwin Brandstetter 中得到解答。所以,我想和你分享解决方案。
数据库管理员回答
在 Postgres 8.4 或更高版本中,您可以使用 EXECUTE
的 USING
子句来安全有效地传递值。这在您的 8.3 版中尚不可用。在您的版本中,它可以像这样工作:
CREATE OR REPLACE FUNCTION test_conditions()
RETURNS SETOF bigint AS
$func$
DECLARE
_rec record;
_func text;
_result bigint;
BEGIN
FOR _func in
SELECT p.proname
FROM pg_catalog.pg_namespace n
JOIN pg_catalog.pg_proc p ON p.pronamespace = n.oid
WHERE n.nspname = 'operacional'
AND p.proname LIKE E'fn\_condition\_%' -- no parens, proper string
ORDER BY p.proname -- no parens
LOOP
FOR _rec in
SELECT * FROM my_table WHERE id IN (1,2,3) -- no parens needed
LOOP
EXECUTE 'SELECT ' || quote_ident(_func) || '(' || quote_literal(_rec) || ')'
INTO _result;
RETURN NEXT _result;
END LOOP;
END LOOP;
END
$func$ LANGUAGE plpgsql SET search_path = 'public';
致电:
SELECT * FROM test_conditions();
如果您在函数体中使用
set search_path = 'pg_catalog';
,那么您在public
架构中的 table 将不再可见。全局SET
搜索路径将是一个非常糟糕的主意。效果在设置期间保持不变。您可以使用SET LOCAL
将其包含在事务中,但这仍然不是一个好主意。相反,如果你真的需要,只设置函数的环境,就像演示的那样。
更多关于 Postgres 中的搜索路径:- How does the search_path influence identifier resolution and the “current schema”
只是执行一个
SELECT
而不分配或返回结果是没有意义的。使用EXECUTE
的INTO
子句,然后使用RETURN NEXT
。在现代 Postgres 中,您将用RETURN QUERY EXECUTE
. 替换内部循环
在构建动态查询字符串时使用
quote_ident()
和quote_literal()
正确转义标识符和文字。在现代 Postgres 中,您将使用format()
.将整行转换为它的字符串表示形式、转义和转换回来的效率不是很高。这种替代方法必须重复从 table 读取,但在其他方面更清晰(该行直接作为值传递):
FOR i IN VALUES (1), (2), (3) LOOP EXECUTE 'SELECT ' || quote_ident(_func) || '(t) FROM my_table t WHERE id = ' || i INTO _result; RETURN NEXT _result; END LOOP;
示例函数
您还可以使用此 SQL 函数从根本上简化您的示例函数:
CREATE OR REPLACE FUNCTION fn_condition_1(p_record my_table)
RETURNS bigint AS
$func$
SELECT CASE WHEN .atributo1 > .atributo2 THEN bigint '1' END
$func$ LANGUAGE sql;