Вложенный для цикла с параметризованными курсорами вставляет повторяющиеся записи - PullRequest
0 голосов
/ 02 июня 2019

Мы используем 2 параметризованных курсора, где значение 1-го курсора необходимо во втором курсоре, чтобы получить соответствующие данные, но он выбирает значения из обоих курсоров. Скорее, он должен извлекаться только из 2-го курсора.

Здесь есть 2 курсора. От 1-го курсора ему нужно извлечь значение для атрибута 1 и передать его к cursor2 как параметризованный курсор. Но при вставке он вставляет как 1-й, так и 2-й значения курсора.

Фактические и ожидаемые результаты приведены во фрагменте кода.

/* Table Creation script:*/       
    create table tab1 (order_no number,order_item varchar2(40),header_id number)        
    /        
    create table tab2 (header_id number,line_id number,attribute1 number)        
    /        
    create table final_tab(order_no number, order_item varchar2(40), line_id number)        
    /

    /* Insertion script:*/         
    insert into tab1 values (1,'ABC',12345)
    /
    insert into tab1 values (11,'DEF',34567)
    /
    insert into tab2 values (12345,56789,11)
    /
    insert into tab2 values (12345,23489,11)
    /
    insert into tab2 values (34567,32156,null)
    /
    insert into tab2 values (34567,12534,null)
    /
    commit
    /

    /* Anonymous Block: */
    DECLARE
        CURSOR c1
        IS
            SELECT a.order_no,
                   a.order_item,
                   b.attribute1 end_ord_no,
                   a.header_id,
                   b.line_id
              FROM tab1 a, tab2 b
             WHERE a.header_id = b.header_id AND a.order_no = 1;
        CURSOR c2 (i_ord_no NUMBER)
        IS
            SELECT a.order_no,
                   a.order_item,
                   a.header_id,
                   b.line_id
             FROM tab1 a, tab2 b
             WHERE a.header_id = b.header_id AND a.order_no = i_ord_no;
    BEGIN
        FOR c1_rec IN c1
        LOOP
            FOR c2_rec IN c2 (c1_rec.end_ord_no)
            LOOP
                INSERT INTO final_tab (order_no, order_item, line_id)
                     VALUES (c2_rec.order_no, c2_rec.order_item, c2_rec.line_id);
            END LOOP;
        END LOOP;
        COMMIT;
    END;

/* Actual Result:*/

    Order_NO | Order_Item | Line_id
    11              |  DEF           | 32156
    11              |  DEF           | 12534
    11              |  DEF           | 32156
    11              |  DEF           | 12534

/*Expected Result:*/ 

    Order_NO | Order_Item | Line_id
    11              |  DEF           | 32156
    11              |  DEF           | 12534

1 Ответ

3 голосов
/ 02 июня 2019

Проблема в том, что ваш первый курсор возвращает две строки, каждая из которых имеет значение 11 для END_ORD_NO. Второй курсор выполняется для каждой из двух строк, возвращаемых первым курсором, и каждое выполнение второго курсора возвращает две строки, значения из которых должным образом вставляются в FINAL_TAB.

Чтобы исправить это, вы должны объединить два курсора в один курсор:

SELECT a.order_no,
       a.order_item,
       a.header_id,
       b.line_id
  FROM tab1 a, tab2 b
  WHERE a.header_id = b.header_id AND
        a.order_no IN (SELECT b.attribute1
                         FROM tab1 a, tab2 b
                         WHERE a.header_id = b.header_id AND
                               a.order_no = 1)

, который уменьшает ваш кодовый блок до

DECLARE
  CURSOR cc IS
    SELECT a.order_no,
           a.order_item,
           a.header_id,
           b.line_id
      FROM tab1 a, tab2 b
      WHERE a.header_id = b.header_id AND
            a.order_no IN (SELECT b.attribute1
                             FROM tab1 a, tab2 b
                             WHERE a.header_id = b.header_id AND
                                   a.order_no = 1);
BEGIN
  FOR rec IN cc LOOP
    INSERT INTO final_tab (order_no, order_item, line_id)
      VALUES (rec.order_no, rec.order_item, rec.line_id);
  END LOOP;

  COMMIT;
END;

Это не только обеспечит требуемые результаты, но также уменьшит вероятность ошибок за счет упрощения кода и сократит время выполнения за счет исключения вложенных циклов.

Однако даже это можно еще более упростить до

INSERT INTO final_tab (order_no, order_item, line_id)
  SELECT a.order_no,
         a.order_item,
         b.line_id
    FROM tab1 a, tab2 b
    WHERE a.header_id = b.header_id AND
          a.order_no IN (SELECT b.attribute1
                           FROM tab1 a, tab2 b
                           WHERE a.header_id = b.header_id AND
                                 a.order_no = 1)

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

dbfiddle здесь

Удачи.


EDIT

Если вы должны использовать оба курсора, возможно, вы могли бы использовать оператор MERGE следующим образом:

DECLARE
    CURSOR c1
    IS
        SELECT a.order_no,
               a.order_item,
               b.attribute1 end_ord_no,
               a.header_id,
               b.line_id
          FROM tab1 a, tab2 b
         WHERE a.header_id = b.header_id AND a.order_no = 1;
    CURSOR c2 (i_ord_no NUMBER)
    IS
        SELECT a.order_no,
               a.order_item,
               a.header_id,
               b.line_id
         FROM tab1 a, tab2 b
         WHERE a.header_id = b.header_id AND a.order_no = i_ord_no;
BEGIN
  FOR c1_rec IN c1 LOOP
    FOR c2_rec IN c2 (c1_rec.end_ord_no) LOOP
      MERGE INTO FINAL_TAB ft
        USING (SELECT c2_rec.order_no AS ORDER_NO,
                      c2_rec.order_item AS ORDER_ITEM,
                      c2_rec.line_id AS LINE_ID
                 FROM DUAL) d
          ON (ft.ORDER_NO = d.ORDER_NO AND
              ft.ORDER_ITEM = d.ORDER_ITEM AND
              ft.LINE_ID = d.LINE_ID)
        WHEN NOT MATCHED THEN
          INSERT (order_no, order_item, line_id)
          VALUES (d.order_no, d.order_item, d.line_id);
    END LOOP;  -- c2_rec
  END LOOP;  -- c1_rec

  COMMIT;
END;
...