Как получить элемент n из вложенной таблицы pl/sql?

У меня проблема с компиляцией моей хранимой процедуры.

create or replace type CartLine as object (
    offeringId  OfferingIdList
    ,productLine      varchar2(50)
    ,equipment        char(1)
    ,installment       CHAR(1)
    ,cartItemProcess             varchar2(50)
    ,minimalPrice    decimal
);

create or replace type CartLineType is table of CartLine;

create or replace PROCEDURE GetOfferingRecommendation (
    cartLineList IN CartLineType,
    user IN UserType, 
    customer IN CustomerType, 
    processContext IN ProcessContextType, 
    recommendation out SYS_REFCURSOR  )
IS
    prodLine VARCHAR2(20);
    prodPrice NUMBER(5,0);
BEGIN
    FOR i IN cartLineList.FIRST .. cartLineList.LAST
    LOOP
        SELECT productLine, minimalPrice 
        INTO prodLine, prodPrice  
        FROM TABLE(cartLineList(i));
        OPEN recommendation FOR 
             SELECT CAST(REKOM_ID_SEQ.NEXTVAL AS VARCHAR(10)) 
                  ||'_'||cp.ID_REKOM_OFERTA
                  ||'_'||TO_CHAR(SYSDATE, 'yyyymmdd')  AS recommendationId 
                ,cp.ID_REKOM_OFERTA AS offeringId
                ,cp.PRIORYTET AS priority
            FROM REKOM_CROSS_PROM cp
            WHERE cp.LINIA_PROD = prodLine
            AND prodPrice BETWEEN cp.CENA_MIN AND cp.CENA_MAX
            ;
    END LOOP;
END GetOfferingRecommendation;

Он не компилируется, потому что следующее утверждение неверно:

SELECT productLine, minimalPrice 
INTO prodLine, prodPrice
FROM TABLE(cartLineList(i));

Я хочу выбрать только одно значение для каждой новой итерации моего цикла. Может ли кто-нибудь помочь мне решить мою проблему?

-- РЕДАКТИРОВАТЬ 09.01.2018 16:26 В соответствии с темой: Как вернуть результат многих операторов select в виде одной пользовательской таблицы Я попытался перестроить свою процедуру. Я создал типы для теста:

create or replace TYPE tst AS OBJECT (
rekom_id varchar2(50)
,rekom_priorytet number(5,4)
);
/
create or replace TYPE tst_list IS TABLE OF tst;

После этого я изменил свою процедуру, как показано ниже:

CREATE OR REPLACE PROCEDURE GetOfferingRecommendation (cartLineList IN CartLineType, recommendation out SYS_REFCURSOR  )
IS
CURSOR CUR_TAB IS SELECT productLine, minimalPrice FROM TABLE(cartLineList);
v_tst tst_list;
BEGIN
FOR i IN CUR_TAB
LOOP
    EXECUTE IMMEDIATE 'SELECT tst_list(
         CAST(REKOM_ID_SEQ.NEXTVAL AS VARCHAR(10))||''_''||cp.ID_REKOM_OFERTA||''_''||TO_CHAR(SYSDATE, ''yyyymmdd'')
        ,cp.PRIORYTET)
        FROM REKOM_CROSS_PROM cp
        WHERE cp.LINIA_PROD ='||i.productLine||' AND '||i.minimalPrice||' BETWEEN cp.CENA_MIN AND cp.CENA_MAX'
        BULK COLLECT INTO v_tst;
    EXIT WHEN CUR_TAB%NOTFOUND;
    FOR REC IN 1 .. v_tst.COUNT
    LOOP
    PIPE ROW (v_tst(REC));
    END LOOP;
END LOOP;
OPEN recommendation FOR SELECT * FROM TABLE(v_tst);
END IF;
END GetOfferingRecommendation;

Но я не могу скомпилировать, потому что произошла ошибка: PLS-00629 Не могли бы вы сказать мне, что я делаю неправильно?


