Sendig явный CURSOR как параметр процедуры - PullRequest
0 голосов
/ 30 сентября 2019

У меня есть следующий код. Есть внешняя процедура, имеющая inner_procedure для обработки курсоров (конкатенация данных из курсора в переменную m (message)). Я могу открыть курсор и отправить ссылку на внутреннюю процедуру для обработки, например:

PROCEDURE outer_proc AS

  m VARCHAR2(2000):='';

  cur SYS_REFCURSOR;

  PROCEDURE inner_proc(cur IN SYS_REFCURSOR,m OUT VARCHAR2) IS
    firstname VARCHAR2(20);
    lastname VARCHAR2(20);
  BEGIN
    LOOP
      FETCH cur INTO firstname,lastname;
      EXIT WHEN cur%NOTFOUND;
      m:=m||firstname||' '||lastname;
    END LOOP;
  END;

BEGIN

  OPEN cur FOR SELECT * FROM employees WHERE sallary<1000;
    inner_proc(cur,m);
  CLOSE cur;

  OPEN cur FOR SELECT * FROM employees WHERE sallary>=1000;
    inner_proc(cur,m);
  CLOSE cur;

END;     

Но я хотел бы отправить явное имя курсора внутренней процедуре и оставить внутреннюю процедуру, чтобы открыть курсор и обработать его следующим образом:

PROCEDURE outer_proc AS

  TYPE cur_type IS REF CURSOR;

  m VARCHAR2(2000):='';

  CURSOR c1 IS SELECT * FROM employees WHERE sallary<1000;
  CURSOR c2 IS SELECT * FROM employees WHERE sallary>=1000;

  PROCEDURE inner_proc(cur IN cur_type,m OUT VARCHAR2) IS
    col1 VARCHAR2(20);
    col2 VARCHAR2(20);
  BEGIN
    OPEN cur;
      LOOP
        FETCH cur INTO col1,col2;
        EXIT WHEN cur%NOTFOUND;
        m:=m||col1||' '||col2;
      END LOOP;
    CLOSE cur;
  END;

BEGIN

  inner_proc(c1,m);
  inner_proc(c2,m);

END;     

В моем примере выше у курсоров случайно есть тот же %ROWTYPE, но inner_procedure не может знать об этом заранее. Моя внутренняя процедура должна получить в качестве параметра произвольный курсор, открыть его и, наконец, объединить первый и второй столбцы курсора с сообщением.

В моем первом коде, использующем SYS_REFCURSOR в параметре, я мог бы также отправлять ссылку на разные курсоры. ... почему я должен отправить ссылку на уже открытый курсор, я хотел бы, чтобы процесс открытия и закрытия курсора был частью внутренней процедуры. Я хотел бы отправить имя курсора и оставить открытие и закрытие (как, конечно, прохождение) внутренней процедуре.

Как мне этого добиться?

Ответы [ 2 ]

1 голос
/ 30 сентября 2019

Из документации по явным курсорам (выделение добавлено):

Нельзя назначить значение явному курсору, использовать его в выражении, или использоватьэто как формальный параметр подпрограммы или переменная хоста. Вы можете сделать это с помощью переменной курсора (см. « Переменные курсора »).

Таким образом, вы не можете отправить явный курсор, c1 или c2 в вашем примере- у вас не может быть процедуры, которая принимает явный аргумент курсора.

Вам разрешено указывать ref курсор в качестве формального параметра , как вы уже делаете,Это может быть открытый курсор, как у вас;или переменная курсора, которую вы затем открываете. Но на самом деле вы не хотите, чтобы курсор находился вне вызова процедуры, поэтому ни один из них не выглядит вполне тем, что вам нужно.

Если бы у вас был определенный список курсоров с небольшим количеством символов, вы могли бы буквально передать имя курсора в виде строки, а затем используйте оператор case в процедуре, чтобы открыть соответствующий предопределенный курсор:

PROCEDURE outer_proc AS

  m VARCHAR2(2000):='';

  CURSOR c1 IS SELECT first_name,last_name FROM employees WHERE salary<1000;
  CURSOR c2 IS SELECT first_name,last_name FROM employees WHERE salary>=1000;

  PROCEDURE inner_proc(cur_name IN varchar2,m OUT VARCHAR2) IS
    col1 VARCHAR2(20);
    col2 VARCHAR2(20);
  BEGIN
    CASE cur_name
      WHEN 'c1' THEN
        OPEN c1;
        LOOP
          FETCH c1 INTO col1,col2; -- this will only work in the cursor only selects two columns!
          EXIT WHEN c1%NOTFOUND;
          m:=m||col1||' '||col2;
        END LOOP;
        CLOSE c1;
      WHEN 'c2' THEN
        OPEN c2;
        LOOP
          FETCH c2 INTO col1,col2; -- this will only work in the cursor only selects two columns!
          EXIT WHEN c2%NOTFOUND;
          m:=m||col1||' '||col2;
        END LOOP;
        CLOSE c2;
    END CASE;
  END;

BEGIN

  inner_proc('c1',m);
  inner_proc('c2',m);

