问题
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;
when running this code for the first time and there are no values from tbl_cust that is the same as tbl2, the output is like this:
Updated: 0
Inserted: 4
which is correct. In the 2nd run the output is like this:
Updated: 4
Inserted: 0
which is also correct. But when i delete for example cid '1' and '3' from the database, the result is like this:
Updated: 4
Inserted: 2
The output that I wanted is like this after deleting cid '1' and '3':
Updated: 2
Inserted: 2
回答1:
There is no need to loop through all the records in case of the update. That's why you are getting 4 updates instead of expected 2.
Instead, you should update only in the case of the DUP_VAL_ON_INDEX
exception and only the row that caused the exception.
Try something like this.
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;
回答2:
There is a flaw in your logic. When one insertion fails your code drops into the exception handler, starts a new cursor and updates all the records. The program then terminates after the first failure. So your third run does this:
Inserted: 1 -- insert where CID=1 succeeded
Updated: 4 -- insert where CID=2 failed
Except that only three TBL2 records were actually updated because you count the rows in the loop not how many rows were updated. what you should have done was upd := update + sql%count;
, which only increments the counter when rows are actually updated.
What you wanted to do was this:
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;
/
But what you should do is use a MERGE to implement this logic:
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')
/
Note: the WHEN MATCHED branch uses different values to make it easy to see what happens in your third run.
来源:https://stackoverflow.com/questions/48677925/pl-sql-update-values-using-dup-val-on-index