处理游标的更好方法是不能获得最大游标超过错误

时间:2012-03-13 18:30:29

标签: sql oracle plsql cursor

我有一个存储过程,我认为它是在一个无限循环中捕获的,但是在将它拆开并单独运行各种循环之后,事实证明我的代码导致了

  

ORA-01000:超出最大打开游标数

问题似乎与我填充前两个游标的方式有关。这可能看起来像一个愚蠢的问题,但我希望有人能就如何更好地处理这些游标的数量给我一些建议?

下面你将完整地找到我的程序,然后我将分解似乎导致问题的区域。

完整程序:

    procedure SplitIntrvlsAtShiftBoundaries is

vRecCount number;
RC number;
vShiftIDAtIntervalStart number;
vShiftIDAtIntervalEnd number;
vDummyRecSecs number(6,4) := 0.0;

vStartTimestamp timestamp with time zone;
vEndTimestamp timestamp with time zone;

cCurs Sys_refcursor;

vCurrentRec TMP_SHIFT_LIST_TBL%rowtype;
vCurrentInterval MACHINE_INTERVAL%rowtype;
vNewInterval MACHINE_INTERVAL%rowtype;

lProcName    CONST.PBString;


vIntervalDOW smallint; -- intervals never span a midnite boundary 
                       -- are split over midnite in prior steps.

CURSOR C1 IS SELECT * FROM TMP_SHIFT_MI_TBL ORDER BY START_DATE_TIME ASC, MACHINE_ID;
CURSOR C2 IS SELECT * FROM TMP_SHIFT_LIST_TBL ORDER BY SHIFT_START_DAY ASC, SHIFT_START_TIME; 

