ORACLE更新rownum选择行的行范围

时间:2018-02-21 16:42:17

标签: sql oracle

我需要用这句话更新一堆行:

UPDATE "STATISTICS" SET MEDIA = (CASE WHEN TB_STATISTICS.TYPE > 3 THEN 2 ELSE 1 END);

然而,它因ORA-30036而失败,因为UNDOTBS1没有足够的空间。我没有数据库,也无法增加用于撤消的表空间。

该表有大约3000万行,如果我每次只更新1000万行,它可以正常工作:

UPDATE "STATISTICS" SET MEDIA = (CASE WHEN TB_STATISTICS.TYPE > 3 THEN 2 ELSE 1 END) where rownum < 10000000;

但是,我无法让它更新下一千万行。我试过这个:

UPDATE "STATISTICS" SET MEDIA = (CASE WHEN TB_STATISTICS.TYPE > 3 THEN 2 ELSE 1 END) where rownum > 10000000 and rownum < 20000000;

但它总是说“0行更新”。

我已经看到,在执行SELECT时,可以通过将rownum设置为所选列之一并为其分配和别名来实现。但我不知道如何在UPDATE句子中做到这一点。

THX。

2 个答案:

答案 0 :(得分:2)

使用ROWNUM并不总是这种操作的可靠方法。如果您有CURSOR LOOP列,则可以使用这样的隐式UNIQUE or PRIMARY KEY。在这里,我每100000行执行一次COMMIT。您可以相应地修改它

DECLARE
  cnt INTEGER := 0;
BEGIN
  FOR rec_cur IN
  (
         SELECT unique_key_col,
                  CASE
                       WHEN tb_statistics.TYPE > 3 THEN 2
                       ELSE 1
                  END AS MEDIA
         FROM   "STATISTICS" )
  LOOP
    UPDATE "STATISTICS" s
    SET    s.media = rec_cur.media
    WHERE  s.unique_key_col = rec_cur.unique_key_col;

    IF cnt = 100000 THEN
      COMMIT;
      cnt := 0;
    ELSE
      cnt := cnt + 1;
    END IF;
  END LOOP;
  COMMIT;
END;
/

答案 1 :(得分:2)

您目前的情况是矛盾的,因为价值不能同时低于1000万和超过2000万。但即使你扭转了它,它仍然不会像你期望的那样工作,因为它的方式 - 以及何时 - rownum被设定。 From the documentation

  

大于正整数的ROWNUM值的测试条件始终为false。例如,此查询不返回任何行:

SELECT *
  FROM employees
 WHERE ROWNUM > 1;
     

获取的第一行被赋予ROWNUM 1,并使条件为假。要获取的第二行现在是第一行,并且还指定了ROWNUM为1并使条件为false。所有行随后都无法满足条件,因此不返回任何行。

使用rownum这不是理想的选择,但如果您将语句更改为:

UPDATE "STATISTICS" SET MEDIA = (CASE WHEN TB_STATISTICS.TYPE > 3 THEN 2 ELSE 1 END)
where (MEDIA IS NULL OR  MEDIA != (CASE WHEN TB_STATISTICS.TYPE > 3 THEN 2 ELSE 1 END))
and rownum < 10000000 ;

然后每次运行它时,它将排除您已经更新的行,或者(作为奖励)已经具有正确值的行,因为它们将失败where条件;但它允许该值最初也为空。

您也可以在PL / SQL块中执行此操作,最好使用批量查询和更新,也许这样的事情是您有唯一或主键字段:

declare
  type t_tab_type is table of statistics%rowtype;
  l_tab t_tab_type;
  cursor c is select * from statistics;
begin
  open c;
  loop
  fetch c bulk collect into l_tab limit 10000000;
    forall i in 1..l_tab.count
      update statistics
      set media = case when l_tab(i).tb_statistics.type > 3 then 2 else 1 end
      where unique_key = l_tab(i).unique_key;
    commit;
    exit when c%notfound;
  end loop;
  close c;
end;
/

但可能是一个小于一千万的限制(因此更频繁的提交,除非你跟踪一个单独的变量来控制它),因为你不想为集合使用太多的内存。您还可以通过定义记录类型并使用它来代替%rowtype来减少使用的内存,可能只使用唯一键,并将select *更改为仅匹配记录类型的特定列。如果只得到一列,则为varray。

(也不确定tb_statistics.type是否是对象类型,或者更可能是实际的表名和列名,并且错过了修改发布语句的内容。如果它是同一个表中的列,并且此计算将始终与添加新行相同,然后media可能是虚拟列。)

最好有足够的撤销空间来在一次交易中执行操作,以提高效率和重启性;但似乎这是一次性的,所以也许不值得改变,甚至是一个临时的大型撤销表空间。