Как вернуть вложенную таблицу в функцию? - PullRequest
1 голос
/ 25 апреля 2020

I Перенос данных во вложенную таблицу V_EMP с помощью Bulk Collect. Между началом и концом, как я могу вызвать функцию?

DECLARE

    TYPE T_REC IS RECORD
    (
    T_TITLE VARCHAR2,
    T_YEAR NUMBER(2,1)
    );

    TYPE T_EMP IS TABLE OF T_REC%TYPE;
    V_EMP T_EMP;
    Z_EMP T_EMP;
    V_EMP_ID NUMBER := 101;

    FUNCTION HIST(V_EMP_ID EMPLOYEES.EMPLOYEE_ID%TYPE)
        RETURN V_EMP;

    BEGIN

        SELECT JOB_TITLE T_TITLE, ROUND((END_DATE - START_DATE) / 365,1) T_YEAR
        BULK COLLECT INTO V_EMP
        FROM JOB_HISTORY INNER JOIN JOBS USING(JOB_ID)
        WHERE EMPLOYEE_ID = V_EMP_ID
        ORDER BY START_DATE;

        RETURN V_EMP;
    END HIST;

BEGIN

    Z_EMP := HIST(V_EMP_ID);

    FOR C IN (SELECT T_TITLE, T_YEAR FROM Z_EMP)
    LOOP
        DBMS_OUTPUT.PUT_LINE(C.T_TITLE, C.T_YEAR);
    END LOOP;

END;

1 Ответ

1 голос
/ 25 апреля 2020

Во-первых, ваши типы не совсем правильно объявлены.

В вашем типе T_REC необходимо указать размер столбца VARCHAR2. Я использовал 100 в качестве примера здесь:

    TYPE T_REC IS RECORD
    (
    T_TITLE VARCHAR2(100),
    T_YEAR NUMBER(2,1)
    );

Во-вторых, строка

    TYPE T_EMP IS TABLE OF T_REC%TYPE;

неверна: T_REC сам по себе является типом, поэтому вы не указываете атрибут %TYPE для него. Вместо этого попробуйте следующее:

    TYPE T_EMP IS TABLE OF T_REC;

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

    FUNCTION HIST(V_EMP_ID EMPLOYEES.EMPLOYEE_ID%TYPE)
        RETURN V_EMP;

    BEGIN
        -- ...

Предложение RETURN требует использования типа, но V_EMP является локальной переменной. Кроме того, вместо завершения объявления точкой с запятой необходимо включить ключевое слово IS, чтобы сообщить компилятору PL / SQL, что следующий блок формирует тело функции. Объединяя эти изменения, мы имеем:

    FUNCTION HIST(V_EMP_ID EMPLOYEES.EMPLOYEE_ID%TYPE)
        RETURN T_EMP
    IS
    BEGIN
        -- ...

После исправления этих проблем с объявлениями мы можем посмотреть на блок BEGIN внизу. Первая проблема заключается в том, что вы не можете написать SELECT T_TITLE, T_YEAR FROM Z_EMP для запроса из переменной, которая содержит вложенную таблицу. Вместо этого вы должны обернуть его в вызов TABLE, то есть SELECT T_TITLE, T_YEAR FROM TABLE(Z_EMP).

Однако это не сработает. Вы получите ошибку PLS-00642: local collection types not allowed in SQL statements, если попытаетесь. Это потому, что вы не можете выполнить запрос SQL для типов, объявленных только в блоке PL / SQL. Вместо этого вы можете l oop над значениями в возвращенной коллекции, используя следующее:

    IF Z_EMP.COUNT = 0 THEN
        DBMS_OUTPUT.PUT_LINE('There are no records');
    ELSE
        FOR i IN Z_EMP.FIRST .. Z_EMP.LAST
        LOOP
            DBMS_OUTPUT.PUT_LINE(Z_EMP(i).T_TITLE || ', ' || Z_EMP(i).T_YEAR);
        END LOOP;
    END IF;

Обратите внимание, что в этом случае нам нужно проверить, что коллекция не имеет записей: если коллекция пуста, Z_EMP.FIRST и Z_EMP.LAST будут NULL, и вы получите PL/SQL: numeric or value error, пытаясь использовать их в диапазоне FOR l oop. Также обратите внимание, что DBMS_OUTPUT.PUT_LINE принимает только один аргумент: чтобы избежать ошибки, я объединил два значения вместе с запятой между ними.

В качестве альтернативы, если вы действительно хотите использовать запрос SQL для чтения Значения, возвращаемые вашей функцией, у вас немного больше работы. Вам нужно будет объявить типы T_REC и T_EMP вне вашего блока PL / SQL следующим образом:

CREATE TYPE T_REC IS OBJECT
(
    T_TITLE VARCHAR2(100 CHAR),
    T_YEAR NUMBER(2,1)
);
/

CREATE TYPE T_EMP IS TABLE OF T_REC;
/

Затем вы удалите объявление этих типов в своем блоке. Вам также необходимо настроить запрос внутри вашей функции: вместо выбора

        SELECT JOB_TITLE T_TITLE, ROUND((END_DATE - START_DATE) / 365,1) T_YEAR

и сопоставления этих полей с записями вам придется явно создать объект T_REC из каждой выбранной строки:

        SELECT T_REC(JOB_TITLE, ROUND((END_DATE - START_DATE) / 365,1))

Как только вы это сделаете, l oop внизу можно изменить на следующее:

        FOR C IN (SELECT T_TITLE, T_YEAR FROM TABLE(Z_EMP))
        LOOP
            DBMS_OUTPUT.PUT_LINE(C.T_TITLE || ', ' || C.T_YEAR);
        END LOOP;

Вы также можете избавиться от чека Z_EMP.COUNT = 0, если вы хочу: вышеприведенный l oop не сообщит об ошибке, если Z_EMP пуст, хотя он не будет генерировать никакого вывода.

...