Examples dealing with cursors and tables Please use speaker notes for additional information!
Data - employeez SQL> DESC employeez; Name Null? Type EMPLOYEE_ID NOT NULL NUMBER(4) LAST_NAME VARCHAR2(15) FIRST_NAME VARCHAR2(15) MANAGER_ID NUMBER(4) SALARY NUMBER(7,2) SQL> select * from employeez; EMPLOYEE_ID LAST_NAME FIRST_NAME MANAGER_ID SALARY SMITH JOHN ALLEN KEVIN DOYLE JEAN DENNIS LYNN BAKER LESLIE WARD CYNTHIA PETERS DANIEL SHAW KAREN DUNCAN SARAH LANGE GREGORY JONES TERRY ALBERTS CHRIS PORTER RAYMOND LEWIS RICHARD MARTIN KENNETH SOMMERS DENISE BLAKE MARION CLARK CAROL SCOTT DONALD WEST LIVIA FISHER MATTHEW ROSS PAUL KING FRANCIS TURNER MARY ADAMS DIANE JAMES FRED FORD JENNIFER ROBERTS GRACE DOUGLAS MICHAEL MILLER BARBARA JENSEN ALICE MURRAY JAMES rows selected.
SET SERVEROUTPUT ON DECLARE TYPE t_ManagerTable IS TABLE OF employeez.manager_id%TYPE INDEX BY BINARY_INTEGER; v_ManagerIdTable t_ManagerTable; CURSOR c_Employee1 IS SELECT manager_id FROM employeez; v_ManagerId employeez.manager_id%TYPE; v_FoundFlag BOOLEAN; v_LoopCnt BINARY_INTEGER BEGIN OPEN c_Employee1; LOOP FETCH c_Employee1 INTO v_ManagerId; EXIT WHEN c_Employee1%NOTFOUND; v_FoundFlag := FALSE; v_LoopCnt := 1; WHILE v_LoopCnt <= v_ManagerIdTable.COUNT LOOP IF v_ManagerId = v_ManagerIdTable(v_LoopCnt) THEN v_FoundFlag := TRUE; EXIT; END IF; v_LoopCnt := v_LoopCnt + 1; END LOOP; IF v_FoundFlag = FALSE THEN v_ManagerIdTable(v_ManagerIdTable.COUNT + 1) := v_ManagerId; END IF; END LOOP; CLOSE c_Employee1; FOR i IN 1..v_ManagerIdTable.COUNT LOOP dbms_output.put_line(v_ManagerIdTable(i)); END LOOP; END; / SET SERVEROUTPUT OFF SQL> edit pay_raise_1a pay_raise_1a This table will hold unique manager ids. The processing determines uniqueness and updates the table. The cursor will hold all manager_id. The open fills the c_Employee1 cursor. While loop to check for unique manager ids. If a unique is found, the flag is set. Note the EXIT which takes you out of the loop ahead of schedule if the flag is set. If after exiting the unique loop, the flag is FALSE it means that a match was not found ad so the manager id is moved to the table. The outer LOOP…END LOOP is executed until the cursor is empty.
pay_raise_1a Logic analysis: The table, the cursor and the variables needed for processing were set up in the DECLARE. The OPEN statement fills the cursor according to the specifications in the SELECT statement within the cursor. The outer LOOP is entered and a record is FETCHed from the cursor and the manager_id is placed in the variable v_ManagerId. If the fetch was unsuccessful the outer loop is excited and cursor is closed prior to executing the FOR loop. If the FETCH was successful, the v_FoundFlag is set to FALSE and the v_LoopCnt is initialized at 1. Now the inner WHILE LOOP is entered as long as the v_LoopCt is less than or equal to the table count. The first time the WHILE loop is not entered because v_LoopCt is 1 and the COUNT of the elements in the table is 0. It will drop through the LOOP and reach the IF v_FoundFlag = FALSE statement. The flag is false so the element is added to the table COUNT + 1 which means it goes in as element 1. To follow this logic a little more, please look at the next slide.
COUNT before WHILE 0 COUNT before WHILE 1 COUNT in WHILE before IF 1 LOOP FETCH c_Employee1 INTO v_ManagerId; EXIT WHEN c_Employee1%NOTFOUND v_FoundFlag := FALSE; v_LoopCnt := 1; dbms_output.put_line('COUNT before WHILE ' || v_ManagerIdTable.COUNT); WHILE v_LoopCnt <= v_ManagerIdTable.COUNT LOOP dbms_output.put_line('COUNT in WHILE before IF ' || v_ManagerIdTable.COUNT); IF v_ManagerId = v_ManagerIdTable(v_LoopCnt) THEN v_FoundFlag := TRUE; EXIT; END IF; v_LoopCnt := v_LoopCnt + 1; END LOOP; IF v_FoundFlag = FALSE THEN v_ManagerIdTable(v_ManagerIdTable.COUNT + 1) := v_ManagerId; END IF; END LOOP; SQL> SELECT manager_id 2 FROM employeez; MANAGER_ID is written to the table Note that the logic does not enter the while loop. Because v_LoopCnt is not <= v_ManagerIdTable.COUNT. I am testing to see if 1 <= 0 and it isn’t. Logic - first fetch
COUNT before WHILE 1 COUNT in WHILE before IF 1 LOOP FETCH c_Employee1 INTO v_ManagerId; EXIT WHEN c_Employee1%NOTFOUND v_FoundFlag := FALSE; v_LoopCnt := 1; dbms_output.put_line('COUNT before WHILE ' || v_ManagerIdTable.COUNT); WHILE v_LoopCnt <= v_ManagerIdTable.COUNT LOOP dbms_output.put_line('COUNT in WHILE before IF ' || v_ManagerIdTable.COUNT); IF v_ManagerId = v_ManagerIdTable(v_LoopCnt) THEN v_FoundFlag := TRUE; EXIT; END IF; v_LoopCnt := v_LoopCnt + 1; END LOOP; IF v_FoundFlag = FALSE THEN v_ManagerIdTable(v_ManagerIdTable.COUNT + 1) := v_ManagerId; END IF; END LOOP; SQL> SELECT manager_id 2 FROM employeez; MANAGER_ID is written to the table since the flag is FALSE. Note that the logic does enter the while loop. Because v_LoopCnt is <= v_ManagerIdTable.COUNT. I am testing to see if 1 <= 1 and it is. Logic - second fetch The IF statement was never equal because we were checking 7698 with the only thing in the table which was Therefore the flag stayed FALSE.
LOOP FETCH c_Employee1 INTO v_ManagerId; EXIT WHEN c_Employee1%NOTFOUND v_FoundFlag := FALSE; v_LoopCnt := 1; dbms_output.put_line('COUNT before WHILE ' || v_ManagerIdTable.COUNT); WHILE v_LoopCnt <= v_ManagerIdTable.COUNT LOOP dbms_output.put_line('COUNT in WHILE before IF ' || v_ManagerIdTable.COUNT); IF v_ManagerId = v_ManagerIdTable(v_LoopCnt) THEN v_FoundFlag := TRUE; EXIT; END IF; v_LoopCnt := v_LoopCnt + 1; END LOOP; IF v_FoundFlag = FALSE THEN v_ManagerIdTable(v_ManagerIdTable.COUNT + 1) := v_ManagerId; END IF; END LOOP; SQL> SELECT manager_id 2 FROM employeez; MANAGER_ID is written to the table since the flag is FALSE. Note that the logic does enter the while loop. Because v_LoopCnt is <= v_ManagerIdTable.COUNT. It goes through the loop twice comparing 7839 (the new number) to the numbers already in the table: 7903 and Logic - third fetch The IF statement was never equal because we were checking 7839 with the two things in the table: 7902 and Therefore the flag stayed FALSE. COUNT before WHILE 2 COUNT in WHILE before IF 2
LOOP FETCH c_Employee1 INTO v_ManagerId; EXIT WHEN c_Employee1%NOTFOUND v_FoundFlag := FALSE; v_LoopCnt := 1; dbms_output.put_line('COUNT before WHILE ' || v_ManagerIdTable.COUNT); WHILE v_LoopCnt <= v_ManagerIdTable.COUNT LOOP dbms_output.put_line('COUNT in WHILE before IF ' || v_ManagerIdTable.COUNT); IF v_ManagerId = v_ManagerIdTable(v_LoopCnt) THEN v_FoundFlag := TRUE; EXIT; END IF; v_LoopCnt := v_LoopCnt + 1; END LOOP; IF v_FoundFlag = FALSE THEN v_ManagerIdTable(v_ManagerIdTable.COUNT + 1) := v_ManagerId; END IF; END LOOP; SQL> SELECT manager_id 2 FROM employeez; MANAGER_ID Nothing is written to the table since the flag is TRUE. Note that the logic does enter the while loop. Because v_LoopCnt is <= v_ManagerIdTable.COUNT. It goes through the loop 3 times comparing 7839 (the new number) to the numbers already in the table: 7903, 7698, & It finds a match on the third check and sets the flag to TRUE. Logic - fourth fetch The IF statement was equal on the third check because we were checking 7839 with the three things in the table: 7902,7698 & On the last check there is a match so the flag is set to TRUE and the loop is exited.. COUNT before WHILE 3 COUNT in WHILE before IF 3
LOOP FETCH c_Employee1 INTO v_ManagerId; EXIT WHEN c_Employee1%NOTFOUND v_FoundFlag := FALSE; v_LoopCnt := 1; dbms_output.put_line('COUNT before WHILE ' || v_ManagerIdTable.COUNT); WHILE v_LoopCnt <= v_ManagerIdTable.COUNT LOOP dbms_output.put_line('COUNT in WHILE before IF ' || v_ManagerIdTable.COUNT); IF v_ManagerId = v_ManagerIdTable(v_LoopCnt) THEN v_FoundFlag := TRUE; EXIT; END IF; v_LoopCnt := v_LoopCnt + 1; END LOOP; IF v_FoundFlag = FALSE THEN v_ManagerIdTable(v_ManagerIdTable.COUNT + 1) := v_ManagerId; END IF; END LOOP; SQL> SELECT manager_id 2 FROM employeez; MANAGER_ID Nothing is written to the table since the flag is TRUE. Note that the logic does enter the while loop. Because v_LoopCnt is <= v_ManagerIdTable.COUNT. It goes through the loop 3 times comparing 7839 (the new number) to the numbers already in the table: 7903, 7698, & It finds a match on the third check and sets the flag to TRUE. Logic - fifth fetch The IF statement was equal on the third check because we were checking 7839 with the three things in the table: 7902,7698 & On the last check there is a match so the flag is set to TRUE and the loop is exited.. COUNT before WHILE 3 COUNT in WHILE before IF 3
LOOP FETCH c_Employee1 INTO v_ManagerId; EXIT WHEN c_Employee1%NOTFOUND v_FoundFlag := FALSE; v_LoopCnt := 1; dbms_output.put_line('COUNT before WHILE ' || v_ManagerIdTable.COUNT); WHILE v_LoopCnt <= v_ManagerIdTable.COUNT LOOP dbms_output.put_line('COUNT in WHILE before IF ' || v_ManagerIdTable.COUNT); IF v_ManagerId = v_ManagerIdTable(v_LoopCnt) THEN v_FoundFlag := TRUE; EXIT; END IF; v_LoopCnt := v_LoopCnt + 1; END LOOP; IF v_FoundFlag = FALSE THEN v_ManagerIdTable(v_ManagerIdTable.COUNT + 1) := v_ManagerId; END IF; END LOOP; SQL> SELECT manager_id 2 FROM employeez; MANAGER_ID Nothing is written to the table since the flag is TRUE. Note that the logic does enter the while loop. Because v_LoopCnt is <= v_ManagerIdTable.COUNT. It goes through the loop 2 times comparing 7839 (the new number) to the numbers already in the table: 7903, 7698 (never checks 7839). It finds a match on the second check and sets the flag to TRUE. Logic - sixth fetch The IF statement was equal on the second check because we were checking 7698 with the three things in the table: 7902,7698 & On the second check there is a match so the flag is set to TRUE and the loop is exited.. COUNT before WHILE 3 COUNT in WHILE before IF 3
Logic after close When there are no more records in the cursor - the FETCH does not fetch a record the EXIT based on %NOTFOUND is taken. At that point the logic drops to the END LOOP. The next statement executed is the CLOSE which closes the cursor. Finally, there is a FOR loop which is used for demonstration purposes. It goes through the processing and displays the content of the table. As can be seen from the output there are no duplicate numbers in the table. pay_raise_1a PL/SQL procedure successfully completed.
pay_raise_3 - part 1 DECLARE TYPE t_ManagerTable IS TABLE OF employeez.manager_id%TYPE INDEX BY BINARY_INTEGER; v_ManagerIdTable t_ManagerTable; CURSOR c_Employee1 IS SELECT manager_id FROM employeez; CURSOR c_Employee2 IS SELECT employee_id, salary FROM employeez FOR UPDATE OF salary; v_ManagerId employeez.manager_id%TYPE; v_EmployeeId employeez.employee_id%TYPE; v_Salary employeez.salary%TYPE; v_FoundFlag BOOLEAN; v_LoopCnt BINARY_INTEGER; v_TableIndx BINARY_INTEGER; BEGIN OPEN c_Employee1; LOOP FETCH c_Employee1 INTO v_ManagerId; EXIT WHEN c_Employee1%NOTFOUND; v_FoundFlag := FALSE; v_LoopCnt := 1; WHILE v_LoopCnt <= v_ManagerIdTable.COUNT LOOP IF v_ManagerId = v_ManagerIdTable(v_LoopCnt) THEN v_FoundFlag := TRUE; EXIT; END IF; v_LoopCnt := v_LoopCnt + 1; END LOOP; IF v_FoundFlag = FALSE THEN v_ManagerIdTable(v_ManagerIdTable.COUNT + 1) := v_ManagerId; END IF; END LOOP; CLOSE c_Employee1; SQL> edit pay_raise_3 I have added a second cursor to hold the records I am updating, a hold area for the employee id #, a work area for salary and an index. This is the logic that we looked at on previous slides to develop a table of unique manager id #s.
pay_raise_3 - part 2 OPEN c_Employee2; LOOP FETCH c_Employee2 INTO v_EmployeeId, v_Salary; EXIT WHEN c_Employee2%NOTFOUND; v_FoundFlag := FALSE; v_LoopCnt := 1; WHILE v_LoopCnt <= v_ManagerIdTable.COUNT LOOP IF v_EmployeeId = v_ManagerIdTable(v_LoopCnt) THEN v_FoundFlag := TRUE; EXIT; END IF; v_LoopCnt := v_LoopCnt + 1; END LOOP; IF v_FoundFlag = TRUE THEN v_Salary := 1.1 * v_Salary; ELSE v_Salary := 1.02 * v_Salary; END IF; UPDATE employeez SET salary = v_Salary WHERE CURRENT OF c_Employee2; END LOOP; CLOSE c_Employee2; COMMIT; END; / The inner WHILE loop is checking the employee id against the numbers in the manager to table to see if the employee is a manager. If they are the flag is set to true and the loop is exited. The open fills the cursor according to the select on the previous slide. The FETCH moves the data into the specified variables. If the flag is true it means the person is a manager and they are given a 10% raise. Non managers are given a 2% raise. Flag initialized at false and count at 1. The record is updated. Note the WHERE CURRENT OF pointing to the cursor. This assures that the record being processed is updated.
SQL> SELECT * FROM employeez; EMPLOYEE_ID LAST_NAME FIRST_NAME MANAGER_ID SALARY SMITH JOHN ALLEN KEVIN DOYLE JEAN DENNIS LYNN BAKER LESLIE WARD CYNTHIA PETERS DANIEL SHAW KAREN DUNCAN SARAH LANGE GREGORY JONES TERRY ALBERTS CHRIS PORTER RAYMOND LEWIS RICHARD MARTIN KENNETH SOMMERS DENISE BLAKE MARION CLARK CAROL SCOTT DONALD WEST LIVIA FISHER MATTHEW ROSS PAUL KING FRANCIS TURNER MARY ADAMS DIANE JAMES FRED FORD JENNIFER ROBERTS GRACE DOUGLAS MICHAEL MILLER BARBARA JENSEN ALICE MURRAY JAMES rows selected. pay_raise_3 PL/SQL procedure successfully completed. Pay_raise_3 Managers is not in the manager list so Smith gets a 2% raise going from 800 to was on the manager list so Baker got a 10% raise from 2200 to 2420.
Third version pay_raise_4 - part 1 DECLARE TYPE t_ManagerTable IS TABLE OF employeez.manager_id%TYPE INDEX BY BINARY_INTEGER; v_ManagerIdTable t_ManagerTable; CURSOR c_Employee1 IS SELECT manager_id FROM employeez; CURSOR c_Employee2 IS SELECT employee_id, salary FROM employeez FOR UPDATE OF salary; v_Salary employeez.salary%TYPE; v_FoundFlag BOOLEAN; v_LoopCnt BINARY_INTEGER; BEGIN -- Build a table of manager ID's FOR v_EmployeeData1 IN c_Employee1 LOOP v_FoundFlag := FALSE; v_LoopCnt := 1; WHILE (v_LoopCnt <= v_ManagerIdTable.COUNT) AND (v_FoundFlag = FALSE) LOOP IF v_EmployeeData1.manager_id = v_ManagerIdTable(v_LoopCnt) THEN v_FoundFlag := TRUE; END IF; v_LoopCnt := v_LoopCnt + 1; END LOOP; IF v_FoundFlag = FALSE THEN v_ManagerIdTable(v_ManagerIdTable.COUNT + 1) := v_EmployeeData1.manager_id; END IF; END LOOP; The cursor FOR loop defines the record implicitly in the FOR and links it to the cursor. The FOR automates the processing of the cursor. Note that the WHILE loop does not have an exit. The AND condition that checks to see if the loop should be executed because the count is not less than the number of elements and the flag is FALSE indicating no match yet. Note the use of the implicitly define record from the FOR.
-- Look in manager ID table to determine if employee is a manager -- If employee is a manager increase pay by 10% else increase pay by 2% FOR v_EmployeeData2 IN c_Employee2 LOOP v_FoundFlag := FALSE; v_LoopCnt := 1; WHILE (v_LoopCnt <= v_ManagerIdTable.COUNT) AND (v_FoundFlag = FALSE) LOOP IF v_EmployeeData2.employee_id = v_ManagerIdTable(v_LoopCnt) THEN v_FoundFlag := TRUE; END IF; v_LoopCnt := v_LoopCnt + 1; END LOOP; IF v_FoundFlag = TRUE THEN v_Salary := 1.1 * v_EmployeeData2.salary; ELSE v_Salary := 1.02 * v_EmployeeData2.salary; END IF; UPDATE employeez SET salary = v_Salary WHERE CURRENT OF c_Employee2; END LOOP; COMMIT; END; / Third version pay_raise_4 - part 2 CURSOR c_Employee2 IS SELECT employee_id, salary FROM employeez FOR UPDATE OF salary; This cursor was declared on the previous page - I am showing it here for reference. Note the use of the implicitly defined record in the IF statement. This name was implicitly defined in the FOR loop as the record name associated with the cursor. Therefore v_employeeData2.employee_id refers to the record in the cursor and specifically the employee_id field. (See the select above).
Output SQL> SELECT * FROM employeez; EMPLOYEE_ID LAST_NAME FIRST_NAME MANAGER_ID SALARY SMITH JOHN ALLEN KEVIN DOYLE JEAN DENNIS LYNN BAKER LESLIE WARD CYNTHIA PETERS DANIEL SHAW KAREN DUNCAN SARAH LANGE GREGORY JONES TERRY ALBERTS CHRIS PORTER RAYMOND LEWIS RICHARD MARTIN KENNETH SOMMERS DENISE BLAKE MARION CLARK CAROL SCOTT DONALD WEST LIVIA FISHER MATTHEW ROSS PAUL KING FRANCIS TURNER MARY ADAMS DIANE JAMES FRED FORD JENNIFER ROBERTS GRACE DOUGLAS MICHAEL MILLER BARBARA JENSEN ALICE MURRAY JAMES rows selected. pay_raise_4 PL/SQL procedure successfully completed.
SET SERVEROUTPUT ON DECLARE TYPE t_ManagerTable IS TABLE OF employeez.manager_id%TYPE INDEX BY BINARY_INTEGER; v_ManagerIdTable t_ManagerTable; CURSOR c_Employee1 IS SELECT employee_id, salary, manager_id FROM employeez ORDER BY manager_id FOR UPDATE OF salary; v_Salary employeez.salary%TYPE; v_FoundFlag BOOLEAN; v_LoopCnt BINARY_INTEGER; BEGIN -- Build a table of manager ID's FOR v_EmployeeData1 IN c_Employee1 LOOP IF v_ManagerIdTable.COUNT = 0 OR v_EmployeeData1.manager_id != v_ManagerIdTable(v_ManagerIdTable.COUNT) THEN v_ManagerIdTable(v_ManagerIdTable.COUNT + 1) := v_EmployeeData1.manager_id; END IF; END LOOP; Pay_raise_5 - part 1 If there is nothing in the manager table (count = 0) or if the manager of the current employee is not = to the element in the table then the element is added to the table. See the next slide for details on this IF.
IF from part1 EMPLOYEE_ID MANAGER_ID SQL> SELECT employee_id, manager_id 2 FROM employeez 3 ORDER BY manager_id; FOR v_EmployeeData1 IN c_Employee1 LOOP IF v_ManagerIdTable.COUNT = 0 OR v_EmployeeData1.manager_id != v_ManagerIdTable(v_ManagerIdTable.COUNT) THEN v_ManagerIdTable(v_ManagerIdTable.COUNT + 1) := v_EmployeeData1.manager_id; END IF; END LOOP; First record: v_ManagerIdTable.Count = 0 so 7505 is put in table. (Note that COUNT is incremented by 1.) Second record: count is not 0 and v_Employee Data1.manager_id is equal to v_ManagerIdTable(v_ManagerIdTable.COUNT) so no addition is made to table. Third record through Fifth record same as Second record Sixth record count is not 0 BUT v_EmployeeData1.manager_id is NOT equal to v_ManagerIdTable(v_ManagerIdTable.COUNT so 7506 is added to the table. Table
-- Look in manager ID table to determine if employee is a manager -- If employee is a manager increase pay by 10% else increase pay by 2% FOR v_EmployeeData2 IN c_Employee1 LOOP v_FoundFlag := FALSE; v_LoopCnt := 1; WHILE (v_LoopCnt <= v_ManagerIdTable.COUNT) AND (v_FoundFlag = FALSE) LOOP IF v_EmployeeData2.employee_id = v_ManagerIdTable(v_LoopCnt) THEN v_FoundFlag := TRUE; END IF; v_LoopCnt := v_LoopCnt + 1; END LOOP; IF v_FoundFlag = TRUE THEN v_Salary := 1.1 * v_EmployeeData2.salary; ELSE v_Salary := 1.02 * v_EmployeeData2.salary; END IF; UPDATE employeez SET salary = v_Salary WHERE CURRENT OF c_Employee1; END LOOP; COMMIT; END; / SET SERVEROUTPUT OFF Pay_raise_5 - part 2 Note that on the previous slide we had FOR v_EmployeeData1. This is done to differentiate the uses.
SQL> SELECT * FROM employeez; EMPLOYEE_ID LAST_NAME FIRST_NAME MANAGER_ID SALARY SMITH JOHN ALLEN KEVIN DOYLE JEAN DENNIS LYNN BAKER LESLIE WARD CYNTHIA PETERS DANIEL SHAW KAREN DUNCAN SARAH LANGE GREGORY JONES TERRY ALBERTS CHRIS PORTER RAYMOND LEWIS RICHARD MARTIN KENNETH SOMMERS DENISE BLAKE MARION CLARK CAROL SCOTT DONALD WEST LIVIA FISHER MATTHEW ROSS PAUL KING FRANCIS TURNER MARY ADAMS DIANE JAMES FRED FORD JENNIFER ROBERTS GRACE DOUGLAS MICHAEL MILLER BARBARA JENSEN ALICE MURRAY JAMES pay_raise_5 PL/SQL procedure successfully completed. output