(8) Cursors and Static SQL


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, and MERGE
    • 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?)

  • Always returns FALSE. (Since implicit cursors always close after the statement runs.)
SQL%FOUND and SQL%NOTFOUND
(Were any rows affected?)
  • NULL: If no SELECT OR DML was executed.
  • TRUE: If SELECT or DML returned/affected +0 rows.
  • FALSE: If no 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
  • If SELECT INTO returns no rows: predefined exception NO_DATA_FOUND is raised immediately (before SQL%NOTFOUND can be checked).
  • SELECT INTO that invokes a SQL aggregate function (i.e. avg, sum, count, etc) always returns a value (possibly NULL). Here SQL%NOTFOUND is always FALSE.
SQL%ROWCOUNT
(how many rows affected?)
  • NULL: If no SELECT OR DML was executed.
  • N: N is the number of rows returned (select) or affected (dml).
  • FALSE: If no 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
  • Is like an associative array whose ith element is the number of rows affected by the ith DML statement in the most recently completed FORALL statement..
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
  • SQL%BULK_EXCEPTIONS is like an associative array of information about the DML statements that failed during the most recently run FORALL statement.
  • To allow a FORALL statement to continue even if some of its DML statements fail, include the SAVE EXCEPTIONS clause.
  • After the FORALL statement completes, PL/SQL raises a single exception for the FORALL statement (ORA-24381). In the exception handler for ORA-24381, you can get information about each individual DML statement failure from the implicit cursor attribute SQL%BULK_EXCEPTIONS.

  • SQL%BULK_EXCEPTIONS.COUNT:# of DML stmts that failed.
  • SQL%BULK_EXCEPTIONS(i).ERROR_INDEX: # of the ith failed DML stmt.
  • SQL%BULK_EXCEPTIONS(i).ERROR_CODE: error code for the ith failure.
  • SQLERRM(-(SQL%BULK_EXCEPTIONS(i).ERROR_CODE)): returns the associated error message
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:
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?)



  • Useful for: Checking if an explicit cursor is already open before trying to open it.
  • If you try to open an explicit cursor that is already open, PL/SQL raises the predefined exception CURSOR_ALREADY_OPEN. You must close an explicit cursor before you can reopen it.
  • Checking that an explicit cursor is open before you try to close it.
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?)
  • NULL: after the explicit cursor is opened but before the first fetch
  • TRUE: if the most recent fetch from the explicit cursor returned a row
  • FALSE: otherwise
  • %FOUND use to check if there is a fetched row to process.
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?)

  • Zero after the explicit cursor is opened but before the first fetch
  • Otherwise, the number of rows fetched
Cursor variables

Cursor variable is a pointer: its value is the address of an item, not the item itself. - Similar to an an explicit cursor, except that:
  • 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 a REF 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
(1) Package specification: Define public (a) cursor variable; (b) procedures to open and manipulate cursor. (2) Package body: Procedures that open the cursor for read and write (IN OUT). And list cursor contents (IN). (3) Anonymous block: declares cursor variable of the same type defined in package specification. (4) Anonymous block: Calls procedures to open and iteract through the cursor.
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

(11) Subprograms: Stored Functions







