【问题标题】:PL/SQL Update values using DUP_VAL_ON_INDEXPL/SQL 使用 DUP_VAL_ON_INDEX 更新值
【发布时间】:2018-07-18 13:50:25
【问题描述】:
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

【问题讨论】:

    标签: plsql oracle11g plsqldeveloper


    【解决方案1】:

    更新时无需遍历所有记录。 这就是为什么您会收到 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;
    

    【讨论】:

      【解决方案2】:

      你的逻辑有缺陷。当一次插入失败时,您的代码会进入异常处理程序,启动一个新游标并更新所有记录。然后程序在第一次失败后终止。所以你的第三次运行是这样的:

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

      【讨论】:

        猜你喜欢
        • 2018-12-17
        • 2018-10-05
        • 1970-01-01
        • 1970-01-01
        • 2015-10-01
        • 2021-08-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多