rownum不适用于包含更新查询的PL / SQL中的更新

时间:2019-02-14 05:09:26

标签: oracle plsql

我总共有193267条记录需要更新,并且需要在10,000条记录中进行一次更新,但是更新并没有占用rownum值

它没有批量处理10,000个号码。

set serveroutput ON
DECLARE
    ROWNUM NUMBER := 0;
BEGIN
    LOOP
        UPDATE billing.account_country
        SET    contract_type_id = NULL
        WHERE  ROWNUM <= 10000
               AND mdate < SYSDATE - 300
               AND mdate >= SYSDATE - 500
               AND id IS NOT NULL
               AND id IN ( 209 )
               AND contract_type_id < 1000;

        ROWNUM := SQL%rowcount;

        dbms_output.Put_line('row num:'
                             ||ROWNUM);

        IF ( ROWNUM = 0 ) THEN
          EXIT;
        END IF;
    END LOOP;

    dbms_output.Put_line('done..');
END; 

结果如下:

row num:193267
row num:0
done..

3 个答案:

答案 0 :(得分:0)

我在您的代码中没有看到问题-对我有用:

DECLARE
    i           NUMBER;

    sqlCreate   VARCHAR2 (4000)
                    := 'CREATE TABLE MYTABLE (mycolumn VARCHAR2(100))';

    sqlDrop     VARCHAR2 (4000) := 'DROP TABLE MYTABLE';
BEGIN
    EXECUTE IMMEDIATE sqlCreate;

    FOR x IN 1 .. 10
    LOOP
        EXECUTE IMMEDIATE 'insert into MYTABLE values(' || x || ')';
    END LOOP;

    LOOP
        EXECUTE IMMEDIATE 'UPDATE MYTABLE r
           SET r.mycolumn = r.mycolumn || ''x''
         WHERE ROWNUM <= 4
           AND not r.mycolumn like ''%x%''';

        i := SQL%ROWCOUNT;
        DBMS_OUTPUT.put_line ('i: ' || i);

        IF (i = 0)
        THEN
            EXIT;
        END IF;
    END LOOP;

    EXECUTE IMMEDIATE sqlDrop;
END;

输出:

i: 4
i: 4
i: 2
i: 0

也许

`AND contract_type_id < 1000;`

..还不够。不应该有

AND NOT contract_type_id is null;

答案 1 :(得分:0)

如果您的要求是每次提交的提交不超过10,000条记录(但是仍允许每次提交的提交少于10,000条记录),那么您很幸运。

您不需要管理自己的提交大小,也不需要考虑排队。
Oracle具有用于此类事情的内置实用程序。

我将在下面添加一个自己处理10K块的示例,但是仅使用Oracle的内置函数将节省时间并避免错误。

下面是使用 DBMS_PARALLEL_EXECUTE 的示例(如果您实际上不希望进行太多并行化,则可以将 parallel degree 设置为1)。

此处值得注意的部分是限制提交大小的CHUNK_SIZE => 10000,限制平行的PARALLEL_LEVEL => 1ROWID BETWEEN :STARD_ID AND :END_ID(还删除了ROWNUM,但是其余的原始查询(包括ID IS NOT NULLID IN元素)保持不变

BEGIN
  DBMS_PARALLEL_EXECUTE.CREATE_TASK(TASK_NAME => 'NULL_CONTRACT_TYPE');

  DBMS_PARALLEL_EXECUTE.CREATE_CHUNKS_BY_ROWID(
    TASK_NAME => 'NULL_CONTRACT_TYPE' ,
    TABLE_OWNER => 'BILLING',
    TABLE_NAME => 'ACCOUNT_COUNTRY', 
    BY_ROW => TRUE, 
    CHUNK_SIZE => 10000);

  DBMS_PARALLEL_EXECUTE.RUN_TASK(
    TASK_NAME => 'NULL_CONTRACT_TYPE' ,
    SQL_STMT => 'UPDATE account_country SET contract_type_id = NULL ' ||
                'WHERE  mdate < SYSDATE - 300 ' ||
                'AND mdate >= SYSDATE - 500 ' ||
                'AND id IS NOT NULL AND id IN ( 209 ) ' ||
                'AND contract_type_id < 1000 ' ||
                'AND ROWID BETWEEN :START_ID AND :END_ID',
    LANGUAGE_FLAG => DBMS_SQL.NATIVE, 
    PARALLEL_LEVEL => 1);

  DBMS_PARALLEL_EXECUTE.DROP_TASK(TASK_NAME => 'NULL_CONTRACT_TYPE');

END;
/

然后Oracle将为您完成其余工作-提交您指定的块,仅更新目标记录,等等。

但是,如果您仍然想自己做,一种方法是从游标中获取并限制目标行的数量。下面也是一个示例(和以前一样,我保留了原始查询的原样)。从您的原始查询来看,ID似乎不是唯一的,因此在此示例中,我使用的是ROWID

DECLARE
  TYPE ROWID_LIST IS TABLE OF ROWID;
  V_UPDATE_TARGETS ROWID_LIST := ROWID_LIST();
  CURSOR UPDATE_ACCOUNT_COUNTRY_TARGETS IS (
    SELECT ROWID FROM ACCOUNT_COUNTRY
    WHERE  mdate < SYSDATE - 300
       AND mdate >= SYSDATE - 500
       AND id IS NOT NULL AND id IN ( 209 )
       AND contract_type_id < 1000);
BEGIN
OPEN UPDATE_ACCOUNT_COUNTRY_TARGETS;
LOOP
  EXIT WHEN UPDATE_ACCOUNT_COUNTRY_TARGETS%NOTFOUND;
  FETCH UPDATE_ACCOUNT_COUNTRY_TARGETS BULK COLLECT INTO V_UPDATE_TARGETS LIMIT 10000;
  FORALL ROWID_INDEX IN 1..V_UPDATE_TARGETS.COUNT
    UPDATE ACCOUNT_COUNTRY
    SET CONTRACT_TYPE_ID = NULL
    WHERE ROWID = V_UPDATE_TARGETS(ROWID_INDEX);
  COMMIT;
END LOOP;
CLOSE UPDATE_ACCOUNT_COUNTRY_TARGETS;
END;
/

答案 2 :(得分:0)

代码的基本问题是您创建了一个名为ROWNUM的变量,并最初将其赋值为0。因此,当您第一次运行查询时,它将始终通过

的条件

WHERE ROWNUM <= 10000

,您的查询将根据其他条件更新所有符合条件的记录。 因此,在当前情况下,由于您的记录超过10k,因此查询将仅运行一次。 如果记录少于1万条,则您的程序将进入 INFINITE循环

您唯一需要做的更改就是从代码中删除以下 ROWNUM 声明,您的代码将按预期工作。

ROWNUM NUMBER := 0;

然后该程序将使用 Oracle的内置ROWNUM 子句。