END;     
/

... но это не масштабируется и, возможно, уже хуже, чем чтотеперь у вас есть.

Или передайте курсор запрос вместо курсора в процедуру, а затем откройте и обработайте его с помощью динамического SQL. Так как именно это продемонстрировал @Wernfried, я не буду вдаваться в подробности. Возможный недостаток этого заключается в том, что если у вас уже есть явный курсор, который вы пытаетесь использовать повторно, то теперь вы дублируете этот запрос. Да, и поскольку он динамический, сам запрос не будет проанализирован до времени выполнения, поэтому ошибка может быть перехвачена не так рано, как хотелось бы.

TL; DR - см. @ Wernfried'sответ * 8 -)

1 голос
/ 30 сентября 2019

Я не вижу причин для внутренней процедуры, вы можете достичь ее с помощью отдельной процедуры. Когда вы передаете курсор в процедуру / функцию, он должен быть открыт, т.е. вы не можете открыть курсор внутри внутренней процедуры.

Может быть так (не проверено):

CREATE OR REPLACE FUNCTION process_cursor(cur IN SYS_REFCURSOR) AS VARCHAR2 IS
    res VARCHAR2(10000);

    curid INTEGER;
    col_cnt INTEGER;
    rec_tab DBMS_SQL.DESC_TAB;
    v_rows_processed INTEGER;

    col_1 VARCHAR2(4000);
    col_2 VARCHAR2(4000);

BEGIN
    curid := DBMS_SQL.TO_CURSOR_NUMBER(cur);
    DBMS_SQL.DESCRIBE_COLUMNS(curid, col_cnt, rec_tab);  

    -- The generic approach
    /*
    FOR c in 1..col_cnt LOOP
        rec := rec_tab(c);
        IF rec.col_type = DBMS_TYPES.TYPECODE_NUMBER THEN
            DBMS_SQL.DEFINE_COLUMN(curid, c, num_var); 
        ELSIF rec.col_type = DBMS_TYPES.TYPECODE_VARCHAR2 THEN
            DBMS_SQL.DEFINE_COLUMN(curid, c, string_var, rec.col_max_len); 
        ELSIF rec.col_type = DBMS_TYPES.TYPECODE_DATE THEN
            DBMS_SQL.DEFINE_COLUMN(curid, c, date_var); 
            -- .. some more data types if needed
        END IF;
    END LOOP;
    */

    -- however you like to get only the first two columns which are strings.
    DBMS_SQL.DEFINE_COLUMN(curid, 1, col_1, rec_tab(1).col_max_len);
    DBMS_SQL.DEFINE_COLUMN(curid, 2, col_2, rec_tab(2).col_max_len);

    v_rows_processed := DBMS_SQL.EXECUTE(curid);

    WHILE DBMS_SQL.FETCH_ROWS(curid) > 0 LOOP
        -- The generic approach
        /*
        FOR c IN 1..col_cnt LOOP
            rec := rec_tab(c);
            IF rec.col_type = DBMS_TYPES.TYPECODE_NUMBER THEN
                DBMS_SQL.COLUMN_VALUE(curid, c, num_var); 
            ELSIF rec.col_type = DBMS_TYPES.TYPECODE_VARCHAR2 THEN
                DBMS_SQL.COLUMN_VALUE(curid, c, string_var); 
            ELSIF rec.col_type = DBMS_TYPES.TYPECODE_DATE THEN
                DBMS_SQL.COLUMN_VALUE(curid, c, date_var); 
                -- .. some more data types if needed
            END IF;
        END LOOP;
        */

        -- however you like to get only the first two columns which are strings.
      res := res || DBMS_SQL.COLUMN_VALUE(curid, 1, col_1)||' '||DBMS_SQL.COLUMN_VALUE(curid, 2, col_2);
    END LOOP; 

    DBMS_SQL.CLOSE_CURSOR(curid);
    RETURN res;

END;

Я предпочитаю ФУНКЦИЮ, а не ПРОЦЕДУРУ, если процедура имеет только одно возвращаемое значение.

Вы бывызовите функцию следующим образом:

DECLARE
    cur SYS_REFCURSOR;
    ret VARCHAR2(10000);
BEGIN
    OPEN cur FOR SELECT * FROM employees WHERE salary < 1000;
    ret := process_cursor(cur);

    OPEN cur FOR SELECT * FROM customers WHERE turnover > 1000;
    ret := process_cursor(cur);

END;

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

CREATE OR REPLACE FUNCTION process_cursor_str(sqlStr IN VARCHAR2) AS VARCHAR2 IS

    cur SYS_REFCURSOR;
    res VARCHAR2(10000);
    ...

BEGIN
    OPEN cur FOR sqlStr;
    curid := DBMS_SQL.TO_CURSOR_NUMBER(cur);
    ...


DECLARE
    ret VARCHAR2(10000);
BEGIN
    ret := process_cursor_str('SELECT * FROM employees WHERE salary < 1000');       
    ret := process_cursor_str('SELECT * FROM customers WHERE turnover > 1000');    
END;
...