About Functions:
  • Although a function can return more than one value, it is not a good programming practice to do so.
  • Functions can accept parameters in three modes: IN, OUT, and IN OUT.
  • It IS NOT good programming practice to build functions with parameter modes other than IN because functions always return a value.
  • Functions having compilation errors will have an invalid status, and these functions cannot be invoked.
  • Host or bind variables CANNOT be referenced in the PL/SQL block of the function.
  • There can be more than one RETURN statement in the executable section of the function, but the function stops executing when it meets the first RETURN statement.

  • HEADING: must have a RETURN clause.
    • (A PROCEDURE SHOULD NOT have a RETURN clause).
    • A function heading can include these options:
    • DETERMINISTIC - tells optimizer to avoid redundant invocations. Asserts that EVERY call to the functions with the same values will lead to the same result.
    • PARALLEL_ENABLE - Make the function safe for use in slave sessions of parallel DML Evaluations.
    • PIPELINED -

  • EXECUTABLE section:
    • EVERY execution path MUST lead to a RETURN statement. (or PL/SQL issue a compile-time warning:.
    • "PLW-05005: subprogram F returns without value at line n."

  • RETURN statement: Is optional and NOT RECOMMENDED in procedures.
    • Procedure: the RETURN statement returns control to the invoker, where execution resumes immediately after the invocation. The RETURN statement cannot specify an expression.
    • Anonymous block: the RETURN statement exits its own block and all enclosing blocks. The RETURN statement cannot specify an expression.

set serveroutput on 
BEGIN
  BEGIN
    DBMS_OUTPUT.PUT_LINE('Inside inner block.');
    RETURN;
    DBMS_OUTPUT.PUT_LINE('Unreachable statement.');
  END;
  DBMS_OUTPUT.PUT_LINE('Inside outer block. Unreachable statement.');
END;















Invoking functions from SQL statements
  1. Function should be stored in the database.
  2. Function should have ONLY parameters of the IN mode.
  3. Return DATA TYPE should be compatible with SQL datatypes.
    • (i.e., BOOLEAN is NOT an SQL type, so a function returning BOOLEAN cannot be invoked from a SQL statement.)
    • (i.e., You cannot use PL/SQL data types, such as BOOLEAN, RECORD, or TABLE for returning values.)
  4. Must use POSITIONAL notation to pass values to the IN parameters.
  5. Function that MODIFY any DB Table CANNOT be used in SELECT statemnts
    • (attempts on the contrary will raise: "ORA-14551: cannot perform a DML operation inside a query")
    • the restriction on a function that it cannot issue any INSERT, UPDATE, and DELETE statements, when called from within a SELECT statement, will not stop you from creating a packaged function that violates this rule. The violation will be detected at run time.
  6. Functions used on DELETE or UPDATE cannot query or modify any database tables modified by that statements.
    • (attempts otherwise will lead to a MUTATING TABLE error)
    • (i.e. ORA-04091: table is mutating, trigger/function may not see it)
  7. Functions called from SQL statements CANNOT use:
    • TCL: transaction control language, such as COMMIT
    • SessionCL: session control language, such as SET ROLE.
    • SysCL: System control language, such as ALTER SYSTEM, and
    • DDL statements, such as CREATE.

Using Pragma RESTRICT_REFERENCES
  • PRAGMA RESTRICT_REFERENCES allow a packaged function to be used in a GROUP BY clause of a SELECT statement.
  • i.e. pragma restrict_references (check_emp, wnds, wnps, rnps, rnds);
  • The PRAGMA RESTRICT_REFERENCES indicates to the compiler that the function:
    • Writes No Database State (WNDS) – The function cannot issue any INSERT, UPDATE, or DELETE.
    • Writes No Package State (WNPS) – The function does not make any changes to the packaged variables.
    • Reads No Package State (RNPS) – The function reads no package constructs.
    • Reads No Database State (RNDS) – The function issues no SELECT statements.

Using pragma RESTRICT_REFERENCES
SQL> create table empsm (name varchar2(20), salary number);

SQL> create or replace package test_restrict_ref 
is
 function check_emp return number;
 pragma restrict_references (check_emp, wnds, wnps, rnps, rnds);
end test_restrict_ref;
/
PACKAGE BODY test_restrict_ref compiled

SQL> create or replace package body test_restrict_ref
is 
  function check_emp
    return number is
   sum_sal number := 0; 
  begin
   select avg(salary) into sum_sal   -- violates rdns
   from employees;
   insert into empsm                 -- violates wdns
   values ('New emp', sum_sal);
   return sum_sal;
  exception
    when no_data_found then
       sum_sal := 0;
       return sum_sal;
  end check_emp;
end;
/
PACKAGE BODY test_restrict_ref compiled
Warning: execution completed with warning


show errors;
3/3            PLS-00452: Subprogram 'CHECK_EMP' violates its associated pragma




Using the RESULT_CACHE option
RESULT_CACHE [OPTION (declaration) | CLAUSE (definition)]
Stores function results in the PL/SQL function result cache

PL/SQL Function Result Cache
  • Is a language-supported and system-managed way to cache the results of PL/SQL functions in a shared global area (SGA).
  • To enable result-caching for a function, use the RESULT_CACHE clause.
  • Each time a result-cached PL/SQL function is invoked with different parameter values, those parameters and their result are stored in the cache.
  • If the system needs more memory, it ages out (deletes) one or more cached results.
  • If changes to any of the data sources (tables and views) queried by the function are committed, the cached result becomes invalid and must be recomputed.
  • Can save significant space and time.
  • The best candidates for result-caching are functions that are invoked frequently but depend on information that changes infrequently or never.

RESULT_CACHE hint (query)
  • RESULT_CACHE hint instructs the database to cache the results of the current query or query fragment in memory and then to use the cached results in future executions of the query or query fragment.
  • The hint is recognized in the top-level query, the subquery_factoring_clause, or FROM clause inline view.
  • The cached results reside in the result cache memory portion of the shared pool.
  • A cached result is automatically invalidated whenever a database object used in its creation is successfully modified.
  • This hint takes precedence over settings of the RESULT_CACHE_MODE initialization parameter.
  • The query is eligible for result caching only if all functions entailed in the query—for example, built-in or user-defined functions or virtual columns—are deterministic.
  • If the query is executed from OCI client and OCI client result cache is enabled, then RESULT_CACHE hint enables client caching for the current query.




















Using the RESULT_CACHE option/clause
The clause needs to be included in the Function declaration
SQL> CREATE OR REPLACE PACKAGE department_pkg IS
 type dept_info_rec is RECORD (
   dept_name departments.department_name%TYPE,
   mgr_name  employees.last_name%type,
   dept_size pls_integer);
 
 -- function declaration
 FUNCTION get_dept_info (dept_id pls_integer)
   RETURN dept_info_rec
   RESULT_CACHE;                   -- result_cache option declared.
END department_pkg;
/
PACKAGE department_pkg compiled.


-- Because get_dept_info depends on the DEPARTMENTS and EMPLOYEES tables, 
-- ANY COMMITTED change to DEPARTMENTS or EMPLOYEES invalidates 
-- all cached results for get_dept_info
SQL> CREATE OR REPLACE PACKAGE BODY department_pkg IS
  -- function definition.
 FUNCTION get_dept_info (dept_id pls_integer)
   return dept_info_rec
   result_cache RELIES_ON (departments, employees) -- identify dependencies
 IS
   rec dept_info_rec; 
 BEGIN
   SELECT department_name into rec.dept_name
   from departments
   where department_id = dept_id;
   
   select e.last_name INTO rec.mgr_name
   from departments d, employees e
   where d.department_id = dept_id
   and d.manager_id = e.employee_id;
   
   select count(*) INTO rec.dept_size
   from employees
   where department_id = dept_id;
   
   return rec;
 END get_dept_info;
END department_pkg;

set serveroutput on
declare
  rec department_pkg.dept_info_rec;
begin
  rec := department_pkg.get_dept_info(10);
  dbms_output.put_line('Department:    '|| rec.dept_name);
  dbms_output.put_line('Manager:       '|| rec.mgr_name);
  dbms_output.put_line('Num Employees: '|| rec.dept_size);
end;
/
anonymous block completed
Department:    Administration
Manager:       Whalen
Num Employees: 1


Restrictions to result-cache functions
  • Cannot be defined in a module that has invoker's rights.
  • Cannot be defined in an anonymous block
  • Cannot be pipelined
  • Does not references dictionary tables, temp tables, sequences or nondeterministic SQL functions.
  • Has no OUT or IN OUT parameters
  • No IN parameter of type: BLOB, CLOB, NCLOB, REF CURSOR, Collection, Object, Record
  • RETURN type cannot be: BLOB, CLOB, NCLOB, REF CURSOR, record or collection with an unsupported return type.

Recommended for result-cached functions:
  • Should not have side effects.
  • Should not depend on session-specific settings
  • should not depend on session-specific application contexts

when does a subprogram has side effects?
If it changes ANYTHING except the values of its own local variables. i.e.
  • changes its own OUT or IN OUT parameter
  • changes a global variable
  • changes a public variable in a package
  • changes a database table
  • changes the database
  • changes the external state (use dbms_output or send email).

What kinds of PL/SQL FUnctions can be called by SQL Statements?
The function must obey these PURITY RULES (control side effects):
  1. Function is invoked from SELECT or parallelized Insert/Update/Delete (I/U/D): Subprogram CANNOT modify any DB tables.
  2. Invoked from I/U/D: subprogram cannot query or modify any DB tables modified by the statement. (ORA-04091 - Mutating Table)
  3. Invoked from SELECT I/U/D: Subprogram CANNOT execute any of these statement types: (except if uses PRAGMA Autonomous_transaction)
    • Transaction Control (i.e. COMMIT)
    • Session Control (i.e. SET ROLE, ALTER SESSION)
    • System control (i.e. ALTER SYSTEM)
    • DDL (i.e. CREATE), which are commited automatically.


Result Cache Bypass
  • Ensures that the user of each session sees his or hers own uncommitted changes
    Put the result cache in bypass mode.
BEGIN 
 DBMS_RESULT_CACHE.Bypass(TRUE);
END;

Result Cache management:
  • Initialization Parameters
    • RESULT_CACHE_MAX_SIZE: Max amount of SGA (bytes)
    • RESULT_CACHE_MAX_RESULT: Max % of the result caceh any single result can use.
  • Package:
    • DBMS_RESULT_CACHE: provides interface allowing DBA to administer the RESULT_CACHE in the SGA.
  • Dynamic performance views:
    • [G]V$RESULT_CACHE_STATISTICS
    • [G]V$RESULT_CACHE_MEMORY
    • [G]V$RESULT_CACHE_OBJECTS
    • [G]V$RESULT_CACHE_DEPENDENCY


How Result-cached functions handle session-specific application contexts?
  • Application Context: Either GLOBAL or session-specific.
  • Is a set of attributes and their values.
  • When does a Pl/sql function depends on session-specific context?
    1. when it directly invokes SQL function SYS_CONTEXT
    2. Indirectly invokes SYS_CONTEXT by using Virtual Private Database mechanisms for fine-grained security.
  • If you must cache the results of a function that depends on session-specific application contexts, you must pass the application context to the function as a parameter.


Using Application Context and the Result Cache
  • Assume that config_tab table has a VPD policy that translates query A into query B below::
SELECT value from config_tab where name = param_name;    -- Query A

SELECT value from config_tab                             -- Query B
where name = param_name
And app_id = sys_context('Cofig', 'App_ID');  --VPD policy adds to the WHERE clause

  • To use the RESULT CACHE, the application context needs to be passed as parameter to the function:
create or replace function get_param_value (
 param_name varchar2,
 appctx     varchar2 default sys_context('Config', 'App_ID')) -- pass app context as parameter
 RETURN VARCHAR
 RESULT_CACHE
IS
 rec varchar2(2000);
BEGIN
 select val into rec
 from config_tab
 where name = param_name;
 RETURN rec;
END;

Execution Right: Invoker's vs. Definer's
  • A program unit can execute within the permissions of the user that created the program (definer's right) or within the permissions of the user that invokes the program (invoker's right).
  • To set execution right: AUTHID = { CURRENT_USER | DEFINER (default)}
  • DBA_PROCEDURES: Check AUTHID values.

  • You can define AUTHID for: CREATE FUNCTION, CREATE PACKAGE, CREATE PROCEDURE, CREATE TYPE, and ALTER TYPE.
  • Procedures and Functions take the definer's right by default.
  • An anonymous block always behaves like an Invoker's Right (CURRENT_USER) unit.
  • A trigger or view always behaves like a Definer's Right (DEFINER) unit.
  • You can create a database link to use invoker's rights:
  • CREATE DATABASE LINK link_name CONNECT TO CURRENT_USER USING connect_string;
  • A current-user link lets you connect to a remote database as another user, with that user's privileges.

(10) Subprograms: Blocks and Stored Procedures



Advantages of subprograms:
  1. Modularity
  2. Easier Application design
  3. Maintainability
    • If underlying objects change: only subprogram needs to be modified.
  4. Better code clarity
  5. Packageability
  6. Reusability: Can be shared by many applications in the database.
  7. Better performance
    • subprograms stored in p-code (exec) form. (except for FUNCTIONS)
    • send a set of statements to the server in a group instead of sending individual statements.
    • Reduced network traffic
  8. Data Security and Integrity:
    • Ensure that all the activity on related tables is either performed in its entirety or not performed at all.
    • Inherit the privileges of the owner by default. (AUTHID DEFINER)


Stored procedures
  • Procedures inherit the privileges of the owner by default
  • You can use procedures to grant indirect access to the objects
  • When a procedure is created it is compiled and source code and compiled form (p-code) are stored in the database.
  • Procedures are stored in the database irrespective of whether or not the compilation was successfull.
  • If compilation fails, USER_OBJECTS display that procedure's status as INVALID, and the procedure will FAIL to execute.
  • If there are any modifications in the objects which the procedure references, it will become INVALID and will fail to execute.
  • When called, the p-code is read from the disk and stored in the shared pool area (SGA).
  • Check *_PROCEDURES, *_SOURCE and *_OBJECTS for data dict info on stored procedures.


---- USER A: ----
create table emp
 (name varchar2(10),  sal  number);

insert into emp 
  values ('John', 1000);
commit;

create or replace procedure ins_emp 
              (name varchar2, salary number)
as
begin
 insert into emp values(name, salary);
 dbms_output.put_line('Insert: Success');
 commit;
exception
 when others then 
   dbms_output.put_line('Error: ' || 
              dbms_utility.format_error_stack());
end;
/

set serveroutput on 
exec ins_emp('Mary', 12000);

select * from emp;
NAME           SAL
---------- -------
John          1000
Mary          1200
---- USER B: ----
select * from a.emp;

SQL Error: ORA-00942: table or view does not exist
00942. 00000 -  "table or view does not exist"


---- USER A: ----
grant execute on ins_emp to b;


---- USER B: ----
set serveroutput on
exec a.ins_emp('BJoe', 9000);

anonymous block completed
Insert: Success


---- USER A: ----
select * from emp;
NAME           SAL
---------- -------
John          1000
Mary          1200
BJoe          9000


Granting indirect access to db objects.
Scenario:


User A:
  • User A Owns table emp.
  • User A needs to control access to emp, but still allow other users to add new employees.
  • User A creates procedure ins_emp.










User B:
  • User B do not have access to directly read or write into table A.emp.
  • SELECT from B on A.emp fails


User A:
  • User A grants EXECUTE PROCEDURE on emp to User B.

User B:
  • User B executes A.ins_emp.
  • A.ins_emp runs with owner's privilege and inserts into A.emp.








Formal parameters can be only one of these:
  1. UNCONSTRAINED type name (number, varchar)
  2. Type constrained using %TYPE or %ROWTYPE

  • Using %TYPE is recommended, because if the type of the column in the table changes, it is not necessary to change the application code.
  • If the get_emp_names procedure is part of a package, you can use previously-declared public (package) variables to constrain its parameter data types. For example:
    dept_number NUMBER(2);
    ...
    PROCEDURE get_emp_names(dept_num IN dept_number%TYPE);
  • Parameters can take initial values. Use either the assignment operator or the DEFAULT keyword to give a parameter an initial value. For example, these are equivalent:
    PROCEDURE Get_emp_names (Dept_num IN NUMBER := 20) IS ...
    PROCEDURE Get_emp_names (Dept_num IN NUMBER DEFAULT) IS ...
  • Privileges required to compile the subprogram or package successfully):
    - The owner of the subprogram or package must be explicitly granted the necessary object privileges for all objects referenced within the body of the code.
    - The owner cannot obtain required privileges through roles.
  • Package creation requires a sort. The user creating the package must be able to create a sort segment in the temporary tablespace with which the user is associated.




