Cursors and Collections
Cursor
Every SQL statement executed by the Oracle Server has an individual cursor associated with it:
- Implicit cursors: Declared for all DML and PL/SQL SELECT statements
- Explicit cursors: Declared and named by the programmer
Controlling Explicit Cursors:
- DECLARE: Create a named SQL area
- OPEN: Identify the active set
- FETCH: Load the current row into variables
- EMPTY?: Test for existing rows, return to FETCH if rows found
- CLOSE: Release the active set
-- examples for cursors (implicit and explicit cursor)
set serveroutput on
DECLARE
CURSOR curs1 IS SELECT deptno, ename FROM nikovits.emp WHERE deptno = 10;
rec curs1%ROWTYPE;
BEGIN
OPEN curs1;
LOOP
FETCH curs1 INTO rec;
EXIT WHEN curs1%NOTFOUND;
dbms_output.put_line(to_char(rec.deptno)||' - '||rec.ename);
END LOOP;
CLOSE curs1;
END;
/
Results: ----------- 10 - MILLER 10 - CLARK 10 - KING
Controlling Explicit Cursors:
- Open the cursor.
- Fetch a row from the cursor.
- Continue until empty.
- Close the cursor.
Opening the Cursor:
- Open the cursor to execute the query and identify the active set.
- If the query returns no rows, no exception is raised.
- Use cursor attributes to test the outcome after a fetch.
Fetching Data from the Cursor:
- Retrieve the current row values into output variables.
- Include the same number of variables.
- Match each variable to correspond to the columns positionally.
- Test to see if the cursor contains rows.
Declaring the Cursor:
- Do not include the INTO clause in the cursor declaration.
- If processing rows in a specific sequence is required, use the ORDER BY clause in the query.
Closing the Cursor:
- Close the cursor after completing the processing of the rows.
- Reopen the cursor, if required.
- Do not attempt to fetch data from a cursor once it has been closed.
Explicit Cursor Attributes:
- Obtain status information about a cursor.
- %ISOPEN (Boolean): Evaluates to TRUE if the cursor is open
- %NOTFOUND (Boolean): Evaluates to TRUE if the most recent fetch does not return a row
- %FOUND (Boolean): Evaluates to TRUE if the most recent fetch returns a row; complement of %NOTFOUND
- %ROWCOUNT (Number): Evaluates to the total number of rows returned so far
-- Implicit cursor (for a DELETE statement)
set serveroutput on
DECLARE
v_rows_deleted VARCHAR2(30);
v_job emp.job%TYPE := 'SALESMAN';
BEGIN
DELETE FROM emp WHERE job = v_job;
v_rows_deleted := (SQL%ROWCOUNT ||' row(s) deleted.');
DBMS_OUTPUT.PUT_LINE (v_rows_deleted);
ROLLBACK; -- rollback the transaction, we don't really want to delete
END;
/
Results: ----------- 5 row(s) deleted.
Cursors and Records:
- Process the rows of the active set conveniently by fetching values into a PL/SQL RECORD.
Cursor FOR Loop:
- The cursor FOR loop is a shortcut to process explicit cursors.
- Implicit open, fetch, and close occur.
- The record is implicitly declared.
Cursor Parameters:
- Pass parameter values to a cursor when the cursor is opened and the query is executed.
- Open an explicit cursor several times with a different active set each time.
-- Explicit cursors, 3 forms of cursor usage and cursors with parameter
set serveroutput on
DECLARE
CURSOR curs1(p_deptno NUMBER DEFAULT 10) IS SELECT ename, sal FROM emp WHERE deptno = p_deptno;
CURSOR curs2(p_deptno NUMBER) IS SELECT ename, sal from emp where deptno = p_deptno;
rec curs1%ROWTYPE;
BEGIN
OPEN curs1(); -- default parameter
LOOP
FETCH curs1 INTO rec;
EXIT WHEN curs1%NOTFOUND;
dbms_output.put_line('curs1: '||rec.ename||' - '||to_char(rec.sal));
END LOOP;
CLOSE curs1;
FOR rec IN curs2(20) LOOP -- parameter
dbms_output.put_line('curs2: '||rec.ename||' - '||to_char(rec.sal));
END LOOP;
FOR rec IN (SELECT ename, sal FROM emp WHERE deptno=30) LOOP
dbms_output.put_line('curs3: '||rec.ename||' - '||to_char(rec.sal));
END LOOP;
END;
/
Results: ----------- curs1: CLARK - 2450 curs1: KING - 5000 curs1: MILLER - 1300 curs2: SMITH - 800 curs2: JONES - 2975 curs2: SCOTT - 3000 curs2: ADAMS - 1100 curs2: FORD - 3000 curs3: ALLEN - 1600 curs3: WARD - 1250 curs3: MARTIN - 1250 curs3: BLAKE - 4250 curs3: TURNER - 1500 curs3: JAMES - 950
-- Using a CURSOR for a WITH statement
DECLARE
CURSOR curs1 IS
WITH
tmp1 AS (
SELECT deptno, round(AVG(sal)) dept_avg FROM emp
GROUP BY deptno),
tmp2 AS (
SELECT round(AVG(sal)) gen_avg FROM emp)
SELECT dname, dept_avg, gen_avg, dept_avg-gen_avg diff
FROM tmp1, tmp2, dept WHERE tmp1.deptno = dept.deptno;
rec curs1%ROWTYPE;
BEGIN
OPEN curs1;
LOOP
FETCH curs1 INTO rec;
EXIT WHEN curs1%NOTFOUND;
dbms_output.put_line(rec.dname||' | '||rec.dept_avg||' | '||rec.diff);
END LOOP;
CLOSE curs1;
END;
/
Results: ----------- ACCOUNTING | 2917 | 751 RESEARCH | 2175 | 9 SALES | 1800 | -366 MARKETING | 2133 | -33
Explicit Locking:
- Explicit locking lets you deny access for the duration of a transaction.
- Lock the rows before the update or delete.
Cursor Updates and Deletes:
- Use cursors to update or delete the current row.
- Include the FOR UPDATE clause in the cursor query to lock the rows first.
- Use the WHERE CURRENT OF clause to reference the current row from an explicit cursor.
-- Update with a cursor -> WHERE CURRENT OF
-- FOR UPDATE locks rows in the table
DECLARE
CURSOR curs1 IS SELECT ename, sal FROM emp WHERE deptno = 10 FOR UPDATE;
rec curs1%ROWTYPE;
BEGIN
OPEN curs1;
LOOP
FETCH curs1 INTO rec;
EXIT WHEN curs1%NOTFOUND;
UPDATE emp SET sal = sal + length(rec.ename) WHERE CURRENT OF curs1;
dbms_output.put_line(rec.ename||' - '||to_char(rec.sal));
END LOOP;
CLOSE curs1;
END;
/
SELECT ename, sal FROM emp WHERE deptno=10; -- you can see the updated values
ROLLBACK;
SELECT ename, sal FROM emp WHERE deptno=10; -- old values are restored
-- What happens when we update the table during cursor loop?
-- answer: cursor doesn't see the new values
-- result set will be fixed when opening the cursor
set serveroutput on
DECLARE
v_date date := SYSDATE + 1;
BEGIN
FOR rec IN (SELECT * FROM dept for update) LOOP
dbms_output.put(to_char(sysdate, 'hh24:mi:ss')||' --> ');
dbms_output.put_line(rec.deptno);
SELECT SYSDATE + 2/(24*60*60) INTO v_date FROM dual; -- 2 seconds
WHILE sysdate < v_date LOOP NULL; END LOOP;
update dept set deptno = deptno+1;
END LOOP;
END;
/
-- Cursor update for a join
-- LOC column will be updated in each step of iteration
-- Result set of the cursor:
-- TURNER SALES
-- MARTIN SALES
-- WARD SALES
-- ALLEN SALES
DECLARE
CURSOR c1 IS SELECT ename, dname FROM emp, dept
WHERE emp.deptno = dept.deptno AND job = 'SALESMAN' FOR UPDATE OF loc;
BEGIN
FOR rec IN c1 LOOP
-- UPDATE emp SET sal = sal + 1 WHERE CURRENT OF c1;
UPDATE dept SET loc = loc|| '1' WHERE CURRENT OF c1;
END LOOP;
END;
/
-- cursor variable
-- can be strongly typed (with return type) or weakly typed (without return type)
DECLARE
TYPE empcurtyp IS REF CURSOR RETURN emp%ROWTYPE; -- strong
TYPE genericcurtyp IS REF CURSOR; -- weak
TYPE t_cur IS REF CURSOR;
v_cur t_cur;
PROCEDURE cursor_open(p_cur IN OUT t_cur) IS
BEGIN
OPEN p_cur FOR SELECT ename FROM emp WHERE sal > 3000;
END;
FUNCTION read_cursor(p_cur IN t_cur) RETURN varchar2 IS
v emp.ename%TYPE;
BEGIN
FETCH p_cur INTO v;
RETURN v;
END;
BEGIN
cursor_open(v_cur);
dbms_output.put_line(read_cursor(v_cur));
CLOSE v_cur;
END;
/
Results: ----------- BLAKE
Collection
- A collection is an ordered group of elements having the same data type.
- Each element is identified by a unique subscript that represents its position in the collection.
- Associative array (or index-by table): Unbounded, String or Integer subscript, Either dense or sparse, Only in PL/SQL block, No Object Type Attribute
- Nested table: Unbounded, Integer subscript, Starts dense, can become sparse, Either in PL/SQL block or at schema level, Yes Object Type Attribute
- Variablesize array (Varray): Bounded, Integer subscript, Always dense, Either in PL/SQL block or at schema level, Yes Object Type Attribute
PL/SQL has three collection types:
- associative array (INDEX BY ...; we call it also PL/SQL table)
- VARRAY (variable-size array),
- NESTED TABLE
Collection methods
A collection method is a PL/SQL subprogram: either a function that returns information about a collection, or a procedure that operates on a collection.
| Method | Type | Description |
|---|---|---|
| DELETE | Procedure | Deletes elements from collection. |
| TRIM | Procedure | Deletes elements from end of varray or nested table. |
| EXTEND | Procedure | Adds elements to end of varray or nested table. |
| EXISTS | Function | Returns TRUE if and only if specified element of varray or nested table exists. |
| FIRST | Function | Returns first index in a collection. |
| LAST | Function | Returns last index in a collection. |
| COUNT | Function | Returns number of elements in a collection. |
| LIMIT | Function | Returns maximum number of elements that a collection can have. |
| PRIOR | Function | Returns the index that precedes specified index. |
| NEXT | Function | Returns the index that succeeds specified index. |
/****************** record and associative array ****************/
set serveroutput on
DECLARE
TYPE rek_type IS RECORD(f1 INTEGER DEFAULT 10, f2 emp.ename%TYPE); -- type declaration
rec rek_type; -- variable definition
TYPE tab_type IS TABLE OF VARCHAR2(30) INDEX BY BINARY_INTEGER; -- associative array type
tab tab_type; -- variable of type assoc. array
rec_dept dept%ROWTYPE; -- record variable
TYPE tab_type2 IS TABLE OF rec_dept%ROWTYPE INDEX BY BINARY_INTEGER; -- array of records
tab2 tab_type2;
BEGIN
rec.f2 := 'KING';
dbms_output.put_line(rec.f1||' -- '||rec.f2); -- default value
tab(1) := 'Bubu'; tab(2) := 'Baba'; tab(3) := 'Bobo';
FOR i IN tab.FIRST .. tab.LAST LOOP
dbms_output.put_line(tab(i));
END LOOP;
SELECT * INTO rec_dept FROM dept WHERE deptno = 10;
tab2(1) := rec_dept;
dbms_output.put_line(tab2(1).deptno||' -- '||tab2(1).dname||' -- '||tab2(1).loc);
END;
/
Results: ----------- 10 -- KING Bubu Baba Bobo 10 -- ACCOUNTING -- NEW YORK
Index-By Table:
- An index-by table (also called an associative array) is a set of key-value pairs. Each key is unique and is used to locate the corresponding value.
- The key can be either an integer or a string.
- An index-by table is created using the following syntax. Here, we are creating an index-by table named salary_list, the keys of which will be of the subscript_type and associated values will be of the element_type.
Nested Tables:
- A nested table is like a one-dimensional array with an arbitrary number of elements. However, a nested table differs from an array in the following aspects—
- An array has a declared number of elements, but a nested table does not. The size of a nested table can increase dynamically.
- An array is always dense, i.e., it always has consecutive subscripts. A nested array is dense initially, but it can become sparse when elements are deleted from it.