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 分支使用不同的值以便于查看第三个 运行 中发生的情况。