Procedure lists employees of a department
CREATE OR REPLACE procedure list_emp(p_dept in 
                            employees.department_id%type default 80) as 
 ln    employees.last_name%type;
 sal   employees.salary%type;
 dept_out_of_range EXCEPTION;
 cursor c1 (p_dept in employees.department_id%type)  IS
   select last_name, salary 
   from employees
   where department_id = p_dept
   order by last_name;
BEGIN
  IF p_dept < 0 or p_dept > 200 THEN
    RAISE dept_out_of_range;
  END IF;
  Open c1(p_dept);
  loop 
    fetch c1 into ln, sal;
    exit when c1%NOTFOUND;
    dbms_output.put_line('Dept '|| p_dept || ', '|| ln ||', '|| sal);
  end loop;
  dbms_output.put_line('------ END List ----- ');
EXCEPTION
 when dept_out_of_range then 
   dbms_output.put_line('Department '|| p_dept || ' is not a valid department');
  dbms_output.put_line(sqlerrm);
   dbms_output.put_line(dbms_utility.format_error_backtrace());
 when others then 
   dbms_output.put_line(sqlerrm);
   dbms_output.put_line(dbms_utility.format_error_backtrace());
END list_emp;

set serveroutput on
begin
 list_emp(40);
end;

PROCEDURE list_emp compiled
anonymous block completed
Dept 40, Mavris, 6500
------ END List ----- 

