【问题标题】:For loop update better alternativeFor循环更新更好的选择
【发布时间】:2014-01-30 18:26:28
【问题描述】:

在 Oracle 11g 中,我在过程中使用了以下内容。有人可以提供更好的解决方案来实现相同的结果。

FOR REC IN 
(SELECT E.EMP FROM EMPLOYEE E 
JOIN 
COMPANY C ON E.EMP=C.EMP
WHERE C.FLAG='Y')
 LOOP
 UPDATE EMPLOYEE SET FLAG='Y' WHERE EMP=REC.EMP;
 END LOOP;

有没有更有效/更好的方法来做到这一点?我觉得这个方法会为每条找到的记录运行一个更新语句(如果我错了,请纠正我)。

这是完整的实际代码:

create or replace 
PROCEDURE ACTION_MSC AS 
BEGIN
  -- ALL MIGRATED CONTACTS, CANDIDATES, COMPANIES, JOBS
  -- ALL MIGRATED CANDIDATES, CONTACTS
  FOR REC IN (SELECT DISTINCT AC.PEOPLE_HEX 
    FROM ACTION AC JOIN PEOPLE P ON AC.PEOPLE_HEX=P.PEOPLE_HEX 
    WHERE P.TO_MIGRATE='Y')
  LOOP
    UPDATE ACTION SET TO_MIGRATE='Y' WHERE PEOPLE_HEX=REC.PEOPLE_HEX;
  END LOOP;

  -- ALL MIGRATED COMPANIES
  FOR REC IN (SELECT DISTINCT AC.COMPANY_HEX 
    FROM ACTION AC JOIN COMPANY CM ON AC.COMPANY_HEX=CM.COMPANY_HEX
    WHERE CM.TO_MIGRATE='Y')
  LOOP
    UPDATE ACTION SET TO_MIGRATE='Y' WHERE COMPANY_HEX=REC.COMPANY_HEX;
  END LOOP;

  -- ALL MIGRATED JOBS
  FOR REC IN (SELECT DISTINCT AC.JOB_HEX 
    FROM ACTION AC JOIN "JOB" J ON AC.JOB_HEX=J.JOB_HEX
    WHERE J.TO_MIGRATE='Y')
  LOOP
    UPDATE ACTION SET TO_MIGRATE='Y' WHERE JOB_HEX=REC.JOB_HEX;
  END LOOP;

  COMMIT;
END ACTION_MSC;

【问题讨论】:

  • 请显示完整的触发代码。
  • 第一个建议:避免使用触发器。改为以编程方式执行任务。

标签: sql oracle loops join for-loop


【解决方案1】:

你是对的,它会对找到的每条记录进行一次更新。看起来你可以这样做:

UPDATE EMPLOYEE SET FLAG = 'Y'
WHERE EMP IN (SELECT EMP FROM COMPANY WHERE FLAG = 'Y')
AND FLAG != 'Y';

单个更新通常比循环中的多个单独行更新更快、更有效;另一个例子见this answer。除此之外,您正在减少 PL/SQL 和 SQL 之间的上下文切换次数,如果您有很多行,这些切换次数会加起来。当然,您始终可以使用自己的数据对此进行基准测试。

我添加了对当前标志状态的检查,这样您就不会毫无意义地进行毫无意义的更新。


比较这些方法很容易看出单次更新比循环更新快;带有一些人为的数据:

create table people (id number, people_hex varchar2(16), to_migrate varchar2(1));
insert into people (id, people_hex, to_migrate)
select level, to_char(level - 1, 'xx'), 'Y'
from dual
connect by level <= 100;

create table action (id number, people_hex varchar2(16), to_migrate varchar2(1));
insert into action (id, people_hex, to_migrate)
select level, to_char(mod(level, 200), 'xx'), 'N'
from dual
connect by level <= 500000;

所有这些都会更新action 表中的一半行。循环更新:

begin
  for rec in (select distinct ac.people_hex 
    from action ac join people p on ac.people_hex=p.people_hex 
    where p.to_migrate='Y')
  loop
    update action set to_migrate='Y' where people_hex=rec.people_hex;
  end loop;
end;
/

Elapsed: 00:00:10.87

单次更新(回滚后;我将其留在一个块中以模仿您的过程):

begin
  update action set to_migrate = 'Y'
  where people_hex in (select people_hex from people where to_migrate = 'Y');
end;
/

Elapsed: 00:00:07.14

合并(回滚后):

begin
  merge into action a
  using (select people_hex, to_migrate from people where to_migrate = 'Y') p
  on (a.people_hex = p.people_hex)
  when matched then update set a.to_migrate = p.to_migrate;
end;
/

Elapsed: 00:00:07.00

重复运行有一些变化,特别是更新和合并通常非常接近,但有时交换在我的环境中更快;但两者总是比循环更新快得多。您可以在自己的环境中使用自己的数据分布和卷重复此操作,如果性能如此关键,您应该这样做;但是单个更新将比循环更快。无论您是使用更新还是合并,都不会产生太大的影响。

【讨论】:

  • Alex,我已经为你投票了。请注意,您的 LOOP 查询中的 DISTINCT 肯定会消耗 COST,这会增加总时间。
  • @OracleUser - 是的,但存在重复值,因此如果没有重复值,您最终会多次更新相同的行。我已经在做一些批处理;我从问题中推断出这一点,但可能是错误的或比例不同。使用拉取所有匹配的 rowid 并单独更新每一行一次的版本,它所花费的时间大约是 distinct 版本的两倍,因此更糟。
猜你喜欢
  • 1970-01-01
  • 2018-05-16
  • 2013-06-28
  • 2023-02-22
  • 2019-04-17
  • 1970-01-01
  • 2021-02-04
  • 1970-01-01
  • 2012-10-28
相关资源
最近更新 更多