Пытается собрать и вернуть DBMS_ SQL .COLUMN_VALUE, используя пользовательский тип - PullRequest
2 голосов
/ 27 февраля 2020

Это мой первый вопрос о StackOverflow, и я самоучка, поэтому, пожалуйста, будьте осторожны.

Моя цель здесь - иметь возможность массового сбора заголовков / значений из динамического c запроса / курсора в сгенерированный пакет через

    SELECT * 
    BULK COLLECT INTO l_cur_val 
    FROM TABLE (CUSTOM.GET_REF_VAL(l_cursor));

У меня это успешно работает для заголовков столбцов в блоке кода, аналогичном GET_REF_VAL (просто RETURN l_col_head перед входом в раздел / * COLUMN VALUES * /).

Ошибки появляются, когда я пытаюсь присвоить возвращаемое значение DBMS_ SQL .COLUMN_VALUE в мой t_col_val UDT. (Определения типов в комментариях)

ТИПЫ

CREATE OR REPLACE TYPE CUSTOM.r_col_val IS OBJECT (l_col_val VARCHAR2(250 byte));
CREATE OR REPLACE TYPE CUSTOM.t_col_val IS TABLE OF CUSTOM.r_col_val;

ОШИБКИ

--l_val(n) := r_col_val(l_dum_val);    --returns: ORA-06533: Subscript beyond count
--l_val(n) := l_dum_val;               --returns: PLS-00382: expression is of wrong type

Функция возврата таблицы GET_REF_VAL

CREATE OR REPLACE FUNCTION 
CUSTOM.GET_REF_VAL
    (
    p_cursor IN SYS_REFCURSOR
    )
RETURN t_col_val
IS

l_val t_col_val := t_col_val();
l_col t_col_head := t_col_head();

n INTEGER := 0;

l_cursor        SYS_REFCURSOR := p_cursor;
l_cursor_id     INTEGER;
l_dummy         INTEGER;
l_col_cnt       INTEGER;
l_tab_rec       DBMS_SQL.DESC_TAB2;
l_dum_val       VARCHAR2(250);

BEGIN

    l_cursor_id := DBMS_SQL.TO_CURSOR_NUMBER(l_cursor); 
    DBMS_SQL.DESCRIBE_COLUMNS2(l_cursor_id, l_col_cnt, l_tab_rec);

/* COLUMN HEADERS */
FOR r IN 1..l_col_cnt
    LOOP

        l_col.extend;
        n := n + 1;
        l_col(n) := r_col_head(l_tab_rec(r).col_name);
        DBMS_SQL.DEFINE_COLUMN(l_cursor_id, r, l_dum_val, 4000); 

    END LOOP;

/* COLUMN VALUES */
LOOP 
    IF DBMS_SQL.FETCH_ROWS(l_cursor_id)> 0 THEN 

    FOR i IN 1 .. l_col_cnt
    LOOP

        l_val.extend;
        n := n + 1;
        DBMS_SQL.COLUMN_VALUE(l_cursor_id, i, l_dum_val);        
        DBMS_OUTPUT.PUT_LINE(l_dum_val); -- This return l_dum_val with no issues

        --l_val(n) := r_col_val(l_dum_val); -- ORA-06533: Subscript beyond count
        --l_val(n) := l_dum_val; --PLS-00382: expression is of wrong type

    END LOOP;

    ELSE 
    EXIT; 
    END IF;
END LOOP;  


DBMS_SQL.CLOSE_CURSOR(l_cursor_id);
RETURN l_val;

END;
/

Блок выполнения

DECLARE

l_sql_stmt VARCHAR(10000) :=
q'!
    SELECT
        SYS_CONTEXT('USERENV','OS_USER') AS OS_USER ,
        SYS_CONTEXT('USERENV','SESSION_USER') AS SESSION_USER,
        SYS_CONTEXT('USERENV','ISDBA') AS ISDBA,
        SYS_CONTEXT('USERENV','SID') AS SID,
        SYS_CONTEXT('USERENV','CURRENT_SQL') AS CURRENT_SQL,
        SYS_CONTEXT('USERENV','DB_NAME') AS DB_NAME,
        SYS_CONTEXT('USERENV','HOST') AS HOST,
        SYS_CONTEXT('USERENV','IP_ADDRESS') AS IP_ADDRESS,
        SYS_CONTEXT('USERENV','SERVICE_NAME') AS SERVICE_NAME
    FROM
        DUAL
!';

l_cursor        SYS_REFCURSOR;
l_cursor_id     INTEGER;
l_dummy         VARCHAR2(50);

TYPE t_cur_head IS TABLE OF VARCHAR2(250) INDEX BY BINARY_INTEGER;
l_cur_head t_cur_head;

TYPE t_cur_val IS TABLE OF VARCHAR2(250) INDEX BY BINARY_INTEGER;
l_cur_val t_cur_val;    

BEGIN

l_cursor := CUSTOM.GET_REF_CUR(l_sql_stmt);

IF l_cursor%ISOPEN
THEN

    /* Header fetch works fine */

    /*
    SELECT * 
    BULK COLLECT INTO l_cur_head 
    FROM TABLE (CUSTOM.GET_REF_HEAD(l_cursor));

    FOR i IN 1 .. l_cur_head.COUNT
    LOOP
    DBMS_OUTPUT.PUT_LINE(l_cur_head(i));
    END LOOP;
    */


    /* Values fetch fails */

    SELECT * 
    BULK COLLECT INTO l_cur_val 
    FROM TABLE (CUSTOM.GET_REF_VAL(l_cursor));

    FOR i IN 1 .. l_cur_val.COUNT
    LOOP
    DBMS_OUTPUT.PUT_LINE(l_cur_val(i));
    END LOOP;