-- IF you use an invalid department ID: 
set serveroutput on
begin
 list_emp(300);
end;

anonymous block completed
Department 300 is not a valid department
User-Defined Exception
ORA-06512: at DEV2.LIST_EMP", line 13


Procedure prints last name and salary of all the
employees in the same department as the user given
CREATE OR REPLACE procedure list_emp2(p_user in 
                            employees.last_name%type) as 
 ln    employees.last_name%type;
 sal   employees.salary%type;
 user_not_found EXCEPTION;
 dept employees.department_id%type;
 
 cursor c1 (p_dept in employees.department_id%type)  IS
   select last_name, salary 
   from employees
   where department_id = p_dept
   order by last_name;

 FUNCTION get_dept (p_user in varchar2)
  RETURN employees.department_id%type
 AS 
  v_dept departments.department_id%type;
 BEGIN
   select department_id into v_dept
   from employees 
   where upper(employees.last_name) = upper(p_user);
   RETURN v_dept;
 EXCEPTION
   WHEN no_data_found then 
     RETURN -20;
 END get_dept;

BEGIN
  dept := get_dept(p_user);
  IF dept = -20 then 
    RAISE user_not_found;
  ELSE
    Open c1(dept);
    loop 
      fetch c1 into ln, sal;
      exit when c1%NOTFOUND;
      dbms_output.put_line('Dept '|| dept || ', '|| ln ||', '|| sal);
    end loop;
    dbms_output.put_line('------ END List ----- '); 
  END IF;

