Tuesday, 9 August 2016

Cursors

CURSORS

Cursor is a pointer to memory location which is called as context area which contains the information necessary for processing, including the number of rows processed by the statement, a pointer to the parsed representation of the statement, and the active set which is the set of rows returned by the query.

Cursor contains two parts:
  • Header
  • Body

Header includes cursor name, any parameters and the type of data being loaded.
Body includes the select statement.

Ex:
Cursor c(dno in number) return dept%rowtype is select *from dept;
           In the above 
Header – cursor c(dno in number) return dept%rowtype
Body – select *from dept

Cursor Types:
  1. Implicit Cursor(SQL)
  2. Explicit Cursor
               a) Parameterized Cursors
               b) REF cursors

Cursor Stages:
  • Open
  • Fetch
  • Close
Cursor Attributes:
  1. %found
  2. %notfound
  3. %rowcount
  4. %isopen
  5. %bulk_rowcount
  6. %bulk_exceptions
Cursor Declaration:
Syntax:
       Cursor <cursor_name> is select statement;
   EX: 
       Cursor c is select * from dept;

Cursor Loops:
  1. Simple Loop
  2. While Loop
  3. For Loop
1.Simple Loop:
  Syntax:
     Loop
Fetch <cursor_name> into <record_variable>;
Exit when <cursor_name> % notfound;
    <statements>;
End loop;
Ex:
DECLARE
     cursor c is select * from student;
     v_stud student%rowtype;
BEGIN
     Open c;
     Loop
        fetch c into v_stud;
        exit when c%notfound;
        dbms_output.put_line('Name = ' || v_stud.name);
     end loop;
     close c;
END;
Output:
Name = saketh
Name = srinu
Name = satish
Name = sudha

2.While Loop:
  Syntax:
     While <cursor_name> % found 
      loop
         Fetch <cursor_name> into <record_variable>;
          <statements>;
      End loop;
Ex:
DECLARE
     cursor c is select * from student;
     v_stud student%rowtype;
BEGIN
     open c;
     fetch c into v_stud;
     while c%found loop
          fetch c into v_stud;
          dbms_output.put_line('Name = ' || v_stud.name);
     end loop;
     close c;
END;
Output:
Name = saketh
Name = srinu
Name = satish
Name = sudha

3.For Loop:
    Syntax:
       for <record_variable> in <cursor_name> 
        loop
            <statements>;
        End loop;
Ex:
     declare
     cursor c is select * from student;
     begin
         for v_stud in c
     loop
         dbms_output.put_line('Name = ' || v_stud.name);
     end loop;
    end;
Output:
Name = saketh
Name = srinu
Name = satish
Name = sudha

Implicit Cursors:
============

  • Implicit cursors are automatically created by Oracle whenever an SQL statement is executed, when there is no explicit cursor for the statement. Programmers cannot control the implicit cursors and the information in it.
  • Whenever a DML statement (INSERT, UPDATE and DELETE) is issued, an implicit cursor is associated with this statement. For INSERT operations, the cursor holds the data that needs to be inserted. For UPDATE and DELETE operations, the cursor identifies the rows that would be affected
  • In PL/SQL, you can refer to the most recent implicit cursor as the SQL cursor, which always has the attributes like %FOUND, %ISOPEN, %NOTFOUND, and %ROWCOUNT. The SQL cursor has additional attributes, %BULK_ROWCOUNT and %BULK_EXCEPTIONS, designed for use with the FORALL statement. The following table provides the description of the most used attributes:
     Ex:
      The following program would update the table and increase salary of each customer by 500 and use the SQL%ROWCOUNT attribute to determine the number of rows affected.

DECLARE 
   total_rows number(2);
BEGIN
   UPDATE customers
   SET salary = salary + 500;
   IF sql%notfound THEN
      dbms_output.put_line('no customers selected');
   ELSIF sql%found THEN
      total_rows := sql%rowcount;
      dbms_output.put_line( total_rows || ' customers selected ');
   END IF; 
END;
/

PL/SQL provides some attributes, which allow you to evaluate what happened when the implicit cursor was last used. You can use these attributes in PL/SQL statements like functions but you cannot use then within SQL statements.
%ROWCOUNTThe number of rows processed by a SQL statement.
%FOUNDTRUE if at least one row was processed.
%NOTFOUNDTRUE if no rows were processed.
%ISOPENTRUE if cursor is open or FALSE if cursor has not been opened or has been closed. Only used with explicit cursors.

An Example of PL/SQL Attribute

DECLARE
rows_deleted NUMBER;
BEGIN
DELETE * FROM emp;
rows_deleted := SQL%ROWCOUNT;
END;


