What?
- PL/SQL implicitly declares a cursor for all SQL data manipulation statements
- Feature of PL/SQL that allows SQL syntax directly in a PL/SQL statement
- SQL Statement here is defined during compile time.
- PL/SQL static SQL statements:
SELECT, INSERT, UPDATE, DELETE,
andMERGE
COMMIT, ROLLBACK, SAVEPOINT, SET TRANSACTION
LOCK TABLE
- A PL/SQL static SQL statement can have a PL/SQL identifier wherever its SQL counterpart can have a placeholder for a bind argument.
- PseudoColumns: behave like a table column, but it is not stored in the table
- CURRVAL and NEXTVAL
- LEVEL
- OBJECT_VALUE
- ROWID and ROWNUM
Cursors
- Pointer to a private SQL area that stores information about processing a specific SELECT or DML statement.
- Session cursors live in session memory until the session ends
- A Session cursor can be implicit or explicit.
V$OPEN_CURSOR
lists session cursors that each user session has opened and parsed- A PL/SQL cursor variable lets you retrieve multiple rows from a stored subprogram.
- You can pass cursor variables as parameters in your 3GL application.
- With cursors, an SQL stmt can be reexecuted without including the parse stage.
- Repeated execution of the same SQL statements: can begin at the describe, define, bind, or run step, saving the repeated cost of opening cursors and parsing.
Check the performance characteristics of a cursor | |
---|---|
V$SQL_PLAN | Execution plan information for each child cursor loaded in the library cache |
V$SQL_PLAN_STATISTICS | Execution plan information for each child cursor loaded in the library cache |
V$SQL_PLAN_STATISTICS_ALL | Memory usage statistics for row sources that use SQL memory (sort or hash-join). This view concatenates information in V$SQL_PLAN with execution statistics from V$SQL_PLAN_STATISTICS and V$SQL_WORKAREA |
How many cursors can a session maintain?
- Depends on: The amount of memory available to the session
- Depends on: The value of the initialization parameter
OPEN_CURSORS
Implicit cursors |
- Also called SQL Cursor
- PL/SQL opens an implicit cursor every time you run a SELECT or DML statement
%ROWCOUNT
attribute: stores the number of rows affected by a DML.
- You cannot control an implicit cursor, but you can get information from its attributes
- SQLattribute always refers to the most recently run SELECT or DML statement. If no such statement has run, the value of SQLattribute is NULL.
- Cursor attribute values remain available until another SELECT or DML statement runs.
Implicit Cursor Attributes | |
---|---|
SQL%ISOPEN (Is the Cursor Open?) |
|
SQL%FOUND and SQL%NOTFOUND (Were any rows affected?) |
create table dept_temp as select * from departments; create or replace procedure p ( dept_no number) authid definer as begin delete from dept_temp where department_id = dept_no; If sql%found then dbms_output.put_line('delete succeeded. dept_no: ' || dept_no); else dbms_output.put_line('No Dept_no: '|| dept_no); end if; end; / SQL> set serveroutput on SQL> exec p(400); anonymous block completed No Dept_no: 400
|
SQL%ROWCOUNT (how many rows affected?) |
SQL> set serveroutput on SQL> declare mgr_no number(6) := 122; begin delete from emp_temp where manager_id = mgr_no; dbms_output.put_line ('number of employees deleted: ' || to_char(sql%rowcount)); end; / anonymous block completed number of employees deleted: 8 set serveroutput on declare n number; begin dbms_output.put_line('Select.. ---------'); select count(*) into n from employees; dbms_output.put_line('SQL%ROWCOUNT: [' || SQL%ROWCOUNT ||'] rows returned'); dbms_output.put_line('Update.. ---------'); update employees set salary = salary*1.10 where job_id = 'SA_REP'; dbms_output.put_line('SQL%ROWCOUNT: [' || SQL%ROWCOUNT ||'] rows updated'); end; anonymous block completed Select.. --------- SQL%ROWCOUNT: [1] rows returned Update.. --------- SQL%ROWCOUNT: [30] rows updated |
SQL%BULK_ROWCOUNT |
set serveroutput on declare type NumList is table of number; depts numlist := NumList(30, 50, 60); begin forall j in depts.first..depts.last delete from emp_temp where department_id = depts(j); for i in depts.first..depts.last loop dbms_output.put_line( 'Statement #' || i || ' deleted ' || sql%bulk_rowcount(i) || 'rows.'); end loop; dbms_output.put_line('Total rows deleted: ' || sql%rowcount); end; / anonymous block completed Statement #1 deleted 0rows. Statement #2 deleted 45rows. Statement #3 deleted 5rows. Total rows deleted: 50 |
SQL%BULK_EXCEPTIONS |
create or replace procedure p authid definer as type deplist is table of number; depts deplist := deplist(40, 90, 100); type emptab is table of employees.employee_id%type; type deptab is table of employees.department_id%type; enums emptab; dnums deptab; errm varchar2(100); bad_stmt_no pls_integer; bad_deptno emp_temp.department_id%type; bad_empno emp_temp.employee_id%type; bad_email emp_temp.email%type; dml_errors exception; pragma exception_init(dml_errors, -24381); begin select employee_id, department_id bulk collect into enums, dnums from emp_temp where department_id in (depts(1), depts(2), depts(3)); forall j in enums.first..enums.last save exceptions update emp_temp set email = email || '@dep' || dnums(j) || '.company.com' where employee_id = enums(j); exception when dml_errors then for i in 1..sql%bulk_exceptions.count loop errm := sqlerrm(-(sql%bulk_exceptions(i).error_code)); dbms_output.put_line(error_message); bad_stmt_no := sql%bulk_exceptions(i).error_index; dbms_output.put_line('Bad stmt #: '|| bad_stmt_no); bad_empno := enums(bad_stmt_no); dbms_output.put_line('Bad empno #: '|| bad_empno); bad_deptno := dnums(bad_stmt_no); select email into bad_email from emp_temp where employee_id = bad_empno; dbms_output.put_line('Bad email #' || i || ' : '|| bad_email); end loop; -- commit; when others then dbms_output.put_line('other error'); raise; end; set serveroutput on; exec p; anonymous block completed ORA-12899: value too large for column (actual: , maximum: ) Bad stmt #: 2 Bad empno #: 101 Bad email #1 : NKOCHHAR (...) ORA-12899: value too large for column (actual: , maximum: ) Bad stmt #: 5 Bad empno #: 109 Bad email #3 : DFAVIET (...) |
Using SELECT INTO: Implicit cursor
- May return a single row - (here select directly into a scalar or record)
- you may also use WHERE ROWNUM = n to select a specific row.
- Or return multiple rows: here you need to use Select xxx BULK COLLECT INTO collection;
Returning a single row:
DECLARE bonus NUMBER(8,2); BEGIN SELECT salary * 0.10 INTO bonus FROM employees WHERE employee_id = 100; END; DBMS_OUTPUT.PUT_LINE('bonus = ' || TO_CHAR(bonus));returning into a record
SELECT select_list INTO record_variable_name FROM table_or_view_name; DECLARE TYPE RecordTyp IS RECORD ( last employees.last_name%TYPE id employees.employee_id%TYPE ); rec1 RecordTyp; BEGIN SELECT last_name, employee_id INTO rec1 FROM employees WHERE job_id = 'AD_PRES'; DBMS_OUTPUT.PUT_LINE ('Employee #' || rec1.id || ' = ' || rec1.last); END; /Returning multiple rows: BULK COLLECT clause
DECLARE TYPE NumTab IS TABLE OF employees.employee_id%TYPE; TYPE NameTab IS TABLE OF employees.last_name%TYPE; enums NumTab; names NameTab; PROCEDURE print_first_n (n POSITIVE) IS BEGIN IF enums.COUNT = 0 THEN DBMS_OUTPUT.PUT_LINE ('Collections are empty.'); ELSE DBMS_OUTPUT.PUT_LINE ('First ' || n || ' employees:'); FOR i IN 1 .. n LOOP DBMS_OUTPUT.PUT_LINE ( ' Employee #' || enums(i) || ': ' || names(i)); END LOOP; END IF; END; BEGIN SELECT employee_id, last_name BULK COLLECT INTO enums, names FROM employees ORDER BY employee_id; print_first_n(3); print_first_n(6); END; /
Using RETURNING INTO clause on a DML
SQL> create table emp as select employee_id, last_name, salary from employees; set serveroutput on declare type emprec is record (empid emp.employee_id%type, lname emp.last_name%type, sal emp.salary%type); vemprec emprec; begin delete from emp where employee_id = 188 returning employee_id, last_name, salary INTO vemprec; dbms_output.put_line('the record deleted was: '); dbms_output.put_line(vemprec.lname || 'id: '|| vemprec.empid ||' sal: '|| vemprec.sal); exception when others then dbms_output.put_line(sqlerrm); end; / anonymous block completed the record deleted was: Chungid: 188 sal: 3800
Cursor FOR LOOP
- Use with either implicit or explicit cursor.
- The cursor FOR LOOP statement implicitly declares its loop index as a %ROWTYPE record variable of the type that its cursor returns.
- Implicit cursor FOR LOOP stmt
- SELECT stmt inside the FOR LOOP
set serveroutput on begin for item in ( select last_name, job_id from employees where job_id like '%CLERK%' and manager_id > 100 order by last_name) loop dbms_output.put_line ('Name = '|| item.last_name || ', Job = ' || item.job_id); end loop; end; anonymous block completed Name = Atkinson, Job = ST_CLERK Name = Baida, Job = PU_CLERK Name = Bell, Job = SH_CLERK (...)
- explicit cursor FOR LOOP
- Move the select statement to the declaration section and explicitly delcare a cursor.
set serveroutput on declare cursor c1 is select last_name, job_id from employees where job_id like '%CLERK%' and manager_id > 100 order by last_name; begin for item in c1 loop dbms_output.put_line ('Name = '|| item.last_name || ', Job = ' || item.job_id); end loop; end; anonymous block completed Name = Atkinson, Job = ST_CLERK Name = Baida, Job = PU_CLERK Name = Bell, Job = SH_CLERK (...)
- Cursor FOR LOOP with parameters
set serveroutput on declare cursor c1 (job employees.job_id%type, max_wage number) is select * from employees where job_id = job and salary > max_wage; begin for person in c1 ('ST_CLERK', 3000) loop dbms_output.put_line( 'Name = '|| person.last_name || ', salary = ' || person.salary ||', Job Id = '|| person.job_id); end loop; end; anonymous block completed Name = Nayer, salary = 3200, Job Id = ST_CLERK Name = Bissot, salary = 3300, Job Id = ST_CLERK (...)
Cursors with subqueries
set serveroutput on declare cursor c1 is select t1.department_id, department_name, staff from departments t1, ( select department_id, count(*) as staff from employees group by department_id ) t2 where (t1.department_id = t2.department_id) and staff >= 5 order by staff; begin for dept in c1 loop dbms_output.put_line ('Department = ' || dept.department_name || ', staff = ' || dept.staff); end loop; end; anonymous block completed Department = IT, staff = 5 Department = Finance, staff = 6 Department = Purchasing, staff = 6 Department = Administration, staff = 7 Department = Sales, staff = 34 Department = Shipping, staff = 45
Explicit cursors |
- Also called a named cursor, since unlike implicit ones, an explicit cursor can be referenced by its name
- is a named pointer to a private SQL area that stores information for processing a specific query or DML statement
- Before using an explicit cursor, you must declare (cursor specification) and define it (cursor body).
- You cannot assign a value to an explicit cursor, use it in an expression, or use it as a formal subprogram parameter or host variable.
- You can do those things with a cursor variable.
Explicit cursor declaration, which only declares a cursor:
An explicit cursor definition has this syntax:
CURSOR cursor_name [ parameter_list ] RETURN return_type;
An explicit cursor definition has this syntax:
CURSOR cursor_name [ parameter_list ]
[ RETURN return_type ] IS select_statement;
DECLARE CURSOR c1 RETURN departments%ROWTYPE; -- Declare c1 CURSOR c2 IS -- Declare and define c2 SELECT employee_id, job_id, salary FROM employees WHERE salary > 2000; CURSOR c1 RETURN departments%ROWTYPE IS -- Define c1, SELECT * FROM departments -- repeating return type WHERE department_id = 110; BEGIN NULL; END;
OPEN, FETCH, and CLOSE
- Process multiple result sets in parallel, using multiple cursors.
- Process multiple rows in a single loop iteration, skip rows, or split the processing into multiple loops.
- Specify the query in one PL/SQL unit but retrieve the rows in another.
Managing Explicit Cursors | |
---|---|
OPEN | - Allocates database resources - Process the query (identify result set and lock rows (if FOR UPDATE clause is there) - Position cursor before the first row of the result set. |
FETCH | FETCH cursor_name INTO into_clause - retrieves the current row of the result set, stores the column values of that row into the variables or record, and advances the cursor to the next row. - PL/SQL does not raise an exception when a FETCH statement returns no rows. To detect this, use the attribute %NOTFOUND. |
CLOSE | - After closing a cursor, you cannot fetch records from its result set or reference its attributes. If you try, PL/SQL raises the predefined exception INVALID_CURSOR. - You can reopen a closed cursor. You must close an explicit cursor before you try to reopen it. Otherwise, PL/SQL raises the predefined exception CURSOR_ALREADY_OPEN. |
set serveroutput on declare cursor c1 is select last_name, job_id from employees where regexp_like(job_id, 'S[HT]_CLERK') order by last_name; v_lastname employees.last_name%type; v_jobid employees.job_id%type; cursor c2 is select * from employees where regexp_like(job_id, '[ACADFIMKSA]_M[ANGR]') order by job_id; v_employees employees%rowtype; begin open c1; loop fetch c1 into v_lastname, v_jobid; exit when c1%notfound; dbms_output.put_line(RPAD(v_lastname, 25, ' ') || v_jobid); end loop; close c1; dbms_output.put_line( '-----------------------------'); open c2; loop fetch c2 into v_employees; exit when c2%notfound; dbms_output.put_line(RPAD(v_employees.last_name, 25, ' ') || v_employees.job_id); end loop; close c2; end; anonymous block completed Atkinson ST_CLERK Bell SH_CLERK ... Walsh SH_CLERK ----------------------------- Higgins AC_MGR Glenn AC_MGR ... Zlotkey SA_MAN
Explicit cursors with parameter
set serveroutput on declare cursor c (job varchar2, max_sal number) IS select last_name, first_name, (salary-max_sal) overpayment from employees where job_id = job and salary > max_sal order by salary; procedure print_overpaid is ln_ employees.last_name%type; fn_ employees.first_name%type; overpayment_ employees.salary%type; begin loop fetch c into ln_, fn_, overpayment_; exit when c%notfound; dbms_output.put_line(ln_ || ', ' || fn_ || ' (by ' || overpayment_ || ')'); end loop; end print_overpaid; begin dbms_output.put_line('---------------------'); dbms_output.put_line('Overpaid Clerks:'); dbms_output.put_line('---------------------'); Open c('ST_CLERK', 5000); print_overpaid; close c; dbms_output.put_line('---------------------'); dbms_output.put_line('Overpaid Sales Rep:'); dbms_output.put_line('---------------------'); Open c('SA_REP', 10000); print_overpaid; close c; end;
Explicit Cursor Attributes Syntax: cursor_name%attribute | |
---|---|
%ISOPEN (Is the Cursor Open?) |
declare cursor c1 is select last_name, salary from employees where rownum < 11; v1 employees.last_name%type; v2 employees.salary%type; begin if not c1%isopen then open c1; end if; fetch c1 into v1, v2; if c1%isopen then close c1; end if; end; |
%FOUND and %NOTFOUND (Has a row been fetched?) |
set serveroutput on DECLARE CURSOR c1 IS SELECT last_name, salary FROM employees WHERE ROWNUM < 11 ORDER BY last_name; my_ename employees.last_name%TYPE; my_salary employees.salary%TYPE; BEGIN OPEN c1; LOOP FETCH c1 INTO my_ename, my_salary; IF c1%FOUND THEN -- fetch succeeded DBMS_OUTPUT.PUT_LINE('Name = ' || my_ename || ', salary = ' || my_salary); ELSE -- fetch failed DBMS_OUTPUT.PUT_LINE('---------------------'); DBMS_OUTPUT.PUT_LINE('C''est fini'); EXIT; END IF; END LOOP; END; / |
%ROWCOUNT (How may rows fetched?) |
|
Cursor variables |
- It is not limited to one query.
- You can assign a value to it.
- You can use it in an expression.
- It can be a subprogram parameter.
- It can be a host variable.
- It cannot accept parameters. But you can pass whole queries to it.
- To create a cursor variable, either declare a variable of the predefined type
SYS_REFCURSOR
or define aREF CURSOR
type and then declare a variable of that type.
Cursor variable declarations: Syntax:
TYPE type_name IS REF CURSOR [ RETURN return_type ]
DECLARE DECLARE -- strong type TYPE empcurtyp IS REF CURSOR RETURN employees%ROWTYPE; -- weak type TYPE genericcurtyp IS REF CURSOR; cursor1 empcurtyp; -- strong cursor variable cursor2 genericcurtyp; -- weak cursor variable my_cursor SYS_REFCURSOR; -- weak cursor variable BEGIN NULL; END;
Cursor Variable - Examples
Cursor Variable |
(1) Opens a cursor variable for a query that references the variable factor.
(2) Notice that the variable is evaluated only once: when the cursor is opened.
set serveroutput on declare sal employees.salary%type; sal_multiple employees.salary%type; factor integer := 2; -- declare cursor variable predefined type cv sys_refcursor; begin -- open cursor defining SQL stmt. -- once closed later, the cursor can be opened -- again with a different stmt Open cv for select salary, salary*factor from employees where job_id like 'AD_%'; loop --load the next row of data into variables -- if no rows left, exit loop fetch cv into sal, sal_multiple; exit when cv%notfound; dbms_output.put_line('factor = '|| factor); dbms_output.put_line('sal = '|| sal); dbms_output.put_line('sal_multiple = '|| sal_multiple); -- note that the variable factor is evaluated only one -- when the cursor is opened. After this, new variable -- values are not seen by the cursor unless it is -- closed and opened again. factor := factor + 1; end loop; close cv; end; anonymous block completed factor = 2 sal = 4400 sal_multiple = 8800 factor = 3 sal = 24000 sal_multiple = 48000 factor = 4 sal = 17000 sal_multiple = 34000 factor = 5 sal = 17000 sal_multiple = 34000
Passing cursor variables as parameter |
set serveroutput on -- Package specification. create or replace package emp_data as -- Defines public cursor variable type (strongly enforced) type empcurtype is ref cursor return employees%rowtype; -- define public procedures procedure open_emp_cv (emp_cv in out empcurtype); procedure list_emp_cv (emp_cv in empcurtype); procedure close_emp_cv (emp_cv in empcurtype); end emp_data; -- package body create or replace package body emp_data as -- procedure receives cursor var as parameter -- and writes on it. -- NOTICE: that the SQL is fixed. You can't change -- parameters without recompiling the package body. procedure open_emp_cv(emp_cv in out empcurtype) is begin open emp_cv for select * from employees where rownum <=5 order by hire_date; end open_emp_cv; -- procedure receives cursor var as parameter. -- read only. procedure list_emp_cv (emp_cv in empcurtype) is v_emp employees%rowtype; begin -- loop through all cursor records. -- exit loop when last row reached. -- each FETCH increments %ROWCOUNT attribute. loop fetch emp_cv into v_emp; exit when emp_cv%notfound; dbms_output.put_line('Oldest #' || emp_cv%rowcount ||': ' || v_emp.first_name ||' '|| v_emp.last_name || '. Hire date: '|| v_emp.hire_date); end loop; end list_emp_cv; procedure close_emp_cv (emp_cv in empcurtype) is begin if emp_cv%isopen then dbms_output.put_line('Closing cursor'); close emp_cv; end if; end close_emp_cv; end emp_data; -- anonymous block set serveroutput on declare -- declare cursor variable of the same type -- specified in the package spec. v_empcv emp_data.empcurtype; begin emp_data.open_emp_cv(v_empcv); emp_data.list_emp_cv(v_empcv); emp_data.close_emp_cv(v_empcv); end; anonymous block completed Oldest #1: Steven King. Hire date: 17-JUN-87 Oldest #2: Neena Kochhar. Hire date: 21-SEP-89 Oldest #3: Alexander Hunold. Hire date: 03-JAN-90 Oldest #4: Bruce Ernst. Hire date: 21-MAY-91 Oldest #5: Lex De Haan. Hire date: 13-JAN-93 Closing cursor
Using Cursor expression |
set serveroutput on declare type emp_cur_type is ref cursor; emp_cur emp_cur_type; dept_name departments.department_name%type; emp_name employees.last_name%type; cursor c1 is select department_name, cursor ( select e.last_name from employees e where e.department_id = d.department_id order by e.last_name ) employees from departments d where department_name like 'A%' order by department_name; begin open c1; loop fetch c1 into dept_name, emp_cur; exit when c1%notfound; dbms_output.put_line('Dept: ' || dept_name); loop fetch emp_cur into emp_name; exit when emp_cur%notfound; dbms_output.put_line('-- Employee: '|| emp_name); end loop; end loop; close c1; end; anonymous block completed Dept: Accounting -- Employee: Gietz -- Employee: Higgins Dept: Administration -- Employee: Fansom -- Employee: Fay -- Employee: Fergunson -- Employee: Glenn -- Employee: Hartstein -- Employee: Mavris -- Employee: Whalen
Using SELECT FOR UPDATE and FOR UPDATE Cursors |