EXCEPTION
 when user_not_found then 
   dbms_output.put_line('Invalid Last Name: '|| p_user);
   dbms_output.put_line(sqlerrm);
 when others then 
   dbms_output.put_line(sqlerrm);
   dbms_output.put_line(dbms_utility.format_error_backtrace());
END list_emp2;


set serveroutput on 
begin
 list_emp2('Mavris');
end;
/
anonymous block completed
Dept 40, Mavris, 6500
------ END List ----- 

set serveroutput on 
begin
 list_emp2('Joelma');
end;
/
anonymous block completed
Invalid Last Name: Joelma
User-Defined Exception


Forward Declaration
  • A forward declaration declares a nested subprogram but does not define it.
  • You must define it later in the same block.
  • Forward declaration and the definition must have the same subprogram heading.

DECLARE
  -- Declare proc1 (forward declaration):
  PROCEDURE proc1(number1 NUMBER);

  -- Declare and define proc2:
  PROCEDURE proc2(number2 NUMBER) IS
  BEGIN
    proc1(number2);
  END;

  -- Define proc 1:
  PROCEDURE proc1(number1 NUMBER) IS
  BEGIN
    proc2 (number1);
  END;

BEGIN
  NULL;
END;
/

About Parameters
  • Can be passed BY reference or BY value.
  • BY REFERENCE: Pointer to actual parameter. ACTUAL and FORMAL parameter refer to the SAME memory location.
  • BY VALUE: actual and formal parameters refer to different memory locations.
  • IN mode: Actual param passed by reference. But FORMAL param CANNOT be changed.
  • OUT mode: Actual param passed by value.
    • If you specify NOCOPY, it might be passed by reference.
    • Formal param: unitialized variable. Has NULL as initial value. Subprogram should ASSIGN a value. (So PL/SQL might COPY value from Formal to Actual at the end).
    • NOCOPY is only a hint: each time the subprogram is invoked, the compiler decides, silently, whether to obey or ignore NOCOPY.
  • IN OUT mode: Actual parameter is passed by value (in both directions).
  • OUT and IN OUT:
    • (a) if subprogram exited success: value of formal param COPIED to the acutal param.
    • (b) If failure: formal value not passed. Actual retain previous value.
  • IN parameters CAN be assigned a DEFAULT value when declared.
  • OUT and IN OUT parameters CANNOT be assigned DEFAULT values when declared.
  • PLS-00312: a POSITIONAL parameter association MAY NOT follow a named association