END IF;

END;

Итак, я думаю, что в итоге я хочу знать, что

a) Как обрабатывать возвращаемое значение dbms_ sql .column_value, используя пользовательский тип

б) Как вставить значение VARCHAR2 (l_dum_val) в объект UDT с записями VARCHAR2 (l_col_val)

c) Любые другие очевидные ошибки / плохие методы в коде?

Спасибо за Ваше время терпение.

1 Ответ

2 голосов
/ 27 февраля 2020

Первая из ваших закомментированных строк:

    --l_val(n) := r_col_val(l_dum_val); -- ORA-06533: Subscript beyond count

получает эту ошибку, потому что вы не сбрасываете n в ноль перед вторым l oop. Вам даже не нужна эта переменная-счетчик, вы можете использовать вместо нее l_val.count (в обоих циклах).

Вторая из ваших закомментированных строк:

    --l_val(n) := l_dum_val; --PLS-00382: expression is of wrong type

получает эту ошибку, потому что l_val(n) указывает на объект, который имеет строковый атрибут; он не указывает прямо на строку. Таким образом, вы можете назначить новый объект через его конструктор; это то, что пыталась сделать первая версия, но это должно быть:

    l_val(l_val.count) := r_col_val(l_dum_val);

Как только этот объект существует, вы можете назначить атрибут напрямую с помощью:

    l_val(some_index).l_col_val := r_col_val(l_dum_val);

, но вы должны создайте объект, прежде чем вы сможете получить доступ к его атрибутам, и, поскольку у вас есть только конструктор по умолчанию, в этом случае он, вероятно, вряд ли будет вам полезен.

Так же и с этими изменениями (и некоторыми отступами, и немного рефакторинг, чтобы избавиться от else), теперь это работает:

CREATE OR REPLACE FUNCTION 
GET_REF_VAL
    (
    p_cursor IN SYS_REFCURSOR
    )
RETURN t_col_val
IS

    l_val t_col_val := t_col_val();
    l_col t_col_head := t_col_head();

    l_cursor        SYS_REFCURSOR := p_cursor;
    l_cursor_id     INTEGER;
    l_dummy         INTEGER;
    l_col_cnt       INTEGER;
    l_tab_rec       DBMS_SQL.DESC_TAB2;
    l_dum_val       VARCHAR2(250);

BEGIN

    l_cursor_id := DBMS_SQL.TO_CURSOR_NUMBER(l_cursor); 
    DBMS_SQL.DESCRIBE_COLUMNS2(l_cursor_id, l_col_cnt, l_tab_rec);

    /* COLUMN HEADERS */
    FOR r IN 1..l_col_cnt
    LOOP

        l_col.extend;
        l_col(l_col.count) := r_col_head(l_tab_rec(r).col_name);
        DBMS_SQL.DEFINE_COLUMN(l_cursor_id, r, l_dum_val, 4000); 

    END LOOP;

    /* COLUMN VALUES */
    LOOP 
        IF DBMS_SQL.FETCH_ROWS(l_cursor_id) = 0 THEN 
            EXIT; 
        END IF;

        FOR i IN 1 .. l_col_cnt
        LOOP

            l_val.extend;
            DBMS_SQL.COLUMN_VALUE(l_cursor_id, i, l_dum_val);        
            DBMS_OUTPUT.PUT_LINE(l_dum_val);
            l_val(l_val.count) := r_col_val(l_dum_val);

        END LOOP;
    END LOOP;  

    DBMS_SQL.CLOSE_CURSOR(l_cursor_id);
    RETURN l_val;

END;
/

db <> fiddle


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

CREATE OR REPLACE PROCEDURE
GET_REF_HEAD_AND_VAL
    (
    p_cursor IN OUT SYS_REFCURSOR,
    p_col OUT SYS.odcivarchar2list,
    p_val OUT SYS.odcivarchar2list
    )
IS

    l_cursor_id     INTEGER;
    l_col_cnt       INTEGER;
    l_tab_rec       DBMS_SQL.DESC_TAB3;
    l_value         VARCHAR2(250 byte);

BEGIN

    l_cursor_id := DBMS_SQL.TO_CURSOR_NUMBER(p_cursor); 
    DBMS_SQL.DESCRIBE_COLUMNS3(l_cursor_id, l_col_cnt, l_tab_rec);

    /* COLUMN HEADERS */
    p_col := SYS.odcivarchar2list();
    FOR r IN 1..l_col_cnt
    LOOP

        p_col.extend;
        p_col(p_col.count) := l_tab_rec(r).col_name;
        DBMS_SQL.DEFINE_COLUMN(l_cursor_id, r, l_value, 250);

    END LOOP;

    /* COLUMN VALUES */
    p_val := SYS.odcivarchar2list();
    LOOP 
        IF DBMS_SQL.FETCH_ROWS(l_cursor_id) = 0 THEN 
            EXIT; 
        END IF;

        FOR i IN 1 .. l_col_cnt
        LOOP

            p_val.extend;
            DBMS_SQL.COLUMN_VALUE(l_cursor_id, i, l_value);        
            --DBMS_OUTPUT.PUT_LINE(l_dum_val);
            p_val(p_val.count) := l_value;

        END LOOP;
    END LOOP;  

    DBMS_SQL.CLOSE_CURSOR(l_cursor_id);

END;
/

При этом используется встроенный тип коллекции вместо создания собственных типов объектов / таблиц (хотя вы все равно можете создать свой собственный тип коллекции; для этого не нужно использовать объекты).

db <> fiddle

...