PL/SQL Tables (Index by tables):
PL/SQL Tables are not the same as a database table.  They are essentially a one dimensional array that has two columns.  The first column is the primary key column of type BINARY_INTEGER, the second column is the data column of any data-type.

Declaring tables in PL/SQL is a two step process.  First Declare a table TYPE, then declare a variable of that type.

Syntax:

TYPE type_name IS TABLE OF data-type INDEX BY BINARY_INTEGER

Variable type_name

 

Example:

DECLARE

          TYPE sal_table_type IS TABLE OF NUMBER(8,2) INDEX BY BINARY_INTEGER;  

          TYPE EmpListType IS TABLE OF emp%ROWTYPE INDEX BY BINARY_INTEGER;

          sal_table sal_table_type;

BEGIN

. . .

Note the indentation above, failure to indent for readability will result in one full letter grade deduction and your work being returned to you to fix.    Also I will not help you with any program that is NOT readable.

PL/SQL Records:
Not the same as a database row.  Composed of one or more fields that can be of any data-type.  Once again declaring a PL/SQL Record is a two step process.  First declare a record TYPE, next declare a variable of that type.  This is similar to the type command in Visual Basic and the struct command in C.

Syntax:

TYPE type_name IS RECORD (col1 data-type, col2 data-type, ?);

Variable type_name;

Example:

 

DECLARE

          TYPE comp_record_type IS RECORD (
                   
Sal NUMBER (8,2) NOT NULL,
                   
Comm NUMBER (8,2) NULL,
                   
Total_comp NUMBER(8,2) NOT NULL);

           
          Comp_record comp_record_type;

            TYPE comp_list_type IS TABLE OF comp_record_type INDEX BY BINARY_INTEGER

BEGIN

. . .

Declare PL/SQL Record Type from a Table Definition (%ROWTYPE):
The %ROWTYPE construct allows you to create a PL/SQL record with the same fields as the table it references.  This much simplifies record declaration when you want to emulate a table inside your PL/SQL code.

Syntax:

DECLARE

          Sales_rep_record sales_rep%ROWTYPE;

BEGIN

. . .

Note the indentation above, failure to indent for readability will result in one full letter grade deduction and your work being returned to you to fix.    Also I will not help you with any program that is NOT readable.

-         Assigning Variables in Code

V_variable := expression;

Table_name(index) := expression;

Record_name.field_name := expression;  

      empList (index).column_name := value;

CURSORS:
Cursors are used for processing multiple rows being returned from a SELECT statement.

Creating a cursor:

-         Declare the cursor.

-         Open the cursor.

-         Fetch data from the cursor.

-         Close the cursor.

 

Declaring Cursors, Syntax:

DECLARE
         
CURSOR cursor_name IS
                   
SELECT columns
                   
FROM tables
                   
WHERE condition;

Notes:
-         Do not include the INTO clause in the SELECT.
-        
Declare variables before cursors.

 

Opening Cursors, Syntax:

  OPEN cursor_name;

Note:  This is done in the BEGIN section and is used to prepare the cursor for execution.

  Fetching Data from the Cursor, Syntax:

  FETCH cursor_name INTO variable1, variable2, etc.;

Notes:
-    Include the same number of variables as is included in the SELECT clause of the cursor.

-        
Variables are mapped to columns in the SELECT in the order they are listed.

 

Closing Cursors, Syntax:

CLOSE cursor_name;

Notes:
-    Close the cursor after all the rows are processed.

-        
You can't fetch data from the cursor once you have closed it.

 

Cursor Status Attributes:  
-        
%ISOPEN:  True if cursor is OPEN.  
-        
%NOTFOUND:  True if the most recent FETCH returns nothing.  
-        
%FOUND:  True until most recent FETCH returns nothing.  
-        
%ROWCOUNT:  Total rows fetched so far.

Example:

set serveroutput on;
ACCEPT p_order_nr PROMPT 'Enter Order #:';
DECLARE
         
v_order_nr order_line.order_number%TYPE := &p_order_nr;
         
v_part order_line.part_number%TYPE;
          v_total totals.total%TYPE;
          v_order_total totals.total%TYPE;

          CURSOR order_line_cursor IS
                    SELECT part_number, quoted_price * number_ordered
                    FROM order_line
                    WHERE order_number= v_order_nr;
