Download presentation
Presentation is loading. Please wait.
Published byAlexina Lynch Modified over 9 years ago
1
1 Theory, Practice & Methodology of Relational Database Design and Programming Copyright © Ellis Cohen 2002-2008 Cursors These slides are licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License. For more information on how you may use them, please see http://www.openlineconsult.com/db
2
2 © Ellis Cohen 2001-2008 Overview of Lecture PL/SQL Cursors Cursor-Based Fetch & Scrollability Cursor Sensitivity Cursor-Based Update Cursor Variables & Parameters Nested Cursors
3
3 © Ellis Cohen 2001-2008 PL/SQL Cursors
4
4 © Ellis Cohen 2001-2008 Cursors as Iterators Iterators are programming language objects which iterate over some sort of collection. Cursors are a special kind of iterator built-in to SQL-based embedded languages which iterate over result sets (i.e. the result of a query)
5
5 © Ellis Cohen 2001-2008 Cursors DECLARE CURSOR curs IS SELECT deptno, count(*) AS knt FROM Emps WHERE job = 'ANALYST' GROUP BY deptno; BEGIN FOR aRec IN curs LOOP pl( aRec.deptno || ': ' || aRec.knt ); END LOOP; END; Separates query description from the use of the result set
6
6 © Ellis Cohen 2001-2008 Parameterized Cursors DECLARE CURSOR curs( aJob varchar ) IS SELECT deptno, count(*) AS knt FROM Emps WHERE job = aJob GROUP BY deptno; BEGIN FOR aRec IN curs( 'ANALYST' ) LOOP pl( aRec.deptno || ': ' || aRec.knt ); END LOOP; END;
7
7 © Ellis Cohen 2001-2008 Package Cursors BEGIN FOR aRec IN EmpPkg.DeptsByJobsCurs( :job ) LOOP pl( … ) END LOOP; EXCEPTION WHEN OTHERS THEN plerr(); END; Instantiates a cursor ShowDeptsByJob( :job ) PACKAGE BODY EmpPkg AS CURSOR DeptsByJobsCurs( theJob varchar ) IS SELECT deptno, count(*) AS knt FROM Emps WHERE job = theJob GROUP BY deptno; … Cursors can be defined in packages
8
8 © Ellis Cohen 2001-2008 Cursor-Based Fetch & Scrollability
9
9 © Ellis Cohen 2001-2008 Explicit Fetch Control DECLARE CURSOR curs … BEGIN FOR rec IN curs LOOP statements END LOOP; END; DECLARE CURSOR curs … rec curs%ROWTYPE; BEGIN OPEN curs; FETCH curs INTO rec; WHILE curs%FOUND LOOP statements FETCH curs INTO rec; END LOOP; CLOSE curs; END;
10
10 © Ellis Cohen 2001-2008 Fetching Example DECLARE CURSOR curs( aJob varchar ) IS SELECT ename, deptno FROM Emps WHERE job = aJob; rec curs%ROWTYPE; BEGIN OPEN curs('ANALYST'); LOOP FETCH curs INTO rec; EXIT WHEN curs%NOTFOUND; INSERT INTO CoolEmps VALUES rec; END LOOP; CLOSE curs; END;
11
11 © Ellis Cohen 2001-2008 Cursor Attributes %FOUND Before the first fetch from curs, curs%FOUND is NULL. Afterwards, it is TRUE if the last fetch returned a row, or FALSE if there are no rows left. %NOTFOUND Before the first fetch from curs, curs%FOUND is NULL. Afterwards, it is FALSE if the last fetch returned a row, or TRUE if there are no rows left. %ROWCOUNT curs%ROWCOUNT returns the number of rows fetched (0 if the cursor is not yet open) %ISOPEN curs%ISOPEN return TRUE if the cursor is open, FALSE if it is not
12
12 © Ellis Cohen 2001-2008 More Fetching Example DECLARE CURSOR c1 IS SELECT * FROM Emps1 ORDER BY empno; CURSOR c2 IS SELECT * FROM Emps2 ORDER BY empno; jemps EmpPkg.EmpList; e1 c1%ROWTYPE; e2 c2%ROWTYPE; BEGIN OPEN c1; FETCH c1 INTO e1; OPEN c2; FETCH c2 INTO e2; WHILE (c1%FOUND or c2%FOUND) LOOP IF (c2%NOTFOUND or (c1%FOUND and (e1.empno < e2.empno))) THEN jemps(jemps.count + 1) := e1; FETCH c1 INTO e1; ELSE jemps(jemps.count + 1) := e2; FETCH c2 INTO e2; END IF; END LOOP; CLOSE c1; CLOSE c2; EmpPkg.DoSomethingCoolWith( jemps ); END; What does this do?
13
13 © Ellis Cohen 2001-2008 Answer: More Fetching Example Emps1 and Emps2 are tables of employees, and we use the cursors c1 and c2 to iterate through them in order of their employee number. We compare the employees pointed to by c1 and c2, and take the employee with the smallest employee number, append it to the jemps array, and move that cursor forward to point to the next employee (in order) in the corresponding table. This is a MERGE, and jemps will contain all the employees, in order of their employee number. (SELECT empno FROM Emps1 UNION ALL (SELECT empno FROM Emps2) ORDER BY empno should be implemented internally in essentially the same way
14
14 © Ellis Cohen 2001-2008 Scrollability Can FETCH only move forward through the result set, or can it move backwards or be repositioned to arbitrary rows? Oracle: Forward-Only SQL Server: Based on options available when defining the cursor
15
15 © Ellis Cohen 2001-2008 Cursor Sensitivity
16
16 © Ellis Cohen 2001-2008 Cursor Sensitivity Once we start iterating through the rows selected by a cursor, are the membership or contents of those rows sensitive to database changes made by this user or other users? Oracle: NO SQL Server: Based on options available when defining the cursor
17
17 © Ellis Cohen 2001-2008 No Membership Sensitivity DECLARE CURSOR curs IS SELECT * from Emps WHERE job = 'ANALYST'; rec curs%ROWTYPE; BEGIN OPEN curs; DELETE Emps WHERE job = 'ANALYST'; COMMIT; LOOP FETCH curs INTO rec; EXIT WHEN curs%NOTFOUND; pl( rec.ename || ' ' || rec.deptno ); END LOOP; CLOSE curs; END; DELETE has no effect on output
18
18 © Ellis Cohen 2001-2008 No Content Sensitivity DECLARE CURSOR curs IS SELECT * from Emps WHERE job = 'ANALYST'; rec curs%ROWTYPE; BEGIN OPEN curs; UPDATE Emps SET deptno = 10 WHERE job = 'ANALYST'; COMMIT; LOOP FETCH curs INTO rec; EXIT WHEN curs%NOTFOUND; pl( rec.ename || ' ' || rec.deptno ); END LOOP; CLOSE curs; END; UPDATE has no effect on output
19
19 © Ellis Cohen 2001-2008 Cursor-Based Update
20
20 © Ellis Cohen 2001-2008 Cursor FOR UPDATE CURSOR curs IS SELECT sal FROM Emps WHERE deptno = 30 FOR UPDATE; Specifies that the cursor will be used to update the underlying table Emps
21
21 © Ellis Cohen 2001-2008 Cursored Update Model empno ename deptno sal comm 7499ALLEN301600300 7654MARTIN3012501400 7698BLAKE302850 7839KING105000 7844TURNER3015000 7986STERN501500 Emps 1600 1250 2850 1500 Result Set curs FOR UPDATE maintains the connection (using ROWIDS) between a tuple in the Result Set and the corresponding tuple in the table that was queried CURRENT OF curs
22
22 © Ellis Cohen 2001-2008 Programming with FOR UPDATE DECLARE CURSOR curs IS SELECT sal FROM Emps WHERE deptno = 30 FOR UPDATE; BEGIN FOR rec IN curs LOOP IF (...) THEN UPDATE Emps SET sal = sal * 1.05 WHERE CURRENT OF curs; END IF; END LOOP; END; enables the selected tuples to be updated through the cursor The tuple in Emps last fetched through the cursor Some complex test
23
23 © Ellis Cohen 2001-2008 Update Sensitivity DECLARE CURSOR curs IS SELECT * from Emps WHERE deptno = 30 FOR UPDATE; rec curs%ROWTYPE; BEGIN OPEN curs; UPDATE Emps SET sal = 100 WHERE deptno = 30; LOOP FETCH curs INTO rec; EXIT WHEN curs%NOTFOUND; UPDATE Emps SET sal = sal * 1.1 WHERE CURRENT OF curs; END LOOP; CLOSE curs; END; What will the value of sal be for tuples where deptno = 30? Also, if the cursor was scrollable, what salary value would be obtained when scrolling back to a previously updated tuple?
24
24 © Ellis Cohen 2001-2008 Cursor Variables & Parameters
25
25 © Ellis Cohen 2001-2008 Cursors vs Ref Cursors CURSOR curs IS SELECT * from Emps WHERE deptno = 30 FOR UPDATE; does NOT declare a cursor variable. It is more like a TYPE or PROCEDURE declaration. Although we can open and fetch from it OPEN curs; FETCH curs INTO aRec; There is no way to store an open cursor into a variable pass an open cursor as a parameter return an open cursor from a function That's what REF CURSORs are for!
26
26 © Ellis Cohen 2001-2008 Ref Cursors DECLARE TYPE EmpCursorType IS REF CURSOR RETURN Emps%ROWTYPE; cv EmpCursorType; rec cv%ROWTYPE; BEGIN IF :mtype = 'job' THEN OPEN cv FOR SELECT * FROM Emps WHERE (job LIKE :match); ELSIF :mtype = 'ename' THEN OPEN cv FOR SELECT * FROM Emps WHERE (ename LIKE :match); END IF; LOOP FETCH cv INTO rec; EXIT WHEN cv%NOTFOUND; pl( rec.ename || ' ' || rec.deptno ); END LOOP; CLOSE cv; END; Dynamically choose query to process Type checking
27
27 © Ellis Cohen 2001-2008 Weak Ref Cursors DECLARE TYPE EmpCursorType IS REF CURSOR; cv EmpCursorType; rec Emps%ROWTYPE; BEGIN IF :mtype = 'job' THEN OPEN cv FOR SELECT * FROM Emps WHERE (job LIKE :match); ELSIF :mtype = 'ename' THEN OPEN cv FOR SELECT * FROM Emps WHERE (ename LIKE :match); END IF; LOOP FETCH cv INTO rec; EXIT WHEN cv%NOTFOUND; pl( rec.ename || ' ' || rec.deptno ); END LOOP; CLOSE cv; END; Weak REF CURSORs do not specify a RETURN type. No early type checking Type checking at FETCH-time
28
28 © Ellis Cohen 2001-2008 SYS_REFCURSOR DECLARE cv SYS_REFCURSOR; rec Emps%ROWTYPE; BEGIN IF :mtype = 'job' THEN OPEN cv FOR SELECT * FROM Emps WHERE (job LIKE :match); ELSIF :mtype = 'ename' THEN OPEN cv FOR SELECT * FROM Emps WHERE (ename LIKE :match); END IF; LOOP FETCH cv INTO rec; EXIT WHEN cv%NOTFOUND; pl( rec.ename || ' ' || rec.deptno ); END LOOP; CLOSE cv; END; Built-in Weak REF CURSOR type
29
29 © Ellis Cohen 2001-2008 Returning REF CURSORs SQL> variable rc REFCURSOR; SQL> execute :rc := EmpPkg.GetEmpByJob( 'ANALYST' ); SQL> print :rc Useful hack FUNCTION GetEmpByJob( aJob varchar ) RETURN SYS_REFCURSOR IS cv SYS_REFCURSOR; BEGIN OPEN cv FOR SELECT * FROM Emps WHERE (job = aJob) or (aJob IS NULL); RETURN cv; END; In EmpPkg CURSOR declarations are simpler Functions that return cursors can do error checking and raise exceptions
30
30 © Ellis Cohen 2001-2008 Returning Result Sets SQL> variable rc REFCURSOR; SQL> execute :rc := EmpPkg.GetEmpByJob( 'ANALYST' ); SQL> print :rc EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO ------ ---------- --------- ---------- --------- ---------- ---------- ---------- 7788 SCOTT ANALYST 7566 19-APR-87 3000 20 7902 FORD ANALYST 7566 03-DEC-81 3000 20 SQL> execute :rc := EmpPkg.GetEmpByJob( null ); SQL> print :rc EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO ------ ---------- --------- ---------- --------- ---------- ---------- ---------- 7369 SMITH CLERK 7902 17-DEC-80 800 20 7499 ALLEN SALESMAN 7698 20-FEB-81 1600 300 30 7521 WARD SALESMAN 7698 22-FEB-81 1250 500 30 7566 JONES MANAGER 7839 02-APR-81 2975 20 7654 MARTIN SALESMAN 7698 28-SEP-81 1250 1400 30 7698 BLAKE MANAGER 7839 01-MAY-81 2850 30 7782 CLARK MANAGER 7839 09-JUN-81 2450 10 7788 SCOTT ANALYST 7566 19-APR-87 3000 20 7839 KING PRESIDENT 17-NOV-81 5000 10 7844 TURNER SALESMAN 7698 08-SEP-81 1500 0 30 7876 ADAMS CLERK 7788 23-MAY-87 1100 20 7900 JAMES CLERK 7698 03-DEC-81 950 30 7902 FORD ANALYST 7566 03-DEC-81 3000 20 7934 MILLER CLERK 7782 23-JAN-82 1300 10
31
31 © Ellis Cohen 2001-2008 Using a REF Cursor DECLARE cv SYS_REFCURSOR; rec EmpPkg.DeptKntRec; BEGIN cv := EmpPkg.GetDeptsByJobsCurs( :job ); LOOP FETCH cv INTO aRec; EXIT WHEN cv%NOTFOUND; -- use rec to build part of result page END LOOP; EXCEPTION WHEN OTHERS THEN … -- generate error page … -- return; END; … -- build rest of result page & return END; Returns a REF cursor ShowDeptsByJob( :job )
32
32 © Ellis Cohen 2001-2008 Defining & Opening a REF Cursor TYPE DeptKntRec IS RECORD ( deptno int, knt int ); FUNCTION GetDeptsByJobsCurs( theJob varchar ) RETURN SYS_REFCURSOR IS cv SYS_REFCURSOR; BEGIN OPEN cv FOR SELECT deptno, count(*) AS knt FROM Emps WHERE job = theJob GROUP BY deptno; RETURN cv; END; In EmpPkg
33
33 © Ellis Cohen 2001-2008 Nested Cursors
34
34 © Ellis Cohen 2001-2008 List Employees in Each Dept DECLARE fstr varchar(200); sep varchar(5); BEGIN FOR d IN (SELECT deptno,dname FROM Depts ORDER BY dname) LOOP fstr := d.dname; sep := ': '; FOR e IN (SELECT ename FROM Emps WHERE deptno = d.deptno) LOOP fstr := fstr || sep || e.ename; sep := ', '; END LOOP; pl( fstr ); END LOOP; END; ACCOUNTING: CLARK, KING, MILLER OPERATIONS RESEARCH: SMITH, JONES, SCOTT, ADAMS, FORD SALES: ALLEN, WARD, MARTIN, BLAKE, TURNER, JAMES
35
35 © Ellis Cohen 2001-2008 Nested Cursors ACCOUNTING dname CLARK CURSOR(…) KING MILLER OPERATIONS dname RESEARCH SMITH JONES SCOTT … (EMPTY) … dname CURSOR CURSOR(…)
36
36 © Ellis Cohen 2001-2008 Formatting with Nested Cursors DECLARE curs SYS_REFCURSOR := GetDeptEmpCursNest(); ncurs SYS_REFCURSOR; fstr varchar(200); sep char(2) ; nstr varchar(50); BEGIN LOOP FETCH curs INTO fstr, ncurs; EXIT WHEN curs%notfound; sep := ': '; LOOP FETCH ncurs INTO nstr; EXIT WHEN ncurs%notfound; fstr := fstr || sep || nstr; sep := ', '; END LOOP; pl( fstr ); END LOOP; END; Each tuple consists of a string (dname) plus an [opened] REF CURSOR Lists employees in each department
37
37 © Ellis Cohen 2001-2008 Building Nested Cursors FUNCTION GetDeptEmpCursNest RETURN SYS_REFCURSOR IS cv SYS_REFCURSOR; BEGIN OPEN cv FOR SELECT dname, CURSOR( SELECT ename FROM Emps e WHERE e.deptno = d.deptno) FROM Depts d ORDER BY dname; RETURN cv; END; / Nested CURSOR expression Each tuple consists of a string (dname) plus an [opened] REF CURSOR
Similar presentations
© 2024 SlidePlayer.com. Inc.
All rights reserved.