带有参数化游标的嵌套 for 循环正在插入重复记录
Nested for loop with parameterized cursors is inserting duplicate records
我们正在使用 2 个参数化游标,其中第一个游标的值需要在第二个游标中相应地获取数据,但它是从两个游标中获取值。相反,它应该只从第二个游标获取。
这里有2个游标。它需要从第一个游标获取属性 1 的值并将其作为参数化游标传递给 cursor2。但是在插入时,它同时插入了第一个和第二个游标值。
代码段中给出了实际结果和预期结果。
/* 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
问题是您的第一个游标 returns 两行,对于 END_ORD_NO
,这两行的值都是 11。第二个游标对第一个游标返回的两行中的每一行执行,第二个游标每次执行 returns 两行,其中的值被适当地插入 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)
这样做的好处是不需要使用任何循环就可以完成整个任务,而且速度更快,因为整个操作都是由数据库执行的,而不必减慢与外部过程的接口。
编辑
如果您必须同时使用两个游标,也许您可以使用 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;
我们正在使用 2 个参数化游标,其中第一个游标的值需要在第二个游标中相应地获取数据,但它是从两个游标中获取值。相反,它应该只从第二个游标获取。
这里有2个游标。它需要从第一个游标获取属性 1 的值并将其作为参数化游标传递给 cursor2。但是在插入时,它同时插入了第一个和第二个游标值。
代码段中给出了实际结果和预期结果。
/* 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
问题是您的第一个游标 returns 两行,对于 END_ORD_NO
,这两行的值都是 11。第二个游标对第一个游标返回的两行中的每一行执行,第二个游标每次执行 returns 两行,其中的值被适当地插入 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)
这样做的好处是不需要使用任何循环就可以完成整个任务,而且速度更快,因为整个操作都是由数据库执行的,而不必减慢与外部过程的接口。
编辑
如果您必须同时使用两个游标,也许您可以使用 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;