BEGIN

  lProcName := 'TRANSFORM_PROCESS_2.SplitIntrvlsAtShiftBoundaries';
  pb_util.logdata(3, lProcName, 'Process Started ' );

  DELETE FROM TMP_SHIFT_MI_TBL;
  DELETE FROM TMP_SHIFT_LIST_TBL;

  INSERT INTO TMP_SHIFT_MI_TBL
  SELECT *
  FROM MACHINE_INTERVAL
  WHERE  interval_state = 0
  AND start_date_time <> CONST.STARTDATE
  AND  trunc(calc_end_time) < trunc( CONST.ENDDATE) - 144;

  BEGIN
    FOR R1 IN C1
      LOOP  

        -- gets the current interval from the machine interval table by using the interval id found from the tmp_table
        SELECT * INTO vCurrentInterval 
        FROM Machine_Interval MI 
        WHERE R1.Machine_Interval_ID = MI.Machine_Interval_Id; 

        -- gets the shifts containning the start and end dates obtained from the machine interval
        Shift_pkg.getShiftsContaining(R1.start_date_time, R1.calc_end_time, cCurs, vRecCount);

        FETCH cCurs INTO vCurrentRec;
          LOOP

            -- populates the tmp_shift_list_tbl dynamically
            BEGIN          
              EXIT WHEN cCurs%notfound;

              EXECUTE IMMEDIATE 'insert into tmp_shift_list_tbl
                                 (shift_id_pk, shift_name, shift_start_day, shift_start_time, 
                                  shift_end_day, shift_end_time, site_id_fk, shift_day_id,
                                  startoffset, endoffset) 
                           VALUES(:1, :2, :3, :4, :5, :6, :7, :8, :9, :10)' 
                           USING vCurrentRec.SHIFT_ID_PK, vCurrentRec.SHIFT_NAME,
                                 vCurrentRec.SHIFT_START_DAY, vCurrentRec.SHIFT_START_TIME,
                                 vCurrentRec.SHIFT_END_DAY, vCurrentRec.SHIFT_END_TIME,
                                 vCurrentRec.SITE_ID_FK, vCurrentRec.SHIFT_DAY_ID, 
                                 vCurrentRec.STARTOFFSET, vCurrentRec.ENDOFFSET;

            END;
          END LOOP;  
          pb_util.logdata(3, lProcName, 'FOUND: ', 'found ' || vRecCount ||
                          ' shifts in machine interval: ' || R1.MACHINE_INTERVAL_ID);

        -- depending on the # of shifts found in vRecCount different cases are applied.
        CASE
        WHEN vRecCount = 1 -- apply shift id to interval   
           THEN 
             BEGIN             
               FOR R2 IN C2
                 LOOP

                    UPDATE MACHINE_INTERVAL MI
                    SET MI.SHIFT_ID = R2.SHIFT_ID_PK
                    WHERE MI.MACHINE_INTERVAL_ID = R1.MACHINE_INTERVAL_ID;

                 END LOOP;
             END;      
        WHEN vRecCount > 1 -- split up the interval between the shifts i.e. create intervals
           THEN 
            BEGIN            
              FOR R2 IN C2
                LOOP  

                  -- make copy of the current interval.
                  vNewInterval := vCurrentInterval;
                  -- set the new interval duration
                  vCurrentInterval.Interval_Duration := pb_util.intervaltosecond(R2.shift_Start_Time - 
                                                                                 vCurrentInterval.Start_Date_Time);
                  -- set the new shift id for the machine interval table
                  vCurrentInterval.Shift_ID := R2.Shift_ID_PK;

                  -- update the record.
                  DM.MachineIntervalRecordUpdate(MachineIntervalID => vCurrentInterval.Machine_Interval_ID,
                                                 StartDateTime => vCurrentInterval.Start_Date_Time,                                                 
                                                 IntervalDuration => vCurrentInterval.Interval_Duration,
                                                 IntervalCategory => vCurrentInterval.INTERVAL_CATEGORY,
                                                 NPTCategoryID => vCurrentInterval.NPT_CATEGORY_ID,
                                                 NPTControlledID => vCurrentInterval.CATEGORYTYPENUMERIC,
                                                 NPTOPStateID => vCurrentInterval.OPERATIONALSTATENUMERIC,
                                                 pExecutionSecs => vDummyRecsecs);

                  UPDATE MACHINE_INTERVAL MI
                  SET MI.Shift_ID = vCurrentInterval.Shift_ID
                  WHERE MI.Machine_Interval_ID = vCurrentInterval.Machine_Interval_ID;

                   -- set new start date time & interval duration                              
                  vNewInterval.Start_Date_Time := R2.Shift_End_Time;
                  vNewInterval.Interval_Duration := pb_util.intervaltosecond(vNewInterval.Calc_End_Time - 
                                                                             vNewInterval.Start_Date_Time);

                  -- create new record in interval table.
                  RC := DM.MachineIntervalRecordInsert(MachineIntervalRecord_IN => vNewInterval, 
                                                       pExecutionSecs => vDummyRecsecs);

                  -- set current interval to the newly created interval and loop.
                  vCurrentInterval := vNewInterval;

                END LOOP;
            END;      
        ELSE -- if no shifts are found then set id to null

          UPDATE MACHINE_INTERVAL MI
          SET SHIFT_ID = 0
          WHERE MI.MACHINE_INTERVAL_ID = R1.MACHINE_INTERVAL_ID;

        END CASE;   
      END LOOP;
      pb_util.logdata(3, lProcName, 'Process Completed ' );
      COMMIT;
  END;

