无法获取 SP 的动态执行以返回 INOUT 参数
Posted
技术标签:
【中文标题】无法获取 SP 的动态执行以返回 INOUT 参数【英文标题】:Cannot Get Dynamic Exec of SP to Return INOUT Param 【发布时间】:2021-06-19 04:43:31 【问题描述】:使用 PostgreSQL 13.2,其中存储过程(请求者)被赋予要运行的存储过程列表的名称(作业组)。所有以这种方式执行的 sp 都被编码为写入日志记录作为它们的最后一个任务。我选择从所有 sp 中提取“附加日志”代码,而是使用 INOUT 行类型参数发回日志记录(总是一条记录),但遇到了麻烦。在我下面的示例中,请求者 sp 将从它调用的 sp 返回的记录加载到形状类似于永久日志表的临时表中。
永久表如下所示:
create table public.job_log (
log_id integer,
event_id integer,
job_id integer,
rows_affected integer);
请求者 sp 执行的任何一项作业都可能如下所示:
CREATE OR REPLACE procedure public.get_log_rcd(
inout p_log_rcd public.job_log)
LANGUAGE 'plpgsql'
as
$BODY$
declare
v_log_id integer = 40;
v_event_id integer = 698;
v_job_id integer = 45;
v_rows_affected integer = 60;
begin
select
v_log_id
, v_event_id
, v_job_id
, v_rows_affected
into
p_log_rcd.log_id,
p_log_rcd.event_id,
p_log_rcd.job_id,
p_log_rcd.rows_affected;
end;
$BODY$
这个示例 sp 没有做任何事情——这里的目的只是模拟日志参数的初始化以返回给调用者。
同样,将运行上述作业的请求者 sp 创建一个与永久日志具有相同结构的临时表:
drop table if exists tmp_log_cache;
create temp table tmp_log_cache as table public.job_log with no data;
如果请求者 sp 不必执行动态 SQL,它看起来像这里的块:
do
$$
declare
big_local public.job_log;
begin
call public.get_log_rcd( big_local );
insert into tmp_log_cache (
log_id
, event_id
, job_id
, rows_affected )
values (
big_local.log_id
, big_local.event_id
, big_local.job_id
, big_local.rows_affected);
end;
$$;
做一个
select * from tmp_log_cache;
返回包含预期 4 列值的行,一切正常。但是,需要动态执行。而且,我相信这里的大多数人都知道,以下狗不会打猎:
do
$$
declare
big_local public.job_log;
v_query_text varchar;
v_job_name varchar = 'public.get_log_rcd';
begin
select 'call ' || v_job_name || '( $1 );'
into v_query_text;
execute v_query_text using big_local::public.job_log;
insert into tmp_log_cache (
log_id
, event_id
, job_id
, rows_affected )
values (
big_local.log_id
, big_local.event_id
, big_local.job_id
, big_local.rows_affected);
end;
$$;
上述动态语句执行没有错误,但插入语句只有 NULL 值可以使用——插入一行,全部为空。任何建议都热烈欢迎。组成各种工作组的 sp 可能已经实现为函数,尽管在所有情况下,它们的主要任务是按摩、规范化、清理遥测数据,而不是吐出任何东西。
【问题讨论】:
【参考方案1】:嗯,documentation 声明“参数符号 (...) 仅适用于 SELECT
、INSERT
、UPDATE
和 DELETE
命令。”,所以这可能无法使用参数。
但作为一种解决方法,您可以构建一个动态的DO
并包含一个变量来获取值和其中的INSERT
。
DO
$o$
DECLARE
v_query_text varchar;
v_job_name varchar := format('%I.%I',
'public',
'get_log_rcd');
BEGIN
v_query_text := concat('DO ',
'$i$ ',
'DECLARE ',
' big_local public.job_log; ',
'BEGIN ',
' CALL ', v_job_name, '(big_local); ',
' INSERT INTO tmp_log_cache ',
' (log_id, ',
' event_id, ',
' job_id, ',
' rows_affected) ',
' VALUES (big_local.log_id, ',
' big_local.event_id, ',
' big_local.job_id, '
' big_local.rows_affected); ',
'END; ',
'$i$; ');
EXECUTE v_query_text;
END;
$o$;
db<>fiddle
【讨论】:
【参考方案2】:谢谢——我不会考虑使用执行来执行“执行”的能力。我只是没想到。好吧,这是我的解决方案:翻转到函数。
了解我的“请求者”只允许运行 sp,因为这是我们必须使用 SQL Server 做的事情,而且它是反射,我做了 1 行更改,将上面的示例 sp 翻转为一个函数:
CREATE OR REPLACE function public.get_log_rcdf(
inout p_log_rcd public.job_log)
returns public.job_log
LANGUAGE 'plpgsql'
as
$BODY$
declare
v_log_id integer = 40;
v_event_id integer = 698;
v_job_id integer = 45;
v_rows_affected integer = 60;
begin
select
v_log_id
, v_event_id
, v_job_id
, v_rows_affected
into
p_log_rcd.log_id,
p_log_rcd.event_id,
p_log_rcd.job_id,
p_log_rcd.rows_affected;
end;
$BODY$
事实上,对函数的更改需要添加一个 RETURNS 行。完毕。然后,将动态调用调整为 SELECT,并使用 INTO 修改执行:
do
$$
declare
big_local public.job_log;
v_query_text varchar;
v_job_name varchar = 'public.get_log_rcdf';
begin
select 'select * from ' || v_job_name || '( $1 );'
into v_query_text;
raise info 'SQL text is: %', v_query_text;
execute v_query_text into big_local using big_local;
insert into tmp_log_cache (
log_id
, event_id
, job_id
, rows_affected )
values (
big_local.log_id
, big_local.event_id
, big_local.job_id
, big_local.rows_affected);
end;
$$;
并且该过程现在可以完全按照需要进行。如第一个答案所示,我整理了对动态函数名称的处理,我认为我们到此完成。
【讨论】:
以上是关于无法获取 SP 的动态执行以返回 INOUT 参数的主要内容,如果未能解决你的问题,请参考以下文章
无法使用类型为 '(inout inout $T6, inout inout$T11) 的参数列表调用 'subscript'