Default values and Constraints
If the datatype of a formal parameter is a constrained subtype the:
  • if the subtype has a NOT NULL constraint, the ACTUAL parameter inherits it.
  • if the subtype has the base type VARCHAR2, then ACTUAL param does not inherit the SIZE of subtype
  • if subtype is numeric, actual param inherits the range, but not scale, size or precision.

set serveroutput on
declare
subtype license is varchar2(7) not null;
n license := 'DLLLDDD';

procedure p (x License) IS 
begin 
dbms_output.put_line(x);
end;

begin
p('1ABC123456788');
p(NULL);
end; 
/
Error report:
ORA-06550: line 12, column 5:
PLS-00567: cannot pass NULL to a NOT NULL constrained formal parameter
ORA-06550: line 12, column 3:
PL/SQL: Statement ignored


Parameter Aliasing
  • Aliasing: having TWO different names for the same memory location.
  • Always occur when parameter is passed by reference.
  • Can also occur when a subprogram has cursor variable parameters.
  • Aliasing can occur for one invocation but not another, making subprogram results indeterminate. For example:
    • If actual parameter is a global variable: an assignment to the formal parameter MAY show in the global parameter
    • If the same variable is the actual parameter for two formal parameters:, then an assignment to either formal parameter might show immediately in both formal parameters.
    • If the actual parameter is a package variable: an assignment to either the formal parameter or the package variable might show immediately in both the formal parameter and the package variable.
    • If the subprogram is exited with an unhandled exception: then an assignment to the formal parameter might show in the actual parameter.


