无法获取 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 声明“参数符号 (...) 仅适用于 SELECTINSERTUPDATEDELETE 命令。”,所以这可能无法使用参数。

但作为一种解决方法,您可以构建一个动态的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'

怎样用sql server自定义函数,实现动态的sql,返回一个table类型

跟踪数据库中执行时间超过1.5秒的语句及SP,导入数据库

MySQL-存储过程动态执行sql

创建存储过程 in,out,inout

swift 无法将类型“[Liquor]”的值转换为预期的参数类型“inout _”