exception when others then
pb_util.logdata(1, 'TRANSFORM_PROCESS_2.SplitIntrvlsAtShiftBoundaries', 'EXCEPTION THROWN (880B)', SQLERRM || ' STACK: ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);

END SplitIntrvlsAtShiftBoundaries;

我非常肯定导致问题的游标是这两个:

    CURSOR C1 IS SELECT * FROM TMP_SHIFT_MI_TBL ORDER BY START_DATE_TIME ASC, MACHINE_ID;
CURSOR C2 IS SELECT * FROM TMP_SHIFT_LIST_TBL ORDER BY SHIFT_START_DAY ASC, SHIFT_START_TIME; 

程序似乎打开最大光标的区域超出:

 FOR R1 IN C1
  LOOP  

    -- gets the current interval from the machine interval table by using the interval id found from the tmp_table
    SELECT * INTO vCurrentInterval 
    FROM Machine_Interval MI 
    WHERE R1.Machine_Interval_ID = MI.Machine_Interval_Id; 

    -- gets the shifts containning the start and end dates obtained from the machine interval
    Shift_pkg.getShiftsContaining(R1.start_date_time, R1.calc_end_time, cCurs, vRecCount);

    FETCH cCurs INTO vCurrentRec;
      LOOP

        -- populates the tmp_shift_list_tbl dynamically
        BEGIN          
          EXIT WHEN cCurs%notfound;

          EXECUTE IMMEDIATE 'insert into tmp_shift_list_tbl
                             (shift_id_pk, shift_name, shift_start_day, shift_start_time, 
                              shift_end_day, shift_end_time, site_id_fk, shift_day_id,
                              startoffset, endoffset) 
                       VALUES(:1, :2, :3, :4, :5, :6, :7, :8, :9, :10)' 
                       USING vCurrentRec.SHIFT_ID_PK, vCurrentRec.SHIFT_NAME,
                             vCurrentRec.SHIFT_START_DAY, vCurrentRec.SHIFT_START_TIME,
                             vCurrentRec.SHIFT_END_DAY, vCurrentRec.SHIFT_END_TIME,
                             vCurrentRec.SITE_ID_FK, vCurrentRec.SHIFT_DAY_ID, 
                             vCurrentRec.STARTOFFSET, vCurrentRec.ENDOFFSET;

        END;
      END LOOP;  
      pb_util.logdata(3, lProcName, 'FOUND: ', 'found ' || vRecCount ||
                      ' shifts in machine interval: ' || R1.MACHINE_INTERVAL_ID);

1 个答案:

答案 0 :(得分:5)

隐式游标永远不会生成ORA-01000错误,因为Oracle会在超出范围时负责关闭它们。

代码中的问题是cCursShift_pkg.getShiftsContaining打开游标,代码从游标中获取。但是,当您从中获取光标时,没有代码可以关闭光标。完成提取后,您的代码需要关闭光标。

代码本身有点令人困惑。通常,您将从循环内的游标中获取。但是,您发布的代码在循环外部进行了提取,这意味着如果cCurs返回一行,您将拥有无限循环。因此,当cCursFETCH发送数据时,LOOP FETCH cCurs INTO vCurrentRec; EXIT WHEN cCurs%notfound; EXECUTE IMMEDIATE 'insert into tmp_shift_list_tbl (shift_id_pk, shift_name, shift_start_day, shift_start_time, shift_end_day, shift_end_time, site_id_fk, shift_day_id, startoffset, endoffset) VALUES(:1, :2, :3, :4, :5, :6, :7, :8, :9, :10)' USING vCurrentRec.SHIFT_ID_PK, vCurrentRec.SHIFT_NAME, vCurrentRec.SHIFT_START_DAY, vCurrentRec.SHIFT_START_TIME, vCurrentRec.SHIFT_END_DAY, vCurrentRec.SHIFT_END_TIME, vCurrentRec.SITE_ID_FK, vCurrentRec.SHIFT_DAY_ID, vCurrentRec.STARTOFFSET, vCurrentRec.ENDOFFSET; END LOOP; CLOSE cCurs; 永远不会返回任何数据,或者您有一个无限循环,或者您发布的代码与您实际运行的代码不完全相同。

通常,你会有这样的东西,它会从循环中的光标中获取,当没有更多行可用时退出循环,然后关闭光标。

C1

话虽如此,似乎没有任何理由在这里使用动态SQL。为变量选择更有意义的名称也会有所帮助:R1C2R2cCurs和{{1}}并非完全有意义的名称。而且您通常希望将代码分解为更易于阅读和消化的较小块。