The implicit cursor has the following drawbacks:
  • It is less efficient than an explicit cursor.
  • It is more vulnerable to data errors.
  • It gives you less programmatic control.

Explicit Cursors:
============
Explicit cursors are programmer defined cursors for gaining more control over the context area. An explicit cursor should be defined in the declaration section of the PL/SQL Block. It is created on a SELECT Statement which returns more than one row.

The syntax for creating an explicit cursor is :
      CURSOR cursor_name IS select_statement;

Working with an explicit cursor involves four steps:

  • Declaring the cursor for initializing in the memory
  • Opening the cursor for allocating memory
  • Fetching the cursor for retrieving data
  • Closing the cursor to release allocated memory
Declaring the Cursor:
Declaring the cursor defines the cursor with a name and the associated SELECT statement.
Ex: CURSOR c_customers IS

   SELECT id, name, address FROM customers;

Opening the Cursor:
Opening the cursor allocates memory for the cursor and makes it ready for fetching the rows returned by the SQL statement into it.
Ex: OPEN c_customers;

Fetching the Cursor:

Fetching the cursor involves accessing one row at a time. 
For example we will fetch rows from the above-opened cursor as follows:
Ex:FETCH c_customers INTO c_id, c_name, c_addr;

Closing the Cursor:
Closing the cursor means releasing the allocated memory.
For example, we will close above-opened cursor as follows:
Ex:CLOSE c_customers;

Example:
Following is a complete example to illustrate the concepts of explicit cursors:
DECLARE
   c_id customers.id%type;
   c_name customers.name%type;
   c_addr customers.address%type;
   CURSOR c_customers is
      SELECT id, name, address FROM customers;
BEGIN
   OPEN c_customers;
   LOOP
      FETCH c_customers into c_id, c_name, c_addr;
      EXIT WHEN c_customers%notfound;
      dbms_output.put_line(c_id || ' ' || c_name || ' ' || c_addr);
   END LOOP;
   CLOSE c_customers;
END;
/

Parameterized cursors:

  • This was used when you are going to use the cursor in more than one place with different values for the same where clause.
  • Cursor parameters must be in mode.
  • Cursor parameters may have default values.
  • The scope of cursor parameter is within the select statement.
Ex:

     DECLARE
         cursor c(dno in number) is select * from dept where deptno = dno;
         v_dept dept%rowtype;
      BEGIN
         open c(20);
         loop
             fetch c into v_dept;
             exit when c%notfound;
            dbms_output.put_line('Dname = ' || v_dept.dname || ' Loc = ' || v_dept.loc);
         end loop;
         close c;
     END;
/
Output:
    Dname = RESEARCH Loc = DALLAS

Reference Cursors:

This is unconstrained cursor which will return different types depends upon the user input
  • Ref Cursors cannot be closed implicitly
  • Ref Cursors with return type is called strong cursor
  • Ref Cursors with out return type is called weak cursor
  • you can declare Ref cursor type in package spec as well as body
  • you can declare ref cursor types in local subprograms or anonymous blocks. 
Ex:
CREATE OR REPLACE PROCEDURE REF_CURSOR(
    TABLE_NAME IN VARCHAR)
IS
type t
IS
  ref
  CURSOR;
    c t;
    v_dept dept%rowtype;
  type r
IS
  record
  (
    ename emp.ename%type,
    job emp.job%type,
    sal emp.sal%type);
  v_emp r;
  v_stud student.name%type;
BEGIN
  IF table_name = 'DEPT' THEN
    OPEN c FOR SELECT * FROM dept;
  elsif table_name = 'EMP' THEN
    OPEN c FOR SELECT ename,job,sal FROM emp;
  elsif table_name = 'STUDENT' THEN
    OPEN c FOR SELECT name FROM student;
  END IF;
  LOOP
    IF table_name = 'DEPT' THEN
      FETCH c INTO v_dept;
    EXIT
  WHEN c%notfound;
    dbms_output.put_line('Deptno = ' || v_dept.deptno || ' Dname = ' || v_dept.dname || ' Loc = ' || v_dept.loc);
  elsif table_name = 'EMP' THEN
    FETCH c INTO v_emp;
    EXIT
  WHEN c%notfound;
    dbms_output.put_line('Ename = ' || v_emp.ename || ' Job = ' || v_emp.job || ' Sal = ' || v_emp.sal);
  elsif table_name = 'STUDENT' THEN
    FETCH c INTO v_stud;
    EXIT
  WHEN c%notfound;
    dbms_output.put_line('Name = ' || v_stud);
  END IF;
END LOOP;
CLOSE c;
END;