CASE 1: word_list is formal parameter, with NOCOPY option.
IF compiler obeys the NOCOPY: Any update to the IN OUT parameter will reflect
immediately in the lexicon global variable.
IF compile DOES NOT obey the NOCOPY directive: any update to the IN OUT
parameter will only show up in the global var after a successful completion
of the procedure.
set serveroutput on 
DECLARE 
 type definition is record (
   word  varchar2(20), 
   meaning varchar2(200));

 type dictionary is varray(2000) of definition;
 lexicon dictionary := dictionary();  -- global variable
 
 procedure add_entry (
   word_list IN OUT  NOCOPY dictionary  -- formal param no copy (should point to actual param)
 ) IS
 Begin
  word_list(1).word := 'aardvark';
 end;

BEGIN
  lexicon.EXTEND;
  lexicon(1).word := 'aardwolf';
  add_entry(lexicon);    -- global variable is actual parameter
  dbms_output.put_line(lexicon(1).word);
END;
/
anonymous block completed
aardvark


CASE 2: word_list is formal parameter, with NOCOPY option.
Here an exception is raised within the procedure.
if NOCOPY was obeyed the global variable will have its
value updated despite the procedure failure..
set serveroutput on 
DECLARE 
 type definition is record (
   word  varchar2(20), 
   meaning varchar2(200));
 type dictionary is varray(2000) of definition;
 lexicon dictionary := dictionary();  -- gloval variable

procedure add_entry (
   word_list IN OUT NOCOPY dictionary  -- formal param no copy
 ) IS
   e1 exception;
 Begin
  word_list(1).word := 'aardvark';
  raise e1;
 end;

BEGIN
  lexicon.EXTEND;
  lexicon(1).word := 'aardwolf';
  add_entry(lexicon);    -- global variable is actual parameter
  dbms_output.put_line(lexicon(1).word);
EXCEPTION 
 when others then 
    dbms_output.put_line(sqlerrm);
    dbms_output.put_line(lexicon(1).word);
END;
/
anonymous block completed
User-Defined Exception
aardvark


Aliasing from cursor variables
  • Cursor variable parameters are pointers.
  • Therefore, if a subprogram assigns one cursor variable parameter to another,
    they refer to the same memory location.
  • This aliasing can have unintended results.


