PL/SQL 使用 DUP_VAL_ON_INDEX 更新值
PL/SQL Update values using DUP_VAL_ON_INDEX
DECLARE
ins NUMBER := 0;
upd NUMBER := 0;
CURSOR c1 IS
SELECT cid
FROM tbl_cust
WHERE cid
IN ('1','2','3','4');
BEGIN
FOR rec IN c1 LOOP
INSERT INTO tbl2 (id_tbl2, name_tbl2)
VALUES(rec.cid, DECODE(rec.cid, '1', 'A',
'2', 'B',
'3', 'C',
'4', 'D'));
ins := ins + 1;
END LOOP;
dbms_output.put_line('Updated: ' || ins);
dbms_output.put_line('Inserted: ' || upd);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
FOR rec IN c1 LOOP
UPDATE tbl2 set name_tbl2 = DECODE(rec.cid, '1', 'A',
'2', 'B',
'3', 'C',
'4', 'D')
WHERE cust_cust_code = rec.cid;
upd := upd + 1;
END LOOP;
dbms_output.put_line('Updated: ' || upd);
dbms_output.put_line('Inserted: ' || ins);
END;
当 运行 第一次使用此代码并且 tbl_cust 中没有与 tbl2 相同的值时,输出如下所示:
更新:0
已插入:4
这是正确的。在第二个 运行 中,输出是这样的:
更新:4
已插入:0
这也是正确的。
但是当我从数据库中删除例如 cid '1' 和 '3' 时,结果是这样的:
更新:4
已插入:2
删除cid'1'和'3'后我想要的输出是这样的:
更新:2
已插入:2
更新时无需遍历所有记录。
这就是为什么您获得 4 个更新而不是预期的 2 个更新。
相反,您应该只在 DUP_VAL_ON_INDEX
异常的情况下更新,并且只更新导致异常的行。
尝试这样的事情。
DECLARE
ins NUMBER := 0;
upd NUMBER := 0;
CURSOR c1 IS
SELECT cid
FROM tbl_cust
WHERE cid
IN ('1','2','3','4');
BEGIN
FOR rec IN c1 LOOP
begin
INSERT INTO tbl2 (id_tbl2, name_tbl2)
VALUES(rec.cid, DECODE(rec.cid, '1', 'A',
'2', 'B',
'3', 'C',
'4', 'D'));
ins := ins + 1;
EXCEPTION WHEN DUP_VAL_ON_INDEX THEN
UPDATE tbl2 set name_tbl2 = DECODE(rec.cid, '1', 'A',
'2', 'B',
'3', 'C',
'4', 'D'));
WHERE cust_cust_code = rec.cid;
upd := upd + 1;
continue;
end;
END LOOP;
dbms_output.put_line('Updated: ' || upd);
dbms_output.put_line('Inserted: ' || ins);
END;
你的逻辑有问题。当一次插入失败时,您的代码将进入异常处理程序,启动一个新游标并更新所有记录。程序在第一次失败后终止。所以你的第三个 运行 这样做:
Inserted: 1 -- insert where CID=1 succeeded
Updated: 4 -- insert where CID=2 failed
除了实际上只有三个 TBL2 记录被更新,因为你计算循环中的行数而不是更新了多少行。你应该做的是 upd := update + sql%count;
,它只会在实际更新行时增加计数器。
你想做的是:
DECLARE
ins NUMBER := 0;
upd NUMBER := 0;
CURSOR c1 IS
SELECT cid
FROM tbl_cust
WHERE cid
IN ('1','2','3','4');
BEGIN
FOR rec IN c1 LOOP
begin
INSERT INTO tbl2 (id_tbl2, name_tbl2)
VALUES(rec.cid, DECODE(rec.cid, '1', 'A',
'2', 'B',
'3', 'C',
'4', 'D'));
ins := ins + sql%rowcount;
exception
WHEN DUP_VAL_ON_INDEX THEN
UPDATE tbl2 set name_tbl2 = DECODE(rec.cid, '1', 'A',
'2', 'B',
'3', 'C',
'4', 'D')
WHERE id_tbl2 = rec.cid;
upd := upd + sql%rowcount;
end;
END LOOP;
dbms_output.put_line('Inserted: ' || ins);
dbms_output.put_line('Updated: ' || upd);
END;
/
但是您应该做的是使用 MERGE 来实现此逻辑:
merge into tbl2
using ( select * from tbl_cust ) q
on (q.cid = tbl2.id_tbl2)
when not matched then
insert (id_tbl2, name_tbl2)
values (q.cid, DECODE(q.cid, '1', 'A',
'2', 'B',
'3', 'C',
'4', 'D'))
when matched then
update
set tbl2.name_tbl2 = DECODE(q.cid, '1', 'W',
'2', 'X',
'3', 'Y',
'4', 'Z')
/
注意:WHEN MATCHED 分支使用不同的值以便于查看第三个 运行 中发生的情况。
DECLARE
ins NUMBER := 0;
upd NUMBER := 0;
CURSOR c1 IS
SELECT cid
FROM tbl_cust
WHERE cid
IN ('1','2','3','4');
BEGIN
FOR rec IN c1 LOOP
INSERT INTO tbl2 (id_tbl2, name_tbl2)
VALUES(rec.cid, DECODE(rec.cid, '1', 'A',
'2', 'B',
'3', 'C',
'4', 'D'));
ins := ins + 1;
END LOOP;
dbms_output.put_line('Updated: ' || ins);
dbms_output.put_line('Inserted: ' || upd);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
FOR rec IN c1 LOOP
UPDATE tbl2 set name_tbl2 = DECODE(rec.cid, '1', 'A',
'2', 'B',
'3', 'C',
'4', 'D')
WHERE cust_cust_code = rec.cid;
upd := upd + 1;
END LOOP;
dbms_output.put_line('Updated: ' || upd);
dbms_output.put_line('Inserted: ' || ins);
END;
当 运行 第一次使用此代码并且 tbl_cust 中没有与 tbl2 相同的值时,输出如下所示:
更新:0
已插入:4
这是正确的。在第二个 运行 中,输出是这样的:
更新:4
已插入:0
这也是正确的。 但是当我从数据库中删除例如 cid '1' 和 '3' 时,结果是这样的:
更新:4
已插入:2
删除cid'1'和'3'后我想要的输出是这样的:
更新:2
已插入:2
更新时无需遍历所有记录。 这就是为什么您获得 4 个更新而不是预期的 2 个更新。
相反,您应该只在 DUP_VAL_ON_INDEX
异常的情况下更新,并且只更新导致异常的行。
尝试这样的事情。
DECLARE
ins NUMBER := 0;
upd NUMBER := 0;
CURSOR c1 IS
SELECT cid
FROM tbl_cust
WHERE cid
IN ('1','2','3','4');
BEGIN
FOR rec IN c1 LOOP
begin
INSERT INTO tbl2 (id_tbl2, name_tbl2)
VALUES(rec.cid, DECODE(rec.cid, '1', 'A',
'2', 'B',
'3', 'C',
'4', 'D'));
ins := ins + 1;
EXCEPTION WHEN DUP_VAL_ON_INDEX THEN
UPDATE tbl2 set name_tbl2 = DECODE(rec.cid, '1', 'A',
'2', 'B',
'3', 'C',
'4', 'D'));
WHERE cust_cust_code = rec.cid;
upd := upd + 1;
continue;
end;
END LOOP;
dbms_output.put_line('Updated: ' || upd);
dbms_output.put_line('Inserted: ' || ins);
END;
你的逻辑有问题。当一次插入失败时,您的代码将进入异常处理程序,启动一个新游标并更新所有记录。程序在第一次失败后终止。所以你的第三个 运行 这样做:
Inserted: 1 -- insert where CID=1 succeeded
Updated: 4 -- insert where CID=2 failed
除了实际上只有三个 TBL2 记录被更新,因为你计算循环中的行数而不是更新了多少行。你应该做的是 upd := update + sql%count;
,它只会在实际更新行时增加计数器。
你想做的是:
DECLARE
ins NUMBER := 0;
upd NUMBER := 0;
CURSOR c1 IS
SELECT cid
FROM tbl_cust
WHERE cid
IN ('1','2','3','4');
BEGIN
FOR rec IN c1 LOOP
begin
INSERT INTO tbl2 (id_tbl2, name_tbl2)
VALUES(rec.cid, DECODE(rec.cid, '1', 'A',
'2', 'B',
'3', 'C',
'4', 'D'));
ins := ins + sql%rowcount;
exception
WHEN DUP_VAL_ON_INDEX THEN
UPDATE tbl2 set name_tbl2 = DECODE(rec.cid, '1', 'A',
'2', 'B',
'3', 'C',
'4', 'D')
WHERE id_tbl2 = rec.cid;
upd := upd + sql%rowcount;
end;
END LOOP;
dbms_output.put_line('Inserted: ' || ins);
dbms_output.put_line('Updated: ' || upd);
END;
/
但是您应该做的是使用 MERGE 来实现此逻辑:
merge into tbl2
using ( select * from tbl_cust ) q
on (q.cid = tbl2.id_tbl2)
when not matched then
insert (id_tbl2, name_tbl2)
values (q.cid, DECODE(q.cid, '1', 'A',
'2', 'B',
'3', 'C',
'4', 'D'))
when matched then
update
set tbl2.name_tbl2 = DECODE(q.cid, '1', 'W',
'2', 'X',
'3', 'Y',
'4', 'Z')
/
注意:WHEN MATCHED 分支使用不同的值以便于查看第三个 运行 中发生的情况。