Output: 
SQL> EXEC ref_cursor('DEPT') 
Deptno = 10 Dname = ACCOUNTING Loc = NEW YORK 
Deptno = 20 Dname = RESEARCH Loc = DALLAS 
Deptno = 30 Dname = SALES Loc = CHICAGO 
Deptno = 40 Dname = OPERATIONS Loc = BOSTON 

SQL> EXEC ref_cursor('EMP') 
Ename = SMITH Job = CLERK Sal = 800
 Ename = ALLEN Job = SALESMAN Sal = 1600 
 Ename = WARD Job = SALESMAN Sal = 1250 
 Ename = JONES Job = MANAGER Sal = 2975
 Ename = MARTIN Job = SALESMAN Sal = 1250 
 Ename = BLAKE Job = MANAGER Sal = 2850 
 Ename = CLARK Job = MANAGER Sal = 2450 
 Ename = SCOTT Job = ANALYST Sal = 3000 
 Ename = KING Job = PRESIDENT Sal = 5000
 Ename = TURNER Job = SALESMAN Sal = 1500
 Ename = ADAMS Job = CLERK Sal = 1100 
 Ename = JAMES Job = CLERK Sal = 950 
 Ename = FORD Job = ANALYST Sal = 3000 
 Ename = MILLER Job = CLERK Sal = 1300 
 
 SQL> EXEC ref_cursor('STUDENT') 
 Name = saketh 
 Name = srinu 
 Name = satish 
 Name = sudha

Nested Cursors:
  • Oracle opens the nested cursor defined by a cursor expression implicitly as soon as it fetches the data containing the cursor expression from the parent or outer cursor
  • Nested cursor closes if you closed explicitly
  • Nested cursor closes whenever the outer or parent cursor is executed again or closed or cancelled
  • Nested cursor closes whenever an exception is raised while fetching data from a parent cursor
Ex:
DECLARE
cursor c is select ename,cursor(select dname from dept d where e.empno = d.deptno)  from emp e;
type t is ref cursor;
c1 t;
c2 t;
v1 emp.ename%type;
v2 dept.dname%type;
BEGIN
open c;
loop
     fetch c1 into v1;
          exit when c1%notfound;
          fetch c2 into v2;
          exit when c2%notfound;
          dbms_output.put_line('Ename = ' || v1 || ' Dname = ' || v2);
end loop;
end loop;
close c;
END;

CURSOR CLAUSES:
                        1     Return
2     For update
3     Where current of
4     Bulk collect
RETURN:
         Cursor c return dept%rowtype is select *from dept;
Or
Cursor c1 is select *from dept;
Cursor c  return c1%rowtype is select *from dept;
Or
Type t is record(deptno dept.deptno%type, dname dept.dname%type);
Cursor c return t is select deptno, dname from dept;

FOR UPDATE AND WHERE CURRENT OF:
 Normally, a select operation will not take any locks on the rows being accessed. This will allow other sessions connected to the database to change the data being selected. The result set is still consistent. At open time, when the active set is determined, oracle takes a snapshot of the table. Any changes that have been committed prior to this point are reflected in the active set. Any changes made after this point, even if they are committed, are not reflected unless the cursor is reopened, which will evaluate the active set again.

However, if the FOR UPDATE clause is present, exclusive row locks are taken on the rows in the active set before the open returns. These locks prevent other sessions from changing the rows in the active set until the transaction is committed or rolled back. If another session already has locks on the rows in the active set, then SELECT … FOR UPDATE operation will wait for these locks to be released by the other session. There is no time-out for this waiting period. The SELECT…FOR UPDATE will hang until the other session releases the lock. To handle this situation, the NO WAIT clause is available.
 Syntax:
Select …from … for update of column_name [wait n];

If the cursor is declared with the FOR UPDATE clause, the WHERE CURRENT OF clause can be used in an update or delete statement.

Syntax:
Where current of cursor;

Ex:
DECLARE
       cursor c is select * from dept for update of dname;
BEGIN
       for v in c loop
             update dept set dname = 'aa' where current of c;
             commit;
       end loop;
END;

BULK COLLECT:
1     This is used for array fetches
2     With this you can retrieve multiple rows of data with a single round trip.
3     This reduces the number of context switches between the pl/sql and sql engines.
4     Reduces the overhead of retrieving data.
5     You can use bulk collect in both dynamic and static sql.
6     You can use bulk collect in select, fetch into and returning into clauses.
7   SQL engine automatically initializes and extends the collections you reference in the bulk collect clause.
8    Bulk collect operation empties the collection referenced in the into clause before executing the query.
9     You can use the limit clause of bulk collect to restrict the no of rows retrieved.
10  You can fetch into multiple collections with one column each.
11  Using the returning clause we can return data to the another collection.