(1) The procedure has two cursor variable parameters, emp_cv1 and emp_cv2.
(2) The procedure opens emp_cv1 and assigns its value (which is a pointer) to emp_cv2.
(3) Now emp_cv1 and emp_cv2 refer to the same memory location.
(4) When the procedure closes emp_cv1, it also closes emp_cv2.
(5) Therefore, when the procedure tries to fetch from emp_cv2, PL/SQL raises an exception.
DECLARE
  TYPE EmpCurTyp IS REF CURSOR;
  c1 EmpCurTyp;
  c2 EmpCurTyp;

  PROCEDURE get_emp_data (
    emp_cv1 IN OUT EmpCurTyp,
    emp_cv2 IN OUT EmpCurTyp
  )
  IS
    emp_rec employees%ROWTYPE;
  BEGIN
    OPEN emp_cv1 FOR SELECT * FROM employees;
    emp_cv2 := emp_cv1;  -- now both variables refer to same location
    FETCH emp_cv1 INTO emp_rec;  -- fetches first row of employees
    FETCH emp_cv1 INTO emp_rec;  -- fetches second row of employees
    FETCH emp_cv2 INTO emp_rec;  -- fetches third row of employees
    CLOSE emp_cv1;  -- closes both variables
    FETCH emp_cv2 INTO emp_rec; -- causes error when get_emp_data is invoked
  END;
BEGIN
  get_emp_data(c1, c2);
END;
/
Error report:
ORA-01001: invalid cursor
ORA-06512: at line 19
ORA-06512: at line 22


Default values for PARAMETERS
  • Omitting an actual parameter does not make the value of the corresponding formal parameter NULL.
  • To make the value of a formal parameter NULL, specify NULL as either the default value or the actual parameter.

Using SELECT FOR UPDATE and testing for DEFAULT parameter VALUES
  • SELECT FOR UPDATE enables you to base an update on the existing values in the rows,
    because it ensures that no other user can change those values before you update them.
  • By default, the SELECT FOR UPDATE statement waits until the requested row lock is acquired. To change this behavior, use the NOWAIT, WAIT, or SKIP LOCKED clauses.
  • When SELECT FOR UPDATE is associated with an explicit cursor, the cursor is called a FOR UPDATE cursor.
  • Only a FOR UPDATE cursor can appear in the CURRENT OF clause of an UPDATE or DELETE statement.

set serveroutput on
DECLARE
 PROCEDURE raise_sal_dept (
    deptid in employees.department_id%type,
    amount in employees.salary%type :=100,      -- default
    extra in  employees.salary%type default 50  -- default
  ) IS 
   stmt varchar2(200);
   vempid employees.employee_id%type;
   vsal   employees.salary%type;

  -- Only a FOR UPDATE cursor can appear in the CURRENT OF clause. 
  -- So, a CURSOR VARIABLE CANNOT be used here. You need to define a 
  -- FOR UPDATE CURSOR
  CURSOR vcur IS
     select employee_id, salary 
      from employees
      where department_id = deptid
      FOR UPDATE;
  BEGIN 
   OPEN vcur;
   dbms_output.put_line('Updating salaries of Dept: ' || deptid);
   LOOP
     FETCH vcur INTO vempid, vsal;
     EXIT when vcur%NOTFOUND;
     dbms_output.put_line('Updating Emp Id '|| vempid ||'. New salary: ' ||
                           vsal ||' + '|| amount || ' + '|| extra);
     UPDATE employees
     set salary = salary + amount + extra
     WHERE CURRENT OF vcur;
   END LOOP;
 END raise_sal_dept;
BEGIN
 -- dept 20: 2 employees
 -- dept 70: 1 employee
 -- dept 1:  does not exist
 -- dept 30: 6 employees
 raise_sal_dept(20);    -- same as raise_sal_dept(20, 100, 50);
END;
/
anonymous block completed
Updating salaries of Dept: 20
Updating Emp Id 201. New salary: 13150 + 100 + 50
Updating Emp Id 202. New salary: 6150 + 100 + 50


Local Subprograms
  • Local subprograms should be placed LAST in the declarative section. Otherwise, a compilation error will be generated.
  • Local subprograms can ONLY be referenced from within the main program.
  • So, local subprograms CANNOT be invoked from outside the parent subprogram.

Overloaded subprograms
  • PL/SQL lets you overload nested subprograms, package subprograms, and type methods.
  • You can use the same name for several different subprograms if their formal parameters differ in name, number, order, OR data type family

Restrictions to overloading
  • You cannot use two functions with the same name that differ only in their return data type.
  • You cannot use two procedures with the same name if their parameters differ only in name or mode.
  • You cannot overload two subprograms if their parameters belong to the same family of data types.