PL/SQL Update values using DUP_VAL_ON_INDEX

痞子三分冷 提交于 2020-01-03 05:31:20

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!