BULK COLLECT IN FETCH
 Ex:
DECLARE
     Type t is table of dept%rowtype;
     nt t;
     Cursor c is select *from dept;
BEGIN
     Open c;
     Fetch c bulk collect into nt;
     Close c;
     For i in nt.first..nt.last loop
           dbms_output.put_line('Dname = ' || nt(i).dname || ' Loc = ' ||
                                                          nt(i).loc);
     end loop;
END;

Output:
Dname = ACCOUNTING Loc = NEW YORK
Dname = RESEARCH Loc = DALLAS
Dname = SALES Loc = CHICAGO
Dname = OPERATIONS Loc = BOSTON

BULK COLLECT IN SELECT
 Ex:
DECLARE
     Type t is table of dept%rowtype;
     Nt t;
BEGIN
     Select * bulk collect into nt from dept;
     for i in nt.first..nt.last loop
           dbms_output.put_line('Dname = ' || nt(i).dname || ' Loc = ' ||
                                                                nt(i).loc);
     end loop;
END;

Output:
Dname = ACCOUNTING Loc = NEW YORK
Dname = RESEARCH Loc = DALLAS
Dname = SALES Loc = CHICAGO
Dname = OPERATIONS Loc = BOSTON

LIMIT IN BULK COLLECT
 You can use this to limit the number of rows to be fetched.
 Ex:
DECLARE
     Type t is table of dept%rowtype;
     nt t;
     Cursor c is select *from dept;
BEGIN
     Open c;
     Fetch c bulk collect into nt limit 2;
     Close c;
     For i in nt.first..nt.last loop
           dbms_output.put_line('Dname = ' || nt(i).dname || ' Loc = ' || nt(i).loc);
     end loop;
END;

Output:
Dname = ACCOUNTING Loc = NEW YORK
Dname = RESEARCH Loc = DALLAS

MULTIPLE FETCHES IN  INTO CLAUSE
 Ex1:
     DECLARE
           Type t is table of dept.dname%type;
           nt t;
           Type t1 is table of dept.loc%type;
           nt1 t;
           Cursor c is select dname,loc from dept;
      BEGIN
           Open c;
           Fetch c bulk collect into nt,nt1;
           Close c;
           For i in nt.first..nt.last loop
                  dbms_output.put_line('Dname = ' || nt(i));
           end loop;
           For i in nt1.first..nt1.last loop
                  dbms_output.put_line('Loc = ' || nt1(i));
           end loop;
      END;

Output:
Dname = ACCOUNTING
Dname = RESEARCH
Dname = SALES
Dname = OPERATIONS
Loc = NEW YORK
Loc = DALLAS
Loc = CHICAGO
Loc = BOSTON

Ex2:
DECLARE
      type t  is table of dept.dname%type;
      type t1 is table of dept.loc%type;
      nt t;
      nt1 t1;
BEGIN
      Select dname,loc bulk collect into nt,nt1 from dept;
      for i in nt.first..nt.last loop
           dbms_output.put_line('Dname = ' || nt(i));
      end loop;
      for i in nt1.first..nt1.last loop
           dbms_output.put_line('Loc = ' || nt1(i));
      end loop;
END;

Output:
Dname = ACCOUNTING
Dname = RESEARCH
Dname = SALES
Dname = OPERATIONS
Loc = NEW YORK
Loc = DALLAS
Loc = CHICAGO
Loc = BOSTON

RETURNING CLAUSE IN BULK COLLECT
 You can use this to return the processed data to the output variables or typed variables.
 Ex:
DECLARE
       type t is table of number(2);
       nt t := t(1,2,3,4);
       type t1 is table of varchar(2);
       nt1 t1;
       type t2 is table of student%rowtype;
       nt2 t2;
BEGIN
       select name bulk collect into nt1 from student;
       forall v in nt1.first..nt1.last
        E           update student set no = nt(v) where name = nt1(v) returning 
                              no,name,marks bulk collect into nt2;
       for v in nt2.first..nt2.last loop
               dbms_output.put_line('Marks = ' || nt2(v));
       end loop;
END;

Output:
Marks = 100
Marks = 200
Marks = 300
Marks = 400

POINTS TO REMEMBER
     1     Cursor name can be up to 30 characters in length.
2   Cursors declared in anonymous blocks or subprograms closes automatically when that block terminates execution.
3     %bulk_rowcount and %bulk_exceptions can be used only with forall construct.
4     Cursor declarations may have expressions with column aliases.
5     These expressions are called virtual columns or calculated columns.

No comments:

Post a Comment