Reputation: 977
I have a stored procedure that i thought was caught in an infinite loop, but after taking it apart and running the various loops individually it turns out that my code is causing a
ORA-01000: maximum open cursors exceeded
The problem seems to occur with the way i am populating the first two cursors. This may seem like a dumb questions, but i was hoping someone could give me some advice on how to better handle the population of these cursors?
Below you will find my procedure in full, and i will then break down the area that seems to be causing the problem.
Full procedure:
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;
The cursors that i'm pretty sure is causing the problem are these two:
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;
The area that the procedure seems to hit the maximum cursor opened exceeds:
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);
Upvotes: 0
Views: 1198
Reputation: 231651
Implicit cursors will never generate an ORA-01000 error because Oracle takes care of closing them when they go out of scope.
The problem in your code is with cCurs
. Shift_pkg.getShiftsContaining
opens the cursor and your code fetches from the cursor. But there is no code to close the cursor when you are done fetching from it. Your code will need to close the cursor when you are done fetching from it.
The code itself is a little confusing. Normally, you'd fetch from a cursor inside a loop. The code that you've posted, however, has the fetch occurring outside of the loop which means that you'll have an infinite loop if cCurs
returns a row. So either cCurs
never returns any data when you FETCH
from it or you've got an infinite loop or the code you've posted isn't exactly what you're actually running.
Normally, you'd have something like this which will fetch from the cursor in a loop, exit the loop when no more rows are available, and then close the cursor.
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;
That being said, there doesn't appear to be any reason to use dynamic SQL here. It would also be helpful to choose more meaningful names for your variables: C1
, R1
, C2
, R2
, and cCurs
are not exactly meaningful names. And you'll generally want to break up your code into smaller blocks that are easier to read and digest.
Upvotes: 5