基于算术将200万行的一列更新为另一列

时间:2013-12-27 22:23:14

标签: sql performance oracle

我试图在同一个表中的不同列上执行一些数学运算后,在大约200万行上更新一列空值。基本上,我将分钟从一列转换为秒,并根据某些条件使用秒更新空列。我没有光标就试过这个但是它花了太长时间并且没有完成。下面的脚本似乎也永远不会完成。这就是我到目前为止所做的:

DECLARE
CURSOR c1 IS
    SELECT /*+ ORDERED USE_NL(g,e) */
        e.event_code, e.time, e.period, e.time_elapsed, e.rowid
    FROM table1.schedule s, table2.event e
    WHERE e.event_code = s.event_code                
    AND s.schedule_id in (22,39,49,51,53,55,57,59,61,63,65,66,
    68,69,71,72,75,77,78,80,82,84,86,87,89,92,93,95,97,98,
    101,103,105,107,109,111,114,116,118,120,122,125,128,130,
    133,135,137,140,50,52,54,56,58,60,62,64,67,70,73,74,76,79,
    81,83,85,88,90,91,94,96,99,100,102,104,106,108,110,112,113,
    115,117,119,121,123,124,126,127,129,131,132,134,136,138,141)
    AND e.time_elapsed IS NULL
    AND e.time IS NOT NULL
    AND (e.period > 0 OR e.period < 0);


TYPE EventRecType IS RECORD (
    Event_Code table1.schedule.event_code%type,
    evTime table2.event.time%type,
    evPeriod table2.event.period%type,
    evTimeElapsed table2.event.time_elapsed%TYPE,
    evRowId ROWID);

TYPE EventRecTab IS TABLE OF EventRecType INDEX BY PLS_INTEGER;

EventRec EventRecTab;

TYPE typ_evRecord IS RECORD (
  eRowId ROWID,
  TimeElapsed table2.event.time_elapsed%TYPE);

TYPE tab_evTable IS TABLE OF typ_evRecord INDEX BY PLS_INTEGER;

arr_evRecToUpdate tab_evTable;

BEGIN
  OPEN c1;
  LOOP
  FETCH c1 BULK COLLECT INTO EventRec LIMIT 50000; 

  FOR k in 1..EventRec.count LOOP
     if  EventRec(k).evPeriod = 1 AND EventRec(k).evTime < 150 then
       arr_evRecToUpdate(k).TimeElapsed := EventRec(k).evTime*60;
       arr_evRecToUpdate(k).eRowId := EventRec(k).evRowId;  
     elsif EventRec(k).evPeriod = 2 AND EventRec(k).evTime < 150 then          
       arr_evRecToUpdate(k).TimeElapsed := (EventRec(k).evTime-45)*60;
       arr_evRecToUpdate(k).eRowId := EventRec(k).evRowId;   
     elsif EventRec(k).evPeriod = 3 AND EventRec(k).evTime < 150 then    
       arr_evRecToUpdate(k).TimeElapsed := (EventRec(k).evTime-90)*60;
       arr_evRecToUpdate(k).eRowId := EventRec(k).evRowId;      
     elsif EventRec(k).evPeriod = 4 AND EventRec(k).evTime < 150 then      
       arr_evRecToUpdate(k).TimeElapsed := (EventRec(k).evTime-105)*60;
       arr_evRecToUpdate(k).eRowId := EventRec(k).evRowId; 
     elsif EventRec(k).evPeriod = 1 AND EventRec(k).evTime > 150 THEN    
       EventRec(k).evTime := ROUND(EventRec(k).evTime/60);                         
       arr_evRecToUpdate(k).TimeElapsed := EventRec(k).evTime;
       arr_evRecToUpdate(k).eRowId := EventRec(k).evRowId;  
     elsif (EventRec(k).evPeriod = 2) AND (EventRec(k).evTime > 150) THEN     
       EventRec(k).evTime := ROUND((EventRec(k).evTime/60)) + 45;              
       arr_evRecToUpdate(k).TimeElapsed := EventRec(k).evTime;
       arr_evRecToUpdate(k).eRowId := EventRec(k).evRowId;
     end if;

     EXIT WHEN EventRec.COUNT() = 0;
    END LOOP;
    FORALL i_loopIndex IN 1 .. arr_evRecToUpdate.COUNT
      UPDATE table2.event
      SET time_elapsed = arr_evRecToUpdate(i_loopIndex).TimeElapsed
      WHERE rowid = arr_evRecToUpdate(i_loopIndex).eRowid;
    COMMIT;
  END LOOP;        
 CLOSE c1;
END;

3 个答案:

答案 0 :(得分:1)

您是SELECT单独运行时查询消耗时间吗? 请阅读以下我的观点。它不会解决你的问题,但可以帮助你!

要考虑的要点:

1)您BULK COLLECT收集到PL/SQL集合中的所有数据都将放在PGA中,并且它是静态的。如果使用集合的任何其他PL/SQL块在后台运行,则所有块都将使用PGA,并且它也不会共享。这绝对是一项代价高昂的操作,当集合非常庞大时,您可以在50K批次中执行操作超过40次。当然,您使用(subscript)调用它们,这类似于使用index查询表。

