Rather than executing a whole query at once, it is possible to set
up a cursor that encapsulates the query, and then read
the query result set one row at a time. This allows the creation
of SPL program logic that retrieves
a row from the result set, does some processing on the data in that
row, and then retrieves the next row and repeats the process.
Cursors are most often used in the context of a FOR
or WHILE loop. A conditional test should be included in
the SPL logic that detects when the end of the result set has
been reached so the program can exit the loop.
In order to use a cursor, it must first be declared in the
declaration section of the SPL
program. A cursor declaration appears as follows:
CURSOR name IS query;
name is an identifier that will be
used to reference the cursor and its result set later in the
program. query is a SQL SELECT command
that determines the result set retrievable by the cursor.
The following are some examples of cursor declarations:
CREATE OR REPLACE PROCEDURE cursor_example
IS
CURSOR emp_cur_1 IS SELECT * FROM emp;
CURSOR emp_cur_2 IS SELECT empno, ename FROM emp;
CURSOR emp_cur_3 IS SELECT empno, ename FROM emp WHERE deptno = 10 ORDER BY empno;
BEGIN
...
END;
Before a cursor can be used to retrieve rows, it must first be
opened. This is accomplished with the OPEN statement.
OPEN name;
name is the identifier of a cursor that
has been previously declared in the declaration section of the
SPL program.
The OPEN statement must not be executed on a cursor that
has already been, and still is open.
The following shows an OPEN statement with its corresponding
cursor declaration.
CREATE OR REPLACE PROCEDURE cursor_example
IS
CURSOR emp_cur_3 IS SELECT empno, ename FROM emp WHERE deptno = 10 ORDER BY empno;
BEGIN
OPEN emp_cur_3;
...
END;
Once a cursor has been opened, rows can be retrieved from the
cursor's result set by using the FETCH statement.
FETCH name INTO {record |
variable [, variable_2 ]...};
name is the identifier of a previously
opened cursor. record is the identifier
of a previously defined record (for example, using
table%ROWTYPE).
variable, variable_2...
are SPL variables that will receive the
field data from the fetched row. The fields in record
or variable, variable_2...
must match in number and order, the fields returned in the
SELECT list of the query given in the cursor declaration.
The data types of the fields in the SELECT list must match,
or be implicitly convertible to the data types of the fields in
record or the data types of
variable, variable_2...
The following shows the FETCH statement.
CREATE OR REPLACE PROCEDURE cursor_example
IS
v_empno NUMBER(4);
v_ename VARCHAR2(10);
CURSOR emp_cur_3 IS SELECT empno, ename FROM emp WHERE deptno = 10 ORDER BY empno;
BEGIN
OPEN emp_cur_3;
FETCH emp_cur_3 INTO v_empno, v_ename;
...
END;
Instead of explicitly declaring the data type of a target variable,
%TYPE can be used instead. In this way, if the data type of the
database column is changed, the target variable declaration in the
SPL program does not have to be changed.
%TYPE will automatically pick up the new data type of the
specified column.
CREATE OR REPLACE PROCEDURE cursor_example
IS
v_empno emp.empno%TYPE;
v_ename emp.ename%TYPE;
CURSOR emp_cur_3 IS SELECT empno, ename FROM emp WHERE deptno = 10 ORDER BY empno;
BEGIN
OPEN emp_cur_3;
FETCH emp_cur_3 INTO v_empno, v_ename;
...
END;
If all the columns in a table are retrieved in the order defined in the
table, %ROWTYPE can be used to define a record into which
the FETCH statement will place the retrieved data. Each
field within the record can then be accessed using dot notation.
CREATE OR REPLACE PROCEDURE cursor_example
IS
v_emp_rec emp%ROWTYPE;
CURSOR emp_cur_1 IS SELECT * FROM emp;
BEGIN
OPEN emp_cur_1;
FETCH emp_cur_1 INTO v_emp_rec;
DBMS_OUTPUT.PUT_LINE('Employee Number: ' || v_emp_rec.empno);
DBMS_OUTPUT.PUT_LINE('Employee Name : ' || v_emp_rec.ename);
...
END;
Cursor FOR loops are used to fetch and process every record in a cursor with minimal code.
The cursor FOR loop automatically opens the cursor, fetches
all rows identified by the cursor, and then closes the cursor. You can embed
SQL statements directly in the cursor FOR loop.
The syntax for creating a cursor FOR loop is:
FOR record_index IN name_of_cursor
LOOP
statements...
END LOOP;
record_index is a record declared implicitly by SPL with the %ROWTYPE attribute against the cursor specified by name_of_cursor.
name_of_cursor is the name of the cursor.
statements are one or more SPL statements. There must be at least one statement.
Suppose the management decides that they want to know the total amount of money that is being expended on the salaries for all the employee's in a specific department. This task can be achieved by making use of a cursor FOR loop as in the following example function.
CREATE OR REPLACE FUNCTION total_sal_by_dept(p_DeptNo IN NUMBER)
RETURN NUMBER AS v_total NUMBER;
CURSOR emp_check IS
SELECT e.sal
FROM emp e, dept d
WHERE e.deptno=d.deptno
AND d.deptno=p_DeptNo;
BEGIN
v_total :=0;
FOR i IN emp_check
LOOP
v_total := v_total+i.sal;
END LOOP;
RETURN v_total;
END;
For example to get the total salary of all the employees in department 10 we would do the following:
SELECT total_sal_by_dept(10);
total_sal_by_dept
-------------------
8750.00
(1 row)
Once all the desired rows have been retrieved from the cursor result
set, the cursor must be closed. Once closed, the result set is no
longer accessible. The CLOSE statement appears as follows:
CLOSE name;
name is the identifier of a cursor that
is currently open. Once a cursor is closed, it must not be closed
again. However, once the cursor is closed, the OPEN
statement can be issued again on the closed cursor and the query
result set will be rebuilt after which the FETCH
statement can then be used to retrieve the rows of the new result
set.
The following example illustrates the use of the CLOSE
statement:
CREATE OR REPLACE PROCEDURE cursor_example
IS
v_emp_rec emp%ROWTYPE;
CURSOR emp_cur_1 IS SELECT * FROM emp;
BEGIN
OPEN emp_cur_1;
FETCH emp_cur_1 INTO v_emp_rec;
DBMS_OUTPUT.PUT_LINE('Employee Number: ' || v_emp_rec.empno);
DBMS_OUTPUT.PUT_LINE('Employee Name : ' || v_emp_rec.ename);
CLOSE emp_cur_1;
END;
This procedure produces the following output when invoked.
Employee number 7369, SMITH is the
first row of the result set.
EXEC cursor_example;
Employee Number: 7369
Employee Name : SMITH
Using the %ROWTYPE attribute, a record can be defined that contains fields
corresponding to all columns fetched from a cursor or cursor variable.
Each field takes on the data type of its corresponding column.
The %ROWTYPE attribute is prefixed by a cursor name or cursor variable name.
record cursor%ROWTYPE;
record is an identifier assigned to the record.
cursor is an explicitly declared cursor within the current scope
The following example shows how you can use a cursor with %ROWTYPE to get information about
which employee works in which department.
CREATE OR REPLACE PROCEDURE emp_info IS
CURSOR empcur IS SELECT ename,deptno FROM emp;
myvar empcur%ROWTYPE;
BEGIN
OPEN empcur;
LOOP
FETCH empcur INTO myvar;
EXIT WHEN empcur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( myvar.ename || ' works in department ' || myvar.deptno );
END LOOP;
close empcur;
END;
To see this example in work, please type the following:
EXEC emp_info;
Each cursor has a set of attributes associated with it that allows
the program to test the state of the cursor. These attributes are
%ISOPEN, %FOUND, %NOTFOUND,
and %ROWCOUNT. These attributes are described in the
following sections.
The %ISOPEN attribute is used to test whether or not
a cursor is open.
cursor_name%ISOPEN
cursor_name is the name of the cursor for
which a boolean data type of "true" will
be returned if the cursor is open, "false" otherwise.
The following is an example of using %ISOPEN.
CREATE OR REPLACE PROCEDURE cursor_example
IS
...
CURSOR emp_cur_1 IS SELECT * FROM emp;
...
BEGIN
...
IF emp_cur_1%ISOPEN THEN
NULL;
ELSE
OPEN emp_cur_1;
END IF;
FETCH emp_cur_1 INTO ...
...
END;
The %FOUND attribute is used to test whether or not
a row is retrieved from the result set of the specfied cursor
after a FETCH on the cursor.
cursor_name%FOUND
cursor_name is the name of the cursor for
which a boolean data type of "true" will
be returned if a row is retrieved from the result set of the cursor
after a FETCH.
After the last row of the result set has been FETCHed
the next FETCH results in %FOUND returning
"false". "false" is also returned after
the first FETCH if there are no rows in the result set
to begin with.
Referencing %FOUND on a cursor before it is opened or
after it is closed, results in an INVALID_CURSOR
exception being thrown.
%FOUND returns null if it is referenced
when the cursor is open, but before the first FETCH.
The following example uses %FOUND.
CREATE OR REPLACE PROCEDURE cursor_example
IS
v_emp_rec emp%ROWTYPE;
CURSOR emp_cur_1 IS SELECT * FROM emp;
BEGIN
OPEN emp_cur_1;
DBMS_OUTPUT.PUT_LINE('EMPNO ENAME');
DBMS_OUTPUT.PUT_LINE('----- -------');
FETCH emp_cur_1 INTO v_emp_rec;
WHILE emp_cur_1%FOUND LOOP
DBMS_OUTPUT.PUT_LINE(v_emp_rec.empno || ' ' || v_emp_rec.ename);
FETCH emp_cur_1 INTO v_emp_rec;
END LOOP;
CLOSE emp_cur_1;
END;
When the previous procedure is invoked, the output appears as follows:
EXEC cursor_example;
EMPNO ENAME
----- ------
7369 SMITH
7499 ALLEN
7521 WARD
7566 JONES
7654 MARTIN
7698 BLAKE
7782 CLARK
7788 SCOTT
7839 KING
7844 TURNER
7876 ADAMS
7900 JAMES
7902 FORD
7934 MILLER
The %NOTFOUND attribute is the logical opposite
of %FOUND.
cursor_name%NOTFOUND
cursor_name is the name of the cursor for
which a boolean data type of "false" will
be returned if a row is retrieved from the result set of the cursor
after a FETCH.
After the last row of the result set has been FETCHed
the next FETCH results in %NOTFOUND returning
"true". "true" is also returned after
the first FETCH if there are no rows in the result set
to begin with.
Referencing %NOTFOUND on a cursor before it is opened or
after it is closed, results in an INVALID_CURSOR
exception being thrown.
%NOTFOUND returns null if it is referenced
when the cursor is open, but before the first FETCH.
The following example uses %NOTFOUND.
CREATE OR REPLACE PROCEDURE cursor_example
IS
v_emp_rec emp%ROWTYPE;
CURSOR emp_cur_1 IS SELECT * FROM emp;
BEGIN
OPEN emp_cur_1;
DBMS_OUTPUT.PUT_LINE('EMPNO ENAME');
DBMS_OUTPUT.PUT_LINE('----- -------');
LOOP
FETCH emp_cur_1 INTO v_emp_rec;
EXIT WHEN emp_cur_1%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_emp_rec.empno || ' ' || v_emp_rec.ename);
END LOOP;
CLOSE emp_cur_1;
END;
Similar to the prior example, this procedure produces the
same output when invoked.
EXEC cursor_example;
EMPNO ENAME
----- ------
7369 SMITH
7499 ALLEN
7521 WARD
7566 JONES
7654 MARTIN
7698 BLAKE
7782 CLARK
7788 SCOTT
7839 KING
7844 TURNER
7876 ADAMS
7900 JAMES
7902 FORD
7934 MILLER
The %ROWCOUNT attribute returns an integer showing
the number of rows FETCHed so far from the specified
cursor.
cursor_name%ROWCOUNT
cursor_name is the name of the cursor for
which %ROWCOUNT returns the number of rows retrieved thus
far. After the last row has been retrieved, %ROWCOUNT
remains set to the total number of rows returned until the cursor
is closed at which point %ROWCOUNT will throw an
INVALID_CURSOR exception if referenced.
Referencing %ROWCOUNT on a cursor before it is opened or
after it is closed, results in an INVALID_CURSOR
exception being thrown.
%ROWCOUNT returns 0 if it is referenced
when the cursor is open, but before the first FETCH.
%ROWCOUNT also returns 0 after the first
FETCH when there are no rows in the result set to begin
with.
The following example uses %ROWCOUNT.
CREATE OR REPLACE PROCEDURE cursor_example
IS
v_emp_rec emp%ROWTYPE;
CURSOR emp_cur_1 IS SELECT * FROM emp;
BEGIN
OPEN emp_cur_1;
DBMS_OUTPUT.PUT_LINE('EMPNO ENAME');
DBMS_OUTPUT.PUT_LINE('----- -------');
LOOP
FETCH emp_cur_1 INTO v_emp_rec;
EXIT WHEN emp_cur_1%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_emp_rec.empno || ' ' || v_emp_rec.ename);
END LOOP;
DBMS_OUTPUT.PUT_LINE('**********************');
DBMS_OUTPUT.PUT_LINE(emp_cur_1%ROWCOUNT || ' rows were retrieved');
CLOSE emp_cur_1;
END;
This procedure prints the total number of rows retrieved at
the end of the employee list as follows:
EXEC cursor_example;
EMPNO ENAME
----- -------
7369 SMITH
7499 ALLEN
7521 WARD
7566 JONES
7654 MARTIN
7698 BLAKE
7782 CLARK
7788 SCOTT
7839 KING
7844 TURNER
7876 ADAMS
7900 JAMES
7902 FORD
7934 MILLER
**********************
14 rows were retrieved
The following table summarizes the possible cursor states and
the values returned by the cursor attributes.
Table 14-2. Cursor Attributes
Cursor State | %ISOPEN | %FOUND | %NOTFOUND | %ROWCOUNT |
---|
Before OPEN | False | INVALID_CURSOR Exception | INVALID_CURSOR Exception | INVALID_CURSOR Exception |
After OPEN & Before 1st
FETCH | True | Null | Null | 0 |
After 1st Successful FETCH | True | True | False | 1 |
After nth Successful
FETCH (last row) | True | True | False | n |
After n+1st
FETCH (after last row) | True | False | True | n |
After CLOSE | False | INVALID_CURSOR Exception | INVALID_CURSOR Exception | INVALID_CURSOR Exception |
A user can also declare a static cursor that accepts parameters, and can pass values for those
parameters when opening that cursor. In the following example we have created a parameterized
cursor which will display the name and salary of all employees from the emp table that have a
salary less then a specified value which is passed as a parameter.
DECLARE
my_record emp%ROWTYPE;
CURSOR c1 (max_wage NUMBER) IS
SELECT * FROM emp WHERE sal < max_wage;
BEGIN
OPEN c1(2000);
LOOP
FETCH c1 INTO my_record;
EXIT WHEN c1%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('Name = ' || my_record.ename || ', salary = ' || my_record.sal);
END LOOP;
END;
So for example if we pass the value 2000 as max_wage, then we will only be shown the name
and salary of all employees that have a salary less then 2000. The result of the above query is following:
INFO: Name = SMITH, salary = 800.00
INFO: Name = ALLEN, salary = 1600.00
INFO: Name = WARD, salary = 1250.00
INFO: Name = MARTIN, salary = 1250.00
INFO: Name = TURNER, salary = 1500.00
INFO: Name = ADAMS, salary = 1100.00
INFO: Name = JAMES, salary = 950.00
INFO: Name = MILLER, salary = 1300.00