BEGIN
          OPEN order_line_cursor;

          LOOP
                    FETCH order_line_cursor INTO v_part, v_order_total;

                    EXIT WHEN order_line_cursor%NOTFOUND;

                    v_total := v_total + v_order_total;  

                    dbms_output.put_line ('Order total for part ' ||v_part || ' is ' || v_order_total);

                    INSERT INTO totals (part, total)
                             VALUES (v_part, v_order_total);
          END LOOP;

          CLOSE order_line_cursor;

          COMMIT WORK;
END;
/

Note the indentation above, failure to indent for readability will result in one full letter grade deduction and your work being returned to you to fix.    Also I will not help you with any program that is NOT readable.

 

Cursor FOR Loop:
A cursor for loop is specifically designed to stream line cursor processing.

A cursor For Loop:
-         Explicitly Declare
-         Implicitly Opens
-         Implicitly Fetches
-         Implicitly Declares Record for returned row
-         Implicitly Closes Cursor when for loop ends. 

Syntax:

FOR record_name IN cursor_name LOOP
            
Statement1;
         
Statement2;
END LOOP;

Note the indentation above, failure to indent for readability will result in one full letter grade deduction and your work being returned to you to fix.    Also I will not help you with any program that is NOT readable.

 

Example :


ACCEPT p_order_nr PROMPT 'Enter Order #:'; 
DECLARE 
        v_order_nr order_line.order_number%TYPE := &p_order_nr; 
        v_total totals.total%TYPE := 0; 

        CURSOR c_order_line IS 
                SELECT part_number, quoted_price, number_ordered 
                FROM order_line 
                WHERE order_number= v_order_nr; 
BEGIN 
        FOR r_order_line IN c_order_line LOOP 
                v_total := v_total + (r_order_line.quoted_price * 
                    r_order_line.number_ordered); 

               
dbms_output.put_line ('Order total for part ' ||
r_order_line.part_number || ' is ' || v_total);


                INSERT INTO totals (part, total)
                        VALUES (r_order_line.part_number, v_total); 
        END LOOP; 

        COMMIT WORK; 
END; 
/
Note the indentation above, failure to indent for readability will result in one full letter grade deduction and your work being returned to you to fix.    Also I will not help you with any program that is NOT readable.

 

Advanced Cursors:
1.  Using Nested For Loops
2.  Using Parameters with cursors
3.  For Update Cursors
4.  Where Current of Clause

Click here to download the set up script for the tables for the next few examples

After unzipping them in a folder (assume c:\temp).  Execute the following from sqlplus

SQL>@ c:\temp\createStudent.sql

This should create all the necessary tables and data.

Processing Nested Cursors

SET SERVEROUTPUT ON
DECLARE
        v_zip zipcode.zip%TYPE;
        CURSOR c_zip IS
                SELECT zip, city, state FROM zipcode WHERE state = 'CT';
        CURSOR c_student IS 
            SELECT first_name, last_name FROM student WHERE zip = v_zip;
BEGIN
        FOR r_zip IN c_zip LOOP
                v_zip := r_zip.zip;
                DBMS_OUTPUT.PUT_LINE (CHR(10));
                DBMS_OUTPUT.PUT_LINE ('Students living in ' || r_zip.city);
                FOR r_student IN c_student LOOP
                        DBMS_OUTPUT.PUT_LINE (r_student.first_name||' '||r_student.last_name);
                END LOOP;
        END LOOP;
END;
/

SET SERVEROUTPUT ON
DECLARE
        v_amount course.cost%TYPE;
        v_instructor_id instructor.instructor_id%TYPE;
        CURSOR c_inst IS 
                SELECT first_name, last_name, instructor_id
   
                FROM instructor;
   
     CURSOR c_cost IS
                SELECT c.cost 
                FROM course c, section s, enrollment e
                WHERE s.instructor_id = v_instructor_id
                AND c.course_no = s.course_no
                AND s.section_id = e.section_id;
BEGIN
        FOR r_inst IN c_inst LOOP
                v_instructor_id := r_inst.instructor_id;
                v_amount := 0;
                DBMS_OUTPUT.PUT_LINE ('Generated by instructor ' || 
                    r_inst.first_name || ' ' || r_inst.last_name);
                FOR r_cost IN c_cost LOOP
                        v_amount := v_amount + NVL(r_cost.cost, 0);
                END LOOP;
                DBMS_OUTPUT.PUT_LINE (TO_CHAR(v_amount, '$999,999'));
        END LOOP;
