Вызовите Oracle хранимую процедуру из оператора Select? - PullRequest
4 голосов
/ 19 февраля 2020

У меня есть Oracle хранимая процедура, которая получает 2 параметра и возвращает 2 параметра (состояние и сообщение).

Я делаю изменения в этом устаревшем приложении, способном только выполнять операторы select,

Мой вопрос: возможно ли обернуть хранимую процедуру какой-либо функцией или другой хранимой процедурой или представлением, или любым другим объектом, о котором я мог бы не знать, поэтому я могу выполнить хранимую процедуру с помощью простого оператора выбора?

Правильный код выполнения будет выглядеть примерно так:

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 вставляет некоторые данные в временная таблица ... это таблица:

 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;

Ответы [ 4 ]

3 голосов
/ 19 февраля 2020

Вы можете создать пакет с конвейерной табличной функцией:

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     
3 голосов
/ 20 февраля 2020

Основываясь на комбинации пары ответов Алекса (коллекции sys.odcivarchar2list и с функциями), вот пара вариантов по темам:

Первая возвращает одну строку, как в большинстве примеров, используя сводка в последнем запросе:

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              
2 голосов
/ 19 февраля 2020

Это зависит от того, что может обработать ваше приложение. У вас может быть функция-обертка, которая возвращает курсор ref:

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 Developer, запускается как скрипт). Но ваше приложение может не знать, что с этим делать.

Вы можете использовать коллекцию или тип объекта, но если вы не определите свой собственный на уровне схемы, интерпретировать это будет немного сложно:

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

Или вы можете go через 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

Ваше приложение может выполнить этот запрос - конечно, передав идентификатор принтера в качестве переменной привязки - и получит простой ответ набор результатов.


Поскольку вы используете 12 c, вы можете использовать возможности 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!!!        

Дело в том, что SP вставляет некоторые данные во временную таблицу

Это довольно важный пропуск п. Вы не можете выполнить вставку или обновление в вызове функции из выбора. В документации перечислены ограничения на функции, вызываемые из SQL, а более подробно рассматривается в этом предупреждении :

Поскольку SQL является декларативным языком вместо императивного (или процедурного) вы не можете знать, сколько раз будет выполняться функция, вызванная оператором SQL, даже если функция написана на PL / SQL, императивном языке.

Если бы функции было разрешено выполнять DML, то вы не могли бы контролировать, сколько раз выполнялся DML. Например, если он выполняет вставку, он может попытаться вставить одну и ту же строку дважды и либо дублировать данные, либо получить нарушение ограничения.


Технически можно объявить функцию с помощью pragma autonomous_transaction Как и в это модифицированный db <> fiddle , но это ужасный хак, который, вероятно, в конечном итоге вызовет больше проблем, чем решит, по причинам, указанным выше. Вы могли бы обойтись без этого, если бы вы только когда-либо делали однорядные вызовы, как в вашем примере, но даже тогда это не гарантируется; и даже если он работает сейчас, он может сломаться в будущем.

1 голос
/ 09 марта 2020

Создайте новый триггер Sql, который просматривает таблицу Table_legacyInputOutput. Вставьте свой ввод в таблицу с идентификатором принтера PRINTER_ID = '551555115' Тогда триггер вызовет хранимую процедуру и обновит таблицу для O_STATUS и O_MESSAGE. Я думаю, что ваше устаревшее приложение может вставить и выбрать по крайней мере. Он просто не может вызвать SP и проверить возвращаемые параметры

Table_legacyInputOutput structure.
PRINTER         O_STATUS   O_MESSAGE  
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...