LOOP FETCH c1 INTO my_ename, my_sal, my_hiredate; IF c1%FOUND THEN -- fetch succeeded ... ELSE -- fetch failed, so exit loop EXIT; END IF; END LOOP;
If a cursor or cursor variable is not open, referencing it with %FOUND raises the predefined exception INVALID_CURSOR.
IF c1%ISOPEN THEN -- cursor is open ... ELSE -- cursor is closed, so open it OPEN c1; END IF;
LOOP FETCH c1 INTO my_ename, my_sal, my_hiredate; EXIT WHEN c1%NOTFOUND; ... END LOOP;
If a cursor or cursor variable is not open, referencing it with %NOTFOUND raises INVALID_CURSOR.
LOOP FETCH c1 INTO my_ename, my_deptno; IF c1%ROWCOUNT > 10 THEN ... END IF; ... END LOOP;
If a cursor or cursor variable is not open, referencing it with %ROWCOUNT raises INVALID_CURSOR.
Table 5 - 1 shows what each cursor attribute yields before and after you execute an OPEN, FETCH, or CLOSE statement.
%FOUND | %ISOPEN | %NOTFOUND | %ROWCOUNT | ||
OPEN | before | exception | FALSE | exception | exception |
after | NULL | TRUE | NULL | 0 | |
first FETCH | before | NULL | TRUE | NULL | 0 |
after | TRUE | TRUE | FALSE | 1 | |
middle FETCHes | before | TRUE | TRUE | FALSE | 1 |
after | TRUE | TRUE | FALSE | data dependent | |
last FETCH | before | TRUE | TRUE | FALSE | data dependent |
after | FALSE | TRUE | TRUE | data dependent | |
CLOSE | before | FALSE | TRUE | TRUE | data dependent |
after | exception | FALSE | exception | exception | |
Notes: 1. Referencing %FOUND, %NOTFOUND, or %ROWCOUNT before a cursor is opened or after it is closed raises INVALID_CURSOR. 2. After the first FETCH, if the result set was empty, %FOUND yields FALSE, %NOTFOUND yields TRUE, and %ROWCOUNT yields 0. | |||||
-- available online in file EXAMP5 DECLARE num1 data_table.n1%TYPE; -- Declare variables num2 data_table.n2%TYPE; -- having same types as num3 data_table.n3%TYPE; -- database columns result temp.col1%TYPE; CURSOR c1 IS SELECT n1, n2, n3 FROM data_table WHERE exper_num = 1; BEGIN OPEN c1; LOOP FETCH c1 INTO num1, num2, num3; EXIT WHEN c1%NOTFOUND; -- yields TRUE when FETCH -- finds no more rows result := num2/(num1 + num3); INSERT INTO temp VALUES (result, NULL, NULL); END LOOP; CLOSE c1; COMMIT; END;
In the next example, you check all storage bins that contain part number 5469, withdrawing their contents until you accumulate 1000 units:
-- available online in file EXAMP6 DECLARE CURSOR bin_cur(part_number NUMBER) IS SELECT amt_in_bin FROM bins WHERE part_num = part_number AND amt_in_bin > 0 ORDER BY bin_num FOR UPDATE OF amt_in_bin; bin_amt bins.amt_in_bin%TYPE; total_so_far NUMBER(5) := 0; amount_needed CONSTANT NUMBER(5) := 1000; bins_looked_at NUMBER(3) := 0; BEGIN OPEN bin_cur(5469); WHILE total_so_far < amount_needed LOOP FETCH bin_cur INTO bin_amt; EXIT WHEN bin_cur%NOTFOUND; -- if we exit, there's not enough to fill the order bins_looked_at := bins_looked_at + 1; IF total_so_far + bin_amt < amount_needed THEN UPDATE bins SET amt_in_bin = 0 WHERE CURRENT OF bin_cur; -- take everything in the bin total_so_far := total_so_far + bin_amt; ELSE -- we finally have enough UPDATE bins SET amt_in_bin = amt_in_bin - (amount_needed - total_so_far) WHERE CURRENT OF bin_cur; total_so_far := amount_needed; END IF; END LOOP; CLOSE bin_cur; INSERT INTO temp VALUES (NULL, bins_looked_at, '<- bins looked at'); COMMIT; END;
DELETE FROM emp WHERE empno = my_empno; IF SQL%FOUND THEN -- delete succeeded INSERT INTO new_emp VALUES (my_empno, my_ename, ...); ... END IF;
UPDATE emp SET sal = sal * 1.05 WHERE empno = my_empno; IF SQL%NOTFOUND THEN -- update failed INSERT INTO errors VALUES (...); END IF;
DELETE FROM emp WHERE ... IF SQL%ROWCOUNT > 10 THEN -- more than 10 rows were deleted ... END IF;
If a SELECT INTO statement returns more than one row, PL/SQL raises the predefined exception TOO_MANY_ROWS and %ROWCOUNT yields 1, not the actual number of rows that satisfy the query.
UPDATE parts SET quantity = quantity - 1 WHERE partno = part_id; check_status(part_id); -- procedure call IF SQL%NOTFOUND THEN -- dangerous!
You can debug the code as follows:
UPDATE parts SET quantity = quantity - 1 WHERE partno = part_id; sql_notfound := SQL%NOTFOUND; -- assign value to Boolean variable check_status(part_id); IF sql_notfound THEN ...
If a SELECT INTO statement fails to return a row, PL/SQL raises the predefined exception NO_DATA_FOUND whether you check %NOTFOUND on the next line or not. Consider the following example:
BEGIN ... SELECT sal INTO my_sal FROM emp WHERE empno = my_empno; -- might raise NO_DATA_FOUND IF SQL%NOTFOUND THEN -- condition tested only when false ... -- this action is never taken END IF;
The check is useless because the IF condition is tested only when %NOTFOUND is false. When PL/SQL raises NO_DATA_FOUND, normal execution stops and control transfers to the exception-handling part of the block.
However, a SELECT INTO statement that calls a SQL group function never raises NO_DATA_FOUND because group functions always return a value or a null. In such cases, %NOTFOUND yields FALSE, as the following example shows:
BEGIN ... SELECT MAX(sal) INTO my_sal FROM emp WHERE deptno = my_deptno; -- never raises NO_DATA_FOUND IF SQL%NOTFOUND THEN -- always tested but never true ... -- this action is never taken END IF; EXCEPTION WHEN NO_DATA_FOUND THEN ... -- never invoked