END;
/

Using Parameters with cursors

SET SERVEROUTPUT ON
DECLARE
        CURSOR c_student IS
                SELECT first_name, last_name, student_id
                FROM student
                WHERE last_name LIKE 'J%';
        CURSOR c_course (p_studid IN student.student_id%TYPE) IS
                SELECT c.description, s.section_id sec_id
                FROM course c, section s, enrollment e
                WHERE e.student_id = p_studid
                AND c.course_no = s.course_no
                AND s.section_id = e.section_id;
        CURSOR c_grade (p_sid IN section.section_id%TYPE,
                p_stuid IN student.student_id%TYPE)
                IS
                SELECT gt.description grd_desc,
                    TO_CHAR(AVG(g.numeric_grade), '999') num_grd
                FROM enrollment e, grade g, grade_type gt
                WHERE e.section_id = p_sid
                AND e.student_id = g.student_id
                AND e.student_id = p_stuid
                AND e.section_id = g.section_id
                AND g.grade_type_code = gt.grade_type_code
                GROUP BY gt.description;
BEGIN
        FOR r_student IN c_student LOOP
                DBMS_OUTPUT.PUT_LINE(CHR(10));
                DBMS_OUTPUT.PUT_LINE(r_student.first_name||' '||r_student.last_name);
                FOR r_course IN c_course(r_student.student_id) LOOP
                        DBMS_OUTPUT.PUT_LINE ('Grades for course: '|| r_course.description);
                        FOR r_grade IN c_grade(r_course.sec_id, r_student.student_id) LOOP
                                DBMS_OUTPUT.PUT_LINE (r_grade.num_grd||' '||r_grade.grd_desc);
                        END LOOP;
                END LOOP;
        END LOOP;
END;
/

For Update Cursors

DECLARE 
        CURSOR c_course IS 
                SELECT course_no, cost
                FROM course
                FOR UPDATE;
BEGIN
        FOR r_course IN c_course LOOP
                IF r_course.cost < 2500 THEN
                        UPDATE course
                                SET cost = r_course.cost + 10 
                        WHERE course_no = r_course.course_no;
                END IF;
        END LOOP;
END;
/

DECLARE 
        CURSOR c_stud_zip IS
                SELECT s.student_id, z.city
                    FROM student s, zipcode z
                WHERE z.city = 'Brooklyn'
                    AND s.zip = z.zip
                FOR UPDATE OF phone;
BEGIN
        FOR r_stud_zip IN c_stud_zip LOOP
                UPDATE student SET phone = '718'||SUBSTR(phone, 4) 
                WHERE student_id = r_stud_zip.student_id
        END LOOP;
END;
/

Where Current of Clause 

DECLARE 
        CURSOR c_stud_zip IS
                SELECT s.student_id, z.city
                    FROM student s, zipcode z
                WHERE z.city = 'Brooklyn'
                    AND s.zip = z.zip
                FOR UPDATE OF s.phone;
BEGIN
        FOR r_stud_zip IN c_stud_zip LOOP
                DBMS_OUTPUT.PUT_LINE (r_stud_zip.student_id);
                UPDATE student
                SET phone = '718'||SUBSTR(phone, 4) 
                WHERE CURRENT OF c_stud_zip;
        END LOOP;
END;
/

Implicit Cursors with Sub-Queries

You don't have to declare a cursor to deal with a SQL statement returning more than one row.   In PL/SQL you can embed a SQL-Select statement inside a for loop.

BEGIN
   FOR r_cust IN (SELECT * FROM customer ORDER BY lname, fname) LOOP
	dbms_output.put_line (r_cust.fname || ' ' || r_cust.lname);
	dbms_output.put_line (r_cust.street);
	dbms_output.put_line (r_cust.city || ', ' || r_cust.state || '  ' || r_cust.zip_code);
   END LOOP;
END;
/
The Next Example is Sourced from Oracle's Web-Site:

Using Cursor Variables

Like a cursor, a cursor variable points to the current row in the result set of a multi-row query. But, cursors differ from cursor variables the way constants differ from variables. Whereas a cursor is static, a cursor variable is dynamic because it is not tied to a specific query. You can open a cursor variable for any type-compatible query. This gives you more flexibility.