person anton1009    schedule 08.01.2018    source источник
comment
звучит так, как будто вы хотите сделать where cp.linia_prod = cartlinlist(i).productline вместо того, чтобы пытаться выбрать из массива переменную prodline и использовать ее в курсоре ref? Однако вам не имеет смысла создавать open ref-курсор для каждого элемента в массиве; возможно, вы хотите сделать что-то вроде where cp.linia_prod = (select productline from table(cartlinelist)?   -  person Boneist    schedule 08.01.2018
comment
Я не понимаю. Вы показываете только часть процедуры? Если это вся ваша процедура, что она должна делать? Он выбирает некоторые вещи и присваивает их переменным, а затем вообще ничего не делает с этими переменными. В чем смысл?   -  person mathguy    schedule 08.01.2018
comment
Вам нужно объяснить бизнес-логику, которую вы пытаетесь реализовать. Мы не можем реконструировать ваши намерения из какого-то шаткого кода. Во-первых, параметр OUT является скаляром, поэтому процедура вернет только последний экземпляр курсора ref, открытый в цикле. Предположительно, это не то, что вы хотите сделать.   -  person APC    schedule 09.01.2018
comment
Например, я получу вложенную таблицу cartLineList с 2 элементами. Первый элемент имеет productLine = 'prod1' и MinimumPrice = 10 Второй элемент: productLine = 'prod2' и MinimumPrice = 20 Я хочу вернуть курсор с 2 записями из таблицы REKOM_CROSS_PROM, где значения равны для элементов из параметра cartLineList. Хорошо, я знаю, что могу использовать следующий оператор: where cp.linia_prod = (выберите линейку продуктов из таблицы (cartlinelist) Но как сравнять несколько минимальных цен в предложении BETWEEN?   -  person anton1009    schedule 09.01.2018


Ответы (2)


Вы не можете назначать переменные с помощью оператора select из коллекции в цикле, как показано ниже.

SELECT productLine, MinimumPrice INTO prodLine, prodPrice
FROM TABLE(cartLineList(i));

На элементы коллекции нельзя ссылаться внутри оператора SELECT 1 на 1 с помощью цикла. Вы можете прокручивать коллекцию как

For i in 1..collection.count
loop
 ...
 ..
End loop;

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

CREATE OR REPLACE PROCEDURE GETOFFERINGRECOMMENDATION (
     CARTLINELIST     IN       CARTLINETYPE,
     RECOMMENDATION   OUT      SYS_REFCURSOR)
IS
     TYPE V_PRODLINE IS TABLE OF VARCHAR2 (20)
          INDEX BY PLS_INTEGER;

     TYPE V_PRODPRICE IS TABLE OF NUMBER (5, 0)
          INDEX BY PLS_INTEGER;

     PRODLINE                      V_PRODLINE;
     PRODPRICE                     V_PRODPRICE;
BEGIN
    --Putting the collection result to another collection
     SELECT PRODUCTLINE,
            MINIMALPRICE
     BULK COLLECT INTO PRODLINE,
             PRODPRICE
       FROM TABLE (CARTLINELIST);

     -- Assuming number of elements will be same in both prodLine, prodPrice colection, loop can be iterated as below
     FOR I IN 1 .. PRODLINE.LAST
     LOOP
          OPEN RECOMMENDATION FOR
               SELECT    CAST (REKOM_ID_SEQ.NEXTVAL AS VARCHAR (10) )
                      || '_'
                      || CP.ID_REKOM_OFERTA
                      || '_'
                      || TO_CHAR (SYSDATE, 'yyyymmdd') AS RECOMMENDATIONID,
                      CP.ID_REKOM_OFERTA AS OFFERINGID,
                      CP.PRIORYTET AS PRIORITY
                 FROM REKOM_CROSS_PROM CP
                WHERE CP.LINIA_PROD = PRODLINE (I)
                      AND PRODPRICE (I) BETWEEN CP.CENA_MIN AND CP.CENA_MAX;
     END LOOP;
END GETOFFERINGRECOMMENDATION;

ИЛИ согласно @krokodilko.. Вы можете сделать, как показано ниже:

CREATE OR REPLACE PROCEDURE GETOFFERINGRECOMMENDATION (
     CARTLINELIST     IN       CARTLINETYPE,
     RECOMMENDATION   OUT      SYS_REFCURSOR)
IS
     PRODLINE                      VARCHAR2 (20);
     PRODPRICE                     NUMBER (5, 0);
BEGIN
     FOR I IN 1 .. CARTLINELIST.LAST
     LOOP
          --Assign the values of the collection to the variable declared.
          PRODUCTLINE := CARTLINELIST (I).PRODUCTLINE;
          MINIMALPRICE := CARTLINELIST (I).MINIMALPRICE;

          OPEN RECOMMENDATION FOR
               SELECT    CAST (REKOM_ID_SEQ.NEXTVAL AS VARCHAR (10) )
                      || '_'
                      || CP.ID_REKOM_OFERTA
                      || '_'
                      || TO_CHAR (SYSDATE, 'yyyymmdd') AS RECOMMENDATIONID,
                      CP.ID_REKOM_OFERTA AS OFFERINGID,
                      CP.PRIORYTET AS PRIORITY
                 FROM REKOM_CROSS_PROM CP
                WHERE CP.LINIA_PROD = PRODLINE
                      AND PRODPRICE BETWEEN CP.CENA_MIN AND CP.CENA_MAX;
     END LOOP;
END GETOFFERINGRECOMMENDATION;

Демонстрация:

SQL> CREATE OR REPLACE TYPE CARTLINE AS OBJECT (
  2       PRODUCTLINE                   VARCHAR2 (50),
  3       MINIMALPRICE                  DECIMAL
  4  );
  5  /

Type created.

SQL> CREATE OR REPLACE TYPE CARTLINETYPE IS TABLE OF CARTLINE;
  2  /

Type created.

SQL> CREATE OR REPLACE PROCEDURE GETOFFERINGRECOMMENDATION (
  2       CARTLINELIST   IN   CARTLINETYPE)
  3  IS
  4       TYPE V_PRODLINE IS TABLE OF VARCHAR2 (20)
  5            INDEX BY PLS_INTEGER;
  6  
  7       TYPE V_PRODPRICE IS TABLE OF NUMBER (5, 0)
  8            INDEX BY PLS_INTEGER;
  9  
 10       PRODLINE                      V_PRODLINE;
 11       PRODPRICE                     V_PRODPRICE;
 12  BEGIN
 13       SELECT PRODUCTLINE,
 14              MINIMALPRICE
 15       BULK COLLECT INTO PRODLINE,
 16               PRODPRICE
 17         FROM TABLE (CARTLINELIST);
 18  
 19       FOR I IN 1 .. PRODLINE.COUNT
 20       LOOP
 21            DBMS_OUTPUT.PUT_LINE (   'Prod Line '
 22                                  || PRODLINE (I)
 23                                  || '  Prod Price '
 24                                  || PRODPRICE (I) );
 25       END LOOP;
 26  END GETOFFERINGRECOMMENDATION;
 27  /

Procedure created.

Выход:

SQL> DECLARE
  2       VAR                           CARTLINETYPE := CARTLINETYPE ();
  3  BEGIN
  4       --Popuating the collection
  5       VAR.EXTEND (2);
  6       VAR (1) := CARTLINE ('TypeA', 6.0);
  7       VAR (2) := CARTLINE ('TypeB', 7.1);
  8    
  9    --Calling the procedure
 10       GETOFFERINGRECOMMENDATION (CARTLINELIST        => VAR);
 11  END;
 12  /
Prod Line TypeA  Prod Price 6
Prod Line TypeB  Prod Price 7

PL/SQL procedure successfully completed.

SQL> 
person XING    schedule 09.01.2018
comment
Спасибо за отличное объяснение! - person anton1009; 09.01.2018
comment
К сожалению, это не решает мою проблему. Как писал @APC, курсор вернет только последний экземпляр курсора, открытый в цикле, поэтому это будет только одна запись, а не 2:/ - person anton1009; 09.01.2018
comment
@anton1009 Какой курсор. Я показал вам в примере, возвращаются 2 записи. Вы точно следовали тому, что я показал. - person XING; 09.01.2018
comment
Я понимаю ваш пример, но когда я меняю метод DBMS_OUTPUT.PUT_LINE на предложение OPEN CURSOR FOR SELECT (как пример крокодилко), курсор возвращает только одну запись. Мне нужно использовать все элементы вложенной таблицы в предложении SELECT, чтобы вернуть курсор с несколькими записями из таблицы REKOM_CROSS_PROM. - person anton1009; 09.01.2018
comment
@антон1009. Для вашего случая я уже ответил на такой вопрос. Вы можете проверить это. Это использует piperow для получения данных коллекции. stackoverflow.com/questions/48141105/ - person XING; 09.01.2018
comment
будет ли это работать с SYS_REFCURSOR или только с PIPELINED TABLE? - person anton1009; 09.01.2018
comment
@anton1009 anton1009 Вы можете создать параметр OUT. В противном случае вы можете сначала заполнить коллекцию с помощью piperow, а затем открыть SYS_REFCURSOR для коллекции. - person XING; 09.01.2018
comment
Веб-сервис, который будет вызывать процедуру, ожидает в ответ SYS_REFCURSOR, поэтому я попытаюсь построить решение, используя второй вариант. Спасибо за ваши ответы. - person anton1009; 09.01.2018
comment
Я отредактировал свой первый пост. Не могли бы вы взглянуть на это? Я попытался реализовать вашу вторую идею, но возникает ошибка :/ - person anton1009; 09.01.2018
comment
@anton1009 anton1009 Вы не перешли по моей ссылке stackoverflow.com/questions/48141105/ Если вы видите, что я создаю конвейерную функцию, а не процедуру. Конвейерная обработка работает с функцией, а не с процедурой. Вы шаг за шагом красите мою ссылку, и я уверен, что вы сможете реализовать, - person XING; 10.01.2018

Используйте простые присваивания вместо SELECT ... FROM TABLE(cartLineList(i));:

LOOP
/* SELECT productLine, minimalPrice INTO prodLine, prodPrice FROM TABLE(cartLineList(i)); */
   productLine := cartLineList(i).productLine;
   minimalPrice := cartLineList(i).minimalPrice;
   .....
   .....
END LOOP;
person krokodilko    schedule 08.01.2018