在发送到bulk bind之前,PL/SQL engine会消耗SQL Engine本身的时间。您使用ROWID的方法很棒。我不是说永远不应该使用PL / SQL集合。这取决于数据库的繁忙程度。您将在PGA中持有2M结果集,直到CURSOR耗尽为止!更好地与DBA交谈并尽可能增加PGA。

我的看法是在PGA中处理2M行,不太好。

2)正如评论中的某些人所说,拥有staging table也是好的。我已经通过将其拆分为多个并行运行脚本来编写更新操作。这可能需要更多编码。

3)COMMIT大小。这里COMMIT的尺寸为50K。提交大小越高,redo / undo日志的大小就越大。这个表复制了吗?这些表格有triggers

4)有许多文件处理脚本可用(perl)。如果可能的话,将查询数据下载到文件,进行文件处理并重新加载表格。(这可能有利于,当待更新的数量> =总数的50%时)

答案 1 :(得分:1)

更新表是一个缓慢的过程。这里的主要问题是,在更新行之前,您需要先执行一个查询。关于查询调优的所有常见问题都适用于此:基表中有多少行?将选择(更新)它们的百分比?它们如何分布在桌子上?你有什么指数?执行计划是什么样的?

因此,您需要先调整该查询。如果这是一次性的练习,也许建立一个指数是一个太大的投资,但我肯定会考虑建立一个:EVENTS(event_code, period, time_elapsed, time, event_primary_key)否则。

我已成为MERGE的忠实粉丝,作为执行从多个表中提取数据的更新的一种方式,因为Oracle不支持除SELECT之外的其他任何内容的ANSI 92连接语法。

以下可能不太正确,但确实说明了一般原则。

merge into table2.event e
    using ( SELECT e.event_code, e.time, e.period, e.time_elapsed, e.event_primary_key
             , case 
     when  e.evPeriod = 1 AND e.evTime < 150 then
       e.evTime*60;       
     when e.evPeriod = 2 AND e.evTime < 150 then          
       (e.evTime-45)*60;        
     when e.evPeriod = 3 AND e.evTime < 150 then    
       (e.evTime-90)*60;           
     when e.evPeriod = 4 AND e.evTime < 150 then      
       (e.evTime-105)*60;
     when e.evPeriod = 1 AND e.evTime > 150 THEN    
       ROUND(e.evTime/60);                         
     when (e.evPeriod = 2) AND (e.evTime > 150) THEN     
       e.evTime := ROUND((e.evTime/60)) + 45;              
 end as TimeElapsed
    FROM table1.schedule s, table2.event e
    WHERE e.event_code = s.event_code                
    AND s.schedule_id in (22,39,49,51,53,55,57,59,61,63,65,66,
    68,69,71,72,75,77,78,80,82,84,86,87,89,92,93,95,97,98,
    101,103,105,107,109,111,114,116,118,120,122,125,128,130,
    133,135,137,140,50,52,54,56,58,60,62,64,67,70,73,74,76,79,
    81,83,85,88,90,91,94,96,99,100,102,104,106,108,110,112,113,
    115,117,119,121,123,124,126,127,129,131,132,134,136,138,141)
    AND e.time_elapsed IS NULL
    AND e.time IS NOT NULL
    AND (e.period > 0 OR e.period < 0) q
on (q.event_primary_key = e.event_primary_key)
when matched then
    update
    set e.time_elapsed = q.TimeElapsed;

答案 2 :(得分:1)

为什么使用此提示/*+ ORDERED USE_NL(g,e) */?嵌套循环连接仅适用于小型表。大表通常由Hash-Join加入。

与其他答案一样,循环会降低您的操作速度。使用单个DML几乎在所有情况下都是禁食方式。

尝试这个,UPDATE (SELECT ... FROM ...) SET =看起来有点不常见,但它运行正常。

UPDATE 
    (SELECT e.time_elapsed, e.evPeriod, e.evTime
    FROM table1.schedule s
        JOIN table2.event e ON e.event_code = s.event_code
   WHERE s.schedule_id in (22,39,49,51,53,55,57,59,61,63,65,66,68,69,71,72,75,77,78,80,82,84,86,87,89,92,93,95,97,98,101,103,105,107,109,111,114,16,118,120,122,125,128,130,133,135,137,140,50,52,54,56,58,60,62,64,67,70,73,74,76,79,81,83,85,88,90,91,94,96,99,100,102,104,106,108,110,112,113,115,117,119,121,123,124,126,127,129,131,132,134,136,138,141)
        AND e.time_elapsed IS NULL
        AND e.time IS NOT NULL
        AND (e.period > 0 OR e.period < 0)
    )
SET time_elapsed = 
    case 
    when evPeriod = 1 AND evTime < 150 then
        evTime*60       
    when evPeriod = 2 AND evTime < 150 then          
        (evTime-45)*60        
    when evPeriod = 3 AND evTime < 150 then    
        (evTime-90)*60           
    when evPeriod = 4 AND evTime < 150 then      
        (evTime-105)*60
    when evPeriod = 1 AND evTime > 150 THEN    
        ROUND(evTime/60)                         
    when (evPeriod = 2) AND (evTime > 150) THEN     
        ROUND((evTime/60)) + 45              
    end;