Also, you can assign new values to a cursor variable and pass it as a parameter to local and stored subprograms. This gives you an easy way to centralize data retrieval.

Cursor variables are available to every PL/SQL client. For example, you can declare a cursor variable in a PL/SQL host environment such as an OCI or Pro*C program, then pass it as input host variable (bind variable) to PL/SQL. Moreover, application development tools such as Oracle Forms and Oracle Reports, which have a PL/SQL engine, can use cursor variables entirely on the client side.

The Oracle server also has a PL/SQL engine. So, you can pass cursor variables back and forth between an application and server via remote procedure calls (RPCs).

What Are Cursor Variables?

Cursor variables are like C or Pascal pointers, which hold the memory location (address) of some item instead of the item itself. So, declaring a cursor variable creates a pointer, not an item. In PL/SQL, a pointer has datatype REF X, where REF is short for REFERENCE and X stands for a class of objects. Therefore, a cursor variable has datatype REF CURSOR.

To execute a multi-row query, Oracle opens an unnamed work area that stores processing information. To access the information, you can use an explicit cursor, which names the work area. Or, you can use a cursor variable, which points to the work area. Whereas a cursor always refers to the same query work area, a cursor variable can refer to different work areas. So, cursors and cursor variables are not interoperable; that is, you cannot use one where the other is expected.

Why Use Cursor Variables?

Mainly, you use cursor variables to pass query result sets between PL/SQL stored subprograms and various clients. Neither PL/SQL nor any of its clients owns a result set; they simply share a pointer to the query work area in which the result set is stored. For example, an OCI client, Oracle Forms application, and Oracle server can all refer to the same work area.

A query work area remains accessible as long as any cursor variable points to it. Therefore, you can pass the value of a cursor variable freely from one scope to another. For example, if you pass a host cursor variable to a PL/SQL block embedded in a Pro*C program, the work area to which the cursor variable points remains accessible after the block completes.

If you have a PL/SQL engine on the client side, calls from client to server impose no restrictions. For example, you can declare a cursor variable on the client side, open and fetch from it on the server side, then continue to fetch from it back on the client side. Also, you can reduce network traffic by having a PL/SQL block open (or close) several host cursor variables in a single round trip.

Defining REF CURSOR Types

To create cursor variables, you take two steps. First, you define a REF CURSOR type, then declare cursor variables of that type. You can define REF CURSOR types in any PL/SQL block, subprogram, or package using the syntax

TYPE ref_type_name IS REF CURSOR RETURN return_type;

where ref_type_name is a type specifier used in subsequent declarations of cursor variables and return_type must represent a record or a row in a database table. In the following example, you specify a return type that represents a row in the database table dept:

DECLARE
   TYPE DeptCurTyp IS REF CURSOR RETURN dept%ROWTYPE;

REF CURSOR types can be strong (restrictive) or weak (nonrestrictive). As the next example shows, a strong REF CURSOR type definition specifies a return type, but a weak definition does not:

DECLARE
   TYPE EmpCurTyp IS REF CURSOR RETURN emp%ROWTYPE;  -- strong
   TYPE GenericCurTyp IS REF CURSOR;  -- weak

Strong REF CURSOR types are less error prone because the PL/SQL compiler lets you associate a strongly typed cursor variable only with type-compatible queries. However, weak REF CURSOR types are more flexible because the compiler lets you associate a weakly typed cursor variable with any query.

Declaring Cursor Variables

Once you define a REF CURSOR type, you can declare cursor variables of that type in any PL/SQL block or subprogram. In the following example, you declare the cursor variable dept_cv:

DECLARE
   TYPE DeptCurTyp IS REF CURSOR RETURN dept%ROWTYPE;
   dept_cv DeptCurTyp;  -- declare cursor variable

Note: You cannot declare cursor variables in a package. Unlike packaged variables, cursor variables do not have persistent state. Remember, declaring a cursor variable creates a pointer, not an item. So, cursor variables cannot be saved in the database.

Cursor variables follow the usual scoping and instantiation rules. Local PL/SQL cursor variables are instantiated when you enter a block or subprogram and cease to exist when you exit.

In the RETURN clause of a REF CURSOR type definition, you can use %ROWTYPE to specify a record type that represents a row returned by a strongly (not weakly) typed cursor variable, as follows:

DECLARE
   TYPE TmpCurTyp IS REF CURSOR RETURN emp%ROWTYPE;
   tmp_cv TmpCurTyp;  -- declare cursor variable
   TYPE EmpCurTyp IS REF CURSOR RETURN tmp_cv%ROWTYPE;
   emp_cv EmpCurTyp;  -- declare cursor variable

Likewise, you can use %TYPE to provide the datatype of a record variable, as the following example shows:

DECLARE
   dept_rec dept%ROWTYPE;  -- declare record variable
   TYPE DeptCurTyp IS REF CURSOR RETURN dept_rec%TYPE;
   dept_cv DeptCurTyp;  -- declare cursor variable

In the final example, you specify a user-defined RECORD type in the RETURN clause:

DECLARE
   TYPE EmpRecTyp IS RECORD (
      empno NUMBER(4),
      ename VARCHAR2(1O),
      sal   NUMBER(7,2));
   TYPE EmpCurTyp IS REF CURSOR RETURN EmpRecTyp;
   emp_cv EmpCurTyp;  -- declare cursor variable

Cursor Variables As Parameters

You can declare cursor variables as the formal parameters of functions and procedures. In the following example, you define the REF CURSOR type EmpCurTyp, then declare a cursor variable of that type as the formal parameter of a procedure:

DECLARE
   TYPE EmpCurTyp IS REF CURSOR RETURN emp%ROWTYPE;
   PROCEDURE open_emp_cv (emp_cv IN OUT EmpCurTyp) IS ...

Caution: Like all pointers, cursor variables increase the possibility of parameter aliasing. For more information, see "Parameter Aliasing".

Controlling Cursor Variables

You use three statements to control a cursor variable: OPEN-FOR, FETCH, and CLOSE. First, you OPEN a cursor variable FOR a multi-row query. Then, you FETCH rows from the result set one at a time. When all the rows are processed, you CLOSE the cursor variable.

Opening a Cursor Variable

The OPEN-FOR statement associates a cursor variable with a multi-row query, executes the query, and identifies the result set. Here is the syntax:

OPEN {cursor_variable_name | :host_cursor_variable_name} 
   FOR select_statement;

where host_cursor_variable_name identifies a cursor variable declared in a PL/SQL host environment such as an OCI or Pro*C program.

Unlike cursors, cursor variables take no parameters. However, no flexibility is lost because you can pass whole queries (not just parameters) to a cursor variable. The query can reference host variables and PL/SQL variables, parameters, and functions but cannot be FOR UPDATE.

In the example below, you open the cursor variable emp_cv. Notice that you can apply cursor attributes (%FOUND, %NOTFOUND, %ISOPEN, and %ROWCOUNT) to a cursor variable.

IF NOT emp_cv%ISOPEN THEN 
   /* Open cursor variable. */
   OPEN emp_cv FOR SELECT * FROM emp;
END IF;

Other OPEN-FOR statements can open the same cursor variable for different queries. You need not close a cursor variable before reopening it. (Recall that consecutive OPENs of a static cursor raise the predefined exception CURSOR_ALREADY_OPEN.) When you reopen a cursor variable for a different query, the previous query is lost.

Typically, you open a cursor variable by passing it to a stored procedure that declares a cursor variable as one of its formal parameters. For example, the following packaged procedure opens the cursor variable emp_cv:

CREATE PACKAGE emp_data AS
   ...
   TYPE EmpCurTyp IS REF CURSOR RETURN emp%ROWTYPE;
   PROCEDURE open_emp_cv (emp_cv IN OUT EmpCurTyp);
END emp_data;
 
CREATE PACKAGE BODY emp_data AS
   ...
   PROCEDURE open_emp_cv (emp_cv IN OUT EmpCurTyp) IS
   BEGIN
      OPEN emp_cv FOR SELECT * FROM emp;
   END open_emp_cv;
END emp_data;

When you declare a cursor variable as the formal parameter of a subprogram that opens the cursor variable, you must specify the IN OUT mode. That way, the subprogram can pass an open cursor back to the caller.

Alternatively, you can use a stand-alone procedure to open the cursor variable. Simply define the REF CURSOR type in a separate package, then reference that type in the stand-alone procedure. For instance, if you create the following bodiless package, you can create stand-alone procedures that reference the types it defines:

CREATE PACKAGE cv_types AS
   TYPE GenericCurTyp IS REF CURSOR;
   TYPE EmpCurTyp IS REF CURSOR RETURN emp%ROWTYPE;
   TYPE DeptCurTyp IS REF CURSOR RETURN dept%ROWTYPE;
   ...
END cv_types;

In the next example, you create a stand-alone procedure that references the REF CURSOR type EmpCurTyp, which is defined in the package cv_types:

CREATE PROCEDURE open_emp_cv (emp_cv IN OUT cv_types.EmpCurTyp) AS
BEGIN 
   OPEN emp_cv FOR SELECT * FROM emp;
END open_emp_cv;

To centralize data retrieval, you can group type-compatible queries in a stored procedure. In the example below, the packaged procedure declares a selector as one of its formal parameters. (In this context, a selector is a variable used to select one of several alternatives in a conditional control statement.) When called, the procedure opens the cursor variable emp_cv for the chosen query.

CREATE PACKAGE emp_data AS
   TYPE GenericCurTyp IS REF CURSOR;
   TYPE EmpCurTyp IS REF CURSOR RETURN emp%ROWTYPE;
   PROCEDURE open_emp_cv (emp_cv IN OUT EmpCurTyp, choice NUMBER);
END emp_data;

CREATE PACKAGE BODY emp_data AS
   PROCEDURE open_emp_cv (
      emp_cv IN OUT EmpCurTyp,
      choice NUMBER) IS
   BEGIN
      IF choice = 1 THEN
         OPEN emp_cv FOR SELECT * FROM emp WHERE comm IS NOT NULL;
      ELSIF choice = 2 THEN
         OPEN emp_cv FOR SELECT * FROM emp WHERE sal > 2500;
      ELSIF choice = 3 THEN
         OPEN emp_cv FOR SELECT * FROM emp WHERE deptno = 20;
      END IF;
   END open_emp_cv;
END emp_data;

For more flexibility, you can pass a cursor variable and a selector to a stored procedure that executes queries with different return types. Consider this example:

CREATE PACKAGE BODY emp_data AS
   PROCEDURE open_cv (
      generic_cv IN OUT GenericCurTyp,
      choice     NUMBER) IS
   BEGIN
      IF choice = 1 THEN
         OPEN generic_cv FOR SELECT * FROM emp;
      ELSIF choice = 2 THEN
         OPEN generic_cv FOR SELECT * FROM dept;
      ELSIF choice = 3 THEN
         OPEN generic_cv FOR SELECT * FROM salgrade;
      END IF;
   END open_cv;
END emp_data;
 
SQL> CREATE TABLE emp AS SELECT * FROM rtimlin.emp;
CREATE OR REPLACE PACKAGE cv_types AS
   TYPE GenericCurTyp IS REF CURSOR;
   TYPE EmpCurTyp IS REF CURSOR RETURN emp%ROWTYPE;
   TYPE emp_list_type IS TABLE OF emp%ROWTYPE INDEX BY BINARY_INTEGER;
END cv_types;
/

CREATE OR REPLACE PROCEDURE open_emp_cv (emp_cv IN OUT cv_types.EmpCurTyp) AS
BEGIN 
   OPEN emp_cv FOR SELECT * FROM emp;
END open_emp_cv;
/

SET SERVEROUTPUT ON;
DECLARE
	emp_rec emp%ROWTYPE;
	test_cv cv_types.EmpCurTyp;
BEGIN
	open_emp_cv (test_cv);

	LOOP
		FETCH test_cv INTO emp_rec;

		EXIT WHEN test_cv%NOTFOUND;

		DBMS_OUTPUT.PUT_LINE (emp_rec.empname);
	END LOOP;

	CLOSE test_cv;
END;
/
-- Example 2: Using a Table of Records
DECLARE
    test_cv cv_types.EmpCurTyp;
    emp_list cv_types.emp_list_type;
    i BINARY_INTEGER := 0;
BEGIN
    open_emp_cv (test_cv);
    LOOP
        FETCH test_cv INTO emp_list(i);
        EXIT WHEN test_cv%NOTFOUND;

        DBMS_OUTPUT.PUT_LINE (emp_list(i).empname);
        i := i +1;
    END LOOP;
    CLOSE test_cv;
END;
/