从 Select 语句调用 Oracle 存储过程?

Call Oracle stored procedure from Select statement?

我有一个 Oracle 存储过程,它获取 2 个参数和 returns 2 个参数(状态和消息)。

我正在对这个只能执行 select 语句的遗留应用程序进行更改,

我的问题是可以用一些函数或其他存储过程或视图,或任何其他我可能不关心的对象来包装存储过程,所以我可以使用简单的 a [=32= 来执行存储过程吗? ]声明?

正确的执行代码应该是这样的:

DECLARE
  PRINTER_ID VARCHAR2(200);
  O_STATUS VARCHAR2(200);
  O_MESSAGE VARCHAR2(200);
BEGIN
  PRINTER_ID := '551555115';
  IMPL_XEROX_PRINTER_CHECK(    PRINTER_ID => PRINTER_ID,    O_STATUS => O_STATUS,    O_MESSAGE => O_MESSAGE  );
  DBMS_OUTPUT.PUT_LINE('O_STATUS = ' || O_STATUS);
  DBMS_OUTPUT.PUT_LINE('O_MESSAGE = ' || O_MESSAGE);
END;

我想得到的是这样的:

Select O_STATUS,O_MESSAGE from IMPL_XEROX_PRINTER_CHECk_WRAPPER where PRINTER_ID = '551555115';

问题是 SP 正在将一些数据插入临时 table... 这是 table:

 CREATE TABLE "TEST_PRNT_DATA"    ( "COLUMN1" VARCHAR2(20 BYTE),    "COLUMN2" VARCHAR2(20 BYTE),    "COLUMN3" VARCHAR2(20 BYTE)   ) 

/

这是存储过程:

CREATE OR REPLACE PROCEDURE IMPL_XEROX_PRINTER_CHECK 
(
  PRINTER_ID IN VARCHAR2 

, O_STATUS  OUT VARCHAR2
, O_MESSAGE OUT VARCHAR2
)  AS 
    PROC_STATUS         VARCHAR2(10);
    PROC_ERROR_MESSAGE  VARCHAR2(4000);

    rand_num number; 
BEGIN 

     dbms_output.put_line('IMPL_XEROX_PRINTER_CHECK ');
     select round(dbms_random.value(1,10)) into rand_num     from dual;   

     insert into TEST_PRNT_DATA values(1,2,3);

    IF rand_num < 5 THEN
            PROC_STATUS  := 'TRUE';
            O_STATUS:= 'TRUE';
            PROC_ERROR_MESSAGE := 'ALL IS GOOD';
            O_MESSAGE:= 'ALL IS GOOD';
    ELSE
            PROC_STATUS  := 'FALSE';
            O_STATUS:= 'FALSE';
            PROC_ERROR_MESSAGE := 'SOMTHING WENT WRONG!!! ';
            O_MESSAGE:= 'SOMTHING WENT WRONG!!! ';
    END IF;

END IMPL_XEROX_PRINTER_CHECK;

这取决于您的应用程序可以处理的内容。你可以有一个包装函数 returns 一个 ref cursor:

create or replace function impl_xerox_printer_check_wrap (
  printer_id in varchar2 
)
return sys_refcursor as
  o_status varchar2(200);
  o_message varchar2(200);
  o_refcursor sys_refcursor;
begin
  impl_xerox_printer_check(printer_id => printer_id, o_status => o_status, o_message => o_message);
  open o_refcursor for select o_status as status, o_message as message from dual;
  return o_refcursor;
end;
/

select impl_xerox_printer_check_wrap('551555115') from dual;

IMPL_XEROX_PRINTER_C
--------------------
CURSOR STATEMENT : 1

CURSOR STATEMENT : 1

STATUS     MESSAGE                       
---------- ------------------------------
TRUE       ALL IS GOOD                   

(SQL 开发人员显示的输出,运行 作为脚本)。但是您的应用程序可能不知道如何处理它。

您可以使用集合或对象类型,但除非您在架构级别定义自己的类型,否则解释起来有点麻烦:

create or replace function impl_xerox_printer_check_wrap (
  printer_id in varchar2 
)
return sys.odcivarchar2list as
  o_result sys.odcivarchar2list;
begin
  o_result := new sys.odcivarchar2list();
  o_result.extend(2);
  impl_xerox_printer_check(printer_id => printer_id, o_status => o_result(1), o_message => o_result(2));
  return o_result;
