处理游标的更好方法是不要让maxium游标打开超过错误

Jam*_*213 0 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;
Run Code Online (Sandbox Code Playgroud)

我非常肯定会引起问题的游标是这两个:

    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; 
Run Code Online (Sandbox Code Playgroud)

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

 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);
Run Code Online (Sandbox Code Playgroud)

Jus*_*ave 5

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

您的代码中的问题是cCurs. Shift_pkg.getShiftsContaining打开光标,你的代码从光标中取出.但是,当您从中获取光标时,没有代码可以关闭光标.完成从中获取光标后,您的代码将需要关闭光标.

代码本身有点令人困惑.通常,您将从循环内的游标中获取.但是,您发布的代码会在循环外部进行提取,这意味着如果cCurs返回一行,您将拥有无限循环.因此,要么cCurs从不返回任何数据FETCH,或者你有一个无限循环,或者你发布的代码并不完全是你实际运行的.

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

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;
Run Code Online (Sandbox Code Playgroud)

话虽如此,似乎没有任何理由在这里使用动态SQL.这也将是有益的选择更有意义的名称为您的变量:C1,R1,C2,R2,和cCurs不完全有意义的名字.而且您通常希望将代码分解为更易于阅读和消化的较小块.