end;
/

select * from table (impl_xerox_printer_check_wrap('551555115'));

Result Sequence                                  
------------------------------------------------
TRUE
ALL IS GOOD

或者您可以通过 XML,这听起来很奇怪,但结果不错:

create or replace function impl_xerox_printer_check_wrap (
  printer_id in varchar2 
)
return xmltype as
  o_status varchar2(200);
  o_message varchar2(200);
  o_refcursor sys_refcursor;
begin
  impl_xerox_printer_check(printer_id => printer_id, o_status => o_status, o_message => o_message);
  open o_refcursor for select o_status as status, o_message as message from dual;
  return xmltype(o_refcursor);
end;
/

select impl_xerox_printer_check_wrap('551555115') from dual;

IMPL_XEROX_PRINTER_CHECK_WRAP('551555115')                                      
--------------------------------------------------------------------------------
<?xml version="1.0"?>
<ROWSET>
 <ROW>
  <STATUS>FALSE</STATUS>
  <MESSAGE>SOMTHING WENT WRONG!!! </MESSAGE>
 </ROW>
</ROWSET>

好的,这看起来不是很有用...但是您提取了值:

select status, message
from xmltable(
  '/ROWSET/ROW'
  passing impl_xerox_printer_check_wrap('551555115')
  columns status varchar2(200) path 'STATUS',
    message varchar2(200) path 'MESSAGE'
);

STATUS     MESSAGE                       
---------- ------------------------------
FALSE      SOMTHING WENT WRONG!!!        

db<>fiddle

您的应用程序可以 运行 该查询 - 当然将打印机 ID 作为绑定变量传递 - 并将返回一个简单的结果集。


由于您使用的是 12c,因此您可以使用添加到子查询分解中的 PL/SQL 功能,因此您根本不需要创建永久函数(尽管您可能仍然更喜欢):

drop function IMPL_XEROX_PRINTER_CHECK_WRAP;

with
  function impl_xerox_printer_check_wrap (
    printer_id in varchar2 
  )
  return xmltype as
    o_status varchar2(200);
    o_message varchar2(200);
    o_refcursor sys_refcursor;
  begin
    impl_xerox_printer_check(printer_id => printer_id, o_status => o_status, o_message => o_message);
    open o_refcursor for select o_status as status, o_message as message from dual;
    return xmltype(o_refcursor);
  end;
select impl_xerox_printer_check_wrap('551555115')
from dual
/

如果你想要 XML(根据评论),或者如果你不需要 XMLTable:

IMPL_XEROX_PRINTER_CHECK_WRAP('551555115')                                      
--------------------------------------------------------------------------------
<?xml version="1.0"?>
<ROWSET>
 <ROW>
  <STATUS>TRUE</STATUS>
  <MESSAGE>ALL IS GOOD</MESSAGE>
 </ROW>
</ROWSET>
with
  function impl_xerox_printer_check_wrap (
    printer_id in varchar2 
  )
  return xmltype as
    o_status varchar2(200);
    o_message varchar2(200);
    o_refcursor sys_refcursor;
  begin
    impl_xerox_printer_check(printer_id => printer_id, o_status => o_status, o_message => o_message);
    open o_refcursor for select o_status as status, o_message as message from dual;
    return xmltype(o_refcursor);
  end;
select status, message
from xmltable(
  '/ROWSET/ROW'
  passing impl_xerox_printer_check_wrap('551555115')
  columns status varchar2(200) path 'STATUS',
    message varchar2(200) path 'MESSAGE'
)
/

STATUS     MESSAGE                       
---------- ------------------------------
FALSE      SOMTHING WENT WRONG!!!        

The thing is that the SP is inserting some data to a temporary table

这是一个相当重要的遗漏。您不能在 select 的函数调用中执行插入或更新。文档 lists restrictions on functions called from SQL, and goes into more detail in this warning:

Because SQL is a declarative language, rather than an imperative (or procedural) one, you cannot know how many times a function invoked by a SQL statement will run—even if the function is written in PL/SQL, an imperative language.

如果允许该函数执行 DML,那么您将无法控制执行该 DML 的次数。例如,如果它正在执行插入操作,它可能会尝试插入同一行两次,然后重复数据或违反约束条件。


从技术上讲,您可以像 this modified db<>fiddle 中那样使用 pragma autonomous_transaction 声明该函数,但这是一个可怕的 hack,并且最终可能会导致比它解决的问题更多的问题,原因如下所示多于。如果您像示例中那样只进行单行调用,您 可能 会侥幸逃脱,但即便如此也不能保证有效;即使它现在可以工作,将来也可能会崩溃。

您可以使用流水线 table 函数创建包:

CREATE OR REPLACE 
PACKAGE PACKAGE1 
AS 
  type status_t is record ( o_status varchar2(10)
                          , o_message varchar2(4000));
  type status_tt is table of status_t;

  function impl_xerox_printer_check_w(printer_id varchar2) RETURN status_tt PIPELINED;

END PACKAGE1;
/

实施如下:

CREATE OR REPLACE
PACKAGE BODY PACKAGE1 AS

  function impl_xerox_printer_check_w(printer_id varchar2) RETURN status_tt PIPELINED AS
    status status_t;
  BEGIN
    impl_xerox_printer_check(printer_id, status.o_status, status.o_message);

    PIPE ROW (status);
    RETURN;
  END impl_xerox_printer_check_w;

END PACKAGE1;
/

并像这样使用它:

with printers as (
  select dbms_random.string('X',10) printer from dual connect by level <=5
)
select * 
  from printers
 cross apply table(package1.impl_xerox_printer_check_w(printers.printer));

示例输出或查看 db<>fiddle:

PRINTER         O_STATUS   O_MESSAGE                     
--------------- ---------- ------------------------------
55FBCMHYOS      TRUE       ALL IS GOOD                   
0Z37VPOSLK      TRUE       ALL IS GOOD                   
XK1QKTZ8X2      FALSE      SOMTHING WENT WRONG!!!        
K0Y6TN9YTR      FALSE      SOMTHING WENT WRONG!!!        
8D0505711L      TRUE       ALL IS GOOD     

根据 Alex 的几个答案(sys.odcivarchar2list 集合和函数)的组合,这里有几个主题的变体:

第一个 returns 与大多数示例一样,在最后一个查询中使用数据透视表的单行:

with function wrap(printer_id in varchar2) return sys.odcivarchar2list as
  status sys.odcivarchar2list;
begin
  status := new sys.odcivarchar2list();
  status.extend(2);
  impl_xerox_printer_check(printer_id, status(1), status(2));
  return status;
end;
t1 as (
select rownum r, column_value
  from wrap('551555115')
)
select * 
  from t1
  pivot (max(column_value) 
         for r in ( 1 as status
                  , 2 as message));
/

示例输出:

STATUS   MESSAGE                  
-------- -------------------------
FALSE    SOMTHING WENT WRONG!!!   

第二个示例演示如何使用 CROSS APPLY 一次获取多台打印机的状态:

with function wrap(printer_id in varchar2) return sys.odcivarchar2list as
  status sys.odcivarchar2list;
begin
  status := new sys.odcivarchar2list();
  status.extend(2);
  impl_xerox_printer_check(printer_id, status(1), status(2));
  return status;
end;
printers as (
  select dbms_random.string('X',10) printer from dual connect by level <=5
), t1 as (
select printer, mod(rownum-1,2) r,  w.*
  from printers
  cross apply wrap(printers.printer) w
)
select * 
  from t1
  pivot (max(column_value) for r in (0 as status, 1 as message));
/

示例输出:

PRINTER    STATUS   MESSAGE                  
---------- -------- -------------------------
M6N6MZ5NG6 TRUE     ALL IS GOOD              
4H2WKK52V7 TRUE     ALL IS GOOD              
6MB7B9FRWV TRUE     ALL IS GOOD              
389KALS4U9 FALSE    SOMTHING WENT WRONG!!!   
6Y1ACVUHY6 TRUE     ALL IS GOOD              

创建一个新的 Sql 触发器来监视 table Table_legacyInputOutput。使用打印机 ID PRINTER_ID = '551555115' 在 table 中插入您的输入 然后触发器将调用存储过程并更新 table 对于 O_STATUS 和 O_MESSAGE 。 我认为您的遗留应用程序至少可以插入和 select。它只是不能调用 SP 并检查 return 参数

Table_legacyInputOutput structure.
PRINTER         O_STATUS   O_MESSAGE