42.8. Cursors

42.8.1. Implicit cursors
42.8.2. Explicit cursors
42.8.3. Processing Query Result Sets
42.8.4. Cursor Variables

Rather than executing a whole query at once, it is possible to set up a cursor that encapsulates the query, and then read the query result a few rows at a time. One reason for doing this is to avoid memory overrun when the result contains a large number of rows. (However, pl/sql users do not normally need to worry about that, since FOR loops automatically use a cursor internally to avoid memory problems.) A more interesting usage is to return a reference to a cursor that a function has created, allowing the caller to read the rows. This provides an efficient way to return large row sets from functions.

A cursor is a pointer to a private SQL area that stores information about processing a specific SELECT or DML statement.

A cursor that is constructed and managed by pl/sql is an implicit cursor. A cursor that you construct and manage is an explicit cursor. You can get information about any session cursor from its attributes (which you can reference in procedural statements, but not in SQL statements).

42.8.1. Implicit cursors

An implicit cursor is a session cursor that is constructed and managed by pl/sql. pl/sql opens an implicit cursor every time you run a SELECT or DML statement.You cannot control an implicit cursor, but you can get information from its attributes.

The implicit cursor gives the result set of the SELECT query to a RECORD variable, which supports two new keywords INNER and OUTER in addition to identifiers.

For example, the RECORD variable of the implicit cursor to get the result set is named INNER:

create table record_filed(id int, zqdm varchar(100));
insert into record_filed values(12,'hello world!');

DECLARE
  CURSOR c_a IS
     select * from record_filed;
  zqdm_p  record_filed.id%TYPE;
BEGIN
FOR inner IN c_a LOOP
  zqdm_p := inner.id;
end loop;
end;
/

For example, the RECORD variable of the implicit cursor to get the result set is named OUTER:

create table record_filed(id int, zqdm varchar(100));
insert into record_filed values(12,'hello world!');

DECLARE
  CURSOR c_a IS
     select * from record_filed;
  zqdm_p  record_filed.id%TYPE;
BEGIN
FOR outer IN c_a LOOP
  zqdm_p := outer.id;
end loop;
end;
/

It is worth noting:

  • The OUTER is a keyword of UNRESERVED_KEYWORD, which can be used as a table name, column name, function name, type name, etc; INNER is the keyword of TYPE_FUNC_NAME_KEYWORD, which can only be used as the function name;

  • If the type name(Specifically, associative arrays, varrays, and nested tables) is the same as the name of the FOR loop variable, an error is reported;

  • Outside of pl/sql, After you have created an object with the same name as the OUTER, you are not allowed to create an object of the same type as the OUTER.

An implicit cursor closes after its associated statement runs; however, its attribute values remain available until another SELECT or DML statement runs.

The most recently run SELECT or DML statement might be in a different scope. To save an attribute value for later use, assign it to a local variable immediately. Otherwise, other operations, such as subprogram invocations, might change the value of the attribute before you can test it.

42.8.1.1. SQL%ISOPEN Attribute: Is the Cursor Open?

SQL%ISOPEN always returns FALSE,because an implicit cursor always closes after its associated statement runs.

42.8.1.2. SQL%FOUND Attribute: Were Any Rows Affected?

SQL%FOUND returns:

  • NULL if no SELECT or DML statement has run,

  • TRUE if a SELECT statement returned one or more rows or a DML statement affected one or more rows,

  • FALSE otherwise.

uses SQL%FOUND to determine if a DELETE statement affected any rows. For example:

create table employees(salary int, job_id varchar(100), employee_id int, last_name varchar(100), first_name varchar(100), department_id int, hire_date date);
create table departments(department_id int, department_name varchar(100), location_id int, staff int);
create table locations(country_id varchar(100),city varchar(100),location_id int);
create table jobs(job_title varchar(100), job_id varchar(100));

insert into employees values (12308, 'SA_REP', 100, 'huaxiang', 'zhao', 2345, TO_DATE('31-DEC-2005', 'DD-MON-YYYY'));
insert into employees values (12301, 'AD_FG', 12301, 'huahai', 'zhao', 2345, TO_DATE('31-DEC-2004', 'DD-MON-YYYY'));
insert into employees values (12302, 'SA_REP', 12302, 'huaduo', 'zhao', 2345, TO_DATE('31-DEC-2004', 'DD-MON-YYYY'));
insert into employees values (12302, 'S[HT]_CLERK', 12302, 'huashuai', 'zhao', 2345, TO_DATE('31-DEC-2004', 'DD-MON-YYYY'));
insert into employees values (12302, 'AD_FG', 100, 'huashuai', 'zhao', 2345, TO_DATE('31-DEC-2004', 'DD-MON-YYYY'));
insert into employees values (12304, 'AD_FG', 100, 'huashuai', 'zhao', 2345, TO_DATE('31-DEC-2004', 'DD-MON-YYYY'));
insert into employees values (12305, 'AD_FG', 100, 'huashuai', 'zhao', 2345, TO_DATE('31-DEC-2004', 'DD-MON-YYYY'));
insert into employees values (12306, 'AD_FG', 100, 'huashuai', 'zhao', 2345, TO_DATE('31-DEC-2004', 'DD-MON-YYYY'));
insert into employees values (12307, 'AD_FG', 100, 'huashuai', 'zhao', 2345, TO_DATE('31-DEC-2004', 'DD-MON-YYYY'));
insert into employees values (12308, 'AD_MAN', 100, 'huaxiang', 'zhao', 2345, TO_DATE('31-DEC-2004', 'DD-MON-YYYY'));

insert into departments values (110, 'asd', 1700, 2345);
insert into departments values (2345, 'asd', 1800, 2345);
insert into departments values (110, 'asd', 1900, 2345);
insert into departments values (110, 'AD_FG', 12302, 2345);

insert into locations values ('JP', 'Japan', 1700);
insert into locations values ('Toronto', 'Canada', 1800);
insert into locations values ('WH', 'Whitehorse', 1900);

insert into jobs values ('sdadf', 'AD_FG');

DROP TABLE dept_temp;
CREATE TABLE dept_temp AS
  SELECT * FROM departments;
 
DECLARE
  dept_no NUMBER := 12;
BEGIN
  DELETE FROM dept_temp
  WHERE department_id = dept_no;
 
  IF SQL%FOUND THEN
    DBMS_OUTPUT.PUT_LINE ('Delete succeeded for department number ' || dept_no);
  ELSE
    DBMS_OUTPUT.PUT_LINE ('No department number ' || dept_no);
  END IF;
END;
/

42.8.1.3. SQL%NOTFOUND Attribute: Were Any Rows Affected?

SQL%NOTFOUND (the logical opposite of SQL%FOUND) returns:

  • NULL if no SELECT or DML statement has run,

  • FALSE if a SELECT statement returned one or more rows or a DML statement affected one or more rows.

42.8.1.4. SQL%ROWCOUNT Attribute: How Many Rows Were Affected?

SQL%ROWCOUNT returns:

  • NULL if no SELECT or DML statement has run,

  • Otherwise, the number of rows returned by a SELECT statement or affected by a DML statement (an INTEGER).

uses SQL%ROWCOUNT to determine the number of rows that were deleted. For example:

DROP TABLE employees_temp;
CREATE TABLE employees_temp AS
  SELECT * FROM employees;

DECLARE
  mgr_no int := 123005;
  rowcount int := 0;
BEGIN
  DELETE FROM employees_temp WHERE employee_id = mgr_no;
  rowcount := SQL%ROWCOUNT;
  DBMS_OUTPUT.PUT_LINE ('Number of employees deleted: ' || rowcount);
END;
/

42.8.2. Explicit cursors

An explicit cursor is a session cursor that is constructed and managed.You must declare and define an explicit cursor, giving it a name and associating it with a query (typically, the query returns multiple rows). Then you can process the query result set in either of these ways:

  • Open the explicit cursor, fetch rows from the result set, and close the explicit cursor.

  • Use the explicit cursor in a cursor FOR LOOP statement.

Unlike an implicit cursor, you can reference an explicit cursor or cursor variable by its name. Therefore, an explicit cursor or cursor variable is called a named cursor.

42.8.2.1. Declaring and Defining Explicit Cursors

You can either declare an explicit cursor first and then define it later in the same block, subprogram, or package, or declare and define it at the same time.

An explicit cursor declaration, which only declares a cursor, has this syntax:

CURSOR cursor_name parameter_list RETURN return_type;

An explicit cursor definition has this syntax:

CURSOR cursor_name parameter_list RETURN return_type IS select_statement;

To actually use theexplicit cursor, you can define it directly without declaring it.

declares and defines three explicit cursors. For example:

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;
 
  CURSOR c3 RETURN locations%ROWTYPE;      -- Declare c3

  CURSOR c3 IS                             -- Define c3,
    SELECT * FROM locations                -- omitting return type
    WHERE country_id = 'JP';
BEGIN
  NULL;
END;
/

42.8.2.2. Opening and Closing Explicit Cursors

After declaring and defining an explicit cursor, you can open it with the OPEN statement. Cursor names ignore case. The cursor is defined without parameters, and can also be opened in the form of cursor name plus ().

Both of the following uses open cursor c_tmp, for example:

open C_tmp;
open c_tmp;
open c_tmp();

You close an open explicit cursor with the CLOSE statement, thereby allowing its resources to be reused. After closing a cursor, you cannot fetch records from its result set or reference its attributes.

42.8.2.3. Fetching Data with Explicit Cursors

After opening an explicit cursor, you can fetch the rows of the query result set with the FETCH statement. The basic syntax of a FETCH statement that returns one row is:

FETCH cursor_name INTO into_clause;

The into_clause is either a list of variables or a single record variable. For each column that the query returns, the variable list or record must have a corresponding type-compatible variable or field. The %TYPE and %ROWTYPE attributes are useful for declaring variables and records for use in FETCH statements.

The FETCH statement 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.

Typically, you use the FETCH statement inside a LOOP statement, which you exit when the FETCH statement runs out of rows. To detect this exit condition, use the cursor attribute %NOTFOUND. pl/sql does not raise an exception when a FETCH statement returns no rows.

fetches the result sets of two explicit cursors one row at a time, using FETCH and %NOTFOUND inside LOOP statements. The first FETCH statement retrieves column values into variables. The second FETCH statement retrieves column values into a record. The variables and record are declared with %TYPE and %ROWTYPE, respectively. For example:

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;  -- variable for last_name
  v_jobid     employees.job_id%TYPE;     -- variable for job_id

  CURSOR c2 IS
    SELECT * FROM employees
    WHERE REGEXP_LIKE (job_id, '[ACADFIMKSA]_M[ANGR]')
    ORDER BY job_id;

  v_employees employees%ROWTYPE;  -- record variable for row of table

BEGIN
  OPEN c1;
  LOOP  -- Fetches 2 columns into variables
    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  -- Fetches entire row into the v_employees record
    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;
/

fetches the first five rows of a result set into five records, using five FETCH statements, each of which fetches into a different record variable. The record variables are declared with %ROWTYPE. For example:

DECLARE
  CURSOR c IS
    SELECT e.job_id, j.job_title
    FROM employees e, jobs j
    WHERE e.job_id = j.job_id AND e.employee_id = 100
    ORDER BY last_name;
 
  -- Record variables for rows of cursor result set:
 
  job1 c%ROWTYPE;
  job2 c%ROWTYPE;
  job3 c%ROWTYPE;
  job4 c%ROWTYPE;
  job5 c%ROWTYPE;
 
BEGIN
  OPEN c;
  FETCH c INTO job1;  -- fetches first row
  FETCH c INTO job2;  -- fetches second row
  FETCH c INTO job3;  -- fetches third row
  FETCH c INTO job4;  -- fetches fourth row
  FETCH c INTO job5;  -- fetches fifth row
  CLOSE c;
 
  DBMS_OUTPUT.PUT_LINE (job1.job_title || ' (' || job1.job_id || ')');
  DBMS_OUTPUT.PUT_LINE (job2.job_title || ' (' || job2.job_id || ')');
  DBMS_OUTPUT.PUT_LINE (job3.job_title || ' (' || job3.job_id || ')');
  DBMS_OUTPUT.PUT_LINE (job4.job_title || ' (' || job4.job_id || ')');
  DBMS_OUTPUT.PUT_LINE (job5.job_title || ' (' || job5.job_id || ')');
END;
/

42.8.2.4.  Variables in Explicit Cursor Queries

An explicit cursor query can reference any variable in its scope. When you open an explicit cursor, pl/sql evaluates any variables in the query and uses those values when identifying the result set. Changing the values of the variables later does not change the result set.

the explicit cursor query references the variable factor. When the cursor opens, factor has the value 2. Therefore, sal_multiple is always 2 times sal, despite that factor is incremented after every fetch. For example:

DECLARE
  sal           employees.salary%TYPE;
  sal_multiple  employees.salary%TYPE;
  factor        INTEGER := 2;
 
  CURSOR c1 IS
    SELECT salary, salary*factor FROM employees
    WHERE job_id LIKE 'AD_%';
 
BEGIN
  OPEN c1;  -- PL/SQL evaluates factor
 
  LOOP
    FETCH c1 INTO sal, sal_multiple;
    EXIT WHEN c1%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE ('factor = ' || factor);
    DBMS_OUTPUT.PUT_LINE ('sal          =  ' || sal);
    DBMS_OUTPUT.PUT_LINE ('sal_multiple = ' || sal_multiple);
    factor := factor + 1;  -- Does not affect sal_multiple
  END LOOP;
 
  CLOSE c1;
END;
/

Variable in Explicit Cursor Query—Result Set Change. For example:

DECLARE
  sal           employees.salary%TYPE;
  sal_multiple  employees.salary%TYPE;
  factor        INTEGER := 2;
 
  CURSOR c1 IS
    SELECT salary, salary*factor FROM employees
    WHERE job_id LIKE 'AD_%';
 
BEGIN
  DBMS_OUTPUT.PUT_LINE ('factor = ' || factor);
  OPEN c1;  -- PL/SQL evaluates factor
  LOOP
    FETCH c1 INTO sal, sal_multiple;
    EXIT WHEN c1%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE ('sal          =  ' || sal);
    DBMS_OUTPUT.PUT_LINE ('sal_multiple = ' || sal_multiple);
  END LOOP;
  CLOSE c1;
 
  factor := factor + 1;
 
  DBMS_OUTPUT.PUT_LINE ('factor = ' || factor);
  OPEN c1;  -- PL/SQL evaluates factor
  LOOP
    FETCH c1 INTO sal, sal_multiple;
    EXIT WHEN c1%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE ('sal          =  ' || sal);
    DBMS_OUTPUT.PUT_LINE ('sal_multiple = ' || sal_multiple);
  END LOOP;
  CLOSE c1;
END;
/

42.8.2.5. When Explicit Cursor Queries Need Column Aliases

When an explicit cursor query includes a virtual column (an expression), that column must have an alias if either of the following is true:

You close an open explicit cursor with the CLOSE statement, thereby allowing its resources to be reused. 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 use the cursor to fetch into a record that was declared with %ROWTYPE,

  • You want to reference the virtual column in your program.

The virtual column in the explicit cursor needs an alias for both of the preceding reasons. For example:

DECLARE
  CURSOR c1 IS
    SELECT employee_id,
           (salary * .05) raise
    FROM employees
    WHERE job_id LIKE '%_MAN'
    ORDER BY employee_id;
  emp_rec c1%ROWTYPE;
BEGIN
  OPEN c1;
  LOOP
    FETCH c1 INTO emp_rec;
    EXIT WHEN c1%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE ('Raise for employee #' ||  emp_rec.employee_id || ' is $' || emp_rec.raise); 
  END LOOP;
  CLOSE c1;
END;
/

42.8.2.6. Explicit Cursors that Accept Parameters

You can create an explicit cursor that has formal parameters, and then pass different actual parameters to the cursor each time you open it. In the cursor query, you can use a formal cursor parameter anywhere that you can use a constant. Outside the cursor query, you cannot reference formal cursor parameters.

Creates an explicit cursor whose two formal parameters represent a job and its maximum salary. When opened with a specified job and maximum salary, the cursor query selects the employees with that job who are overpaid (for each such employee, the query selects the first and last name and amount overpaid). Next, the example creates a procedure that prints the cursor query result set (for information about procedures, see PL/SQL Subprograms). Finally, the example opens the cursor with one set of actual parameters, prints the result set, closes the cursor, opens the cursor with different actual parameters, prints the result set, and closes the cursor. For example:

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;
 
    last_name_   employees.last_name%TYPE;
    first_name_  employees.first_name%TYPE;
    overpayment_      employees.salary%TYPE;
BEGIN
  DBMS_OUTPUT.PUT_LINE ('----------------------');
  DBMS_OUTPUT.PUT_LINE ('Overpaid Stock Clerks:');
  DBMS_OUTPUT.PUT_LINE ('----------------------');
  OPEN c('ST_CLERK', 5000);
  LOOP
    FETCH c INTO last_name_, first_name_, overpayment_;
    EXIT WHEN c%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE (last_name_ || ',' || first_name_ || ', (by' || overpayment_ || ')');
  END LOOP; 
  CLOSE c;
 
  DBMS_OUTPUT.PUT_LINE ('----------------------');
  DBMS_OUTPUT.PUT_LINE ('Overpaid Sales Representatives:');
  DBMS_OUTPUT.PUT_LINE ('----------------------');
  OPEN c('SA_REP', 10000);
  LOOP
    FETCH c INTO last_name_, first_name_, overpayment_;
    EXIT WHEN c%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE (last_name_ || ',' || first_name_ || ', (by' || overpayment_ || ')');
  END LOOP;
  CLOSE c;
END;
/

42.8.2.7. Formal Cursor Parameters with Default Values

When you create an explicit cursor with formal parameters, you can specify default values for them. When a formal parameter has a default value, its corresponding actual parameter is optional. If you open the cursor without specifying the actual parameter, then the formal parameter has its default value.

parameter_name [IN] data_type [:= | default expression]

The application scenarios are stored procedures, functions, anonymous blocks and packages. Compared with Oracle, there are some differences. For example, for data_type, Oracle supports the declaration of char in the cursor parameter string, but the default value of lightdb's char length is 1, so for strings whose length exceeds 1, you need to specify the char length, char(100). But for variable-length character types, such as varchar, varchar2, etc., the usage is the same as oracle. And does not support oracle types: string, long, raw, long raw, rowid, timestamp with local time, etc. The constant type defined by constant is not supported in the package.

Expressions also have some differences compared to Oracle.

  • Boolean Expressions

    • The package supports functions that return boolean types, boolean expressions and boolean literals (NULL supported by oracle does not support),

    • Functions, stored procedures, and anonymous blocks support functions that return boolean types, boolean constants, Boolean variables, boolean expressions, and boolean literals (NULL supported by oracle does not support).

  • Character Expressions

    • The package supports functions that return values of type characer and characer literals,

    • Functions, stored procedures, and anonymous blocks support functions that return values of type characer, characer constants, characer variables, characer expressions, and characer literals.

  • Date Expressions

    • The package supports functions that return values of type date and date literals,

    • Functions, stored procedures, and anonymous blocks support functions that return values of type date, date constants, date variables, date expressions, and date literals.

  • Numeric Expressions

    • Not supported in package: collection, named_cursor, numberic_constant, numberic_variable, placeholder, SQL% cursor attribute, ** (operator not supported),

    • Not supported in functions, stored procedures, anonymous blocks: collection, named_cursor, placeholder, SQL% cursor attribute, ** (operator not supported).

  • Case Expressions

    • search_case_expression and simple_case_expression are supported in packages, functions, stored procedures, anonymous blocks.

Creates an explicit cursor whose formal parameter represents a location ID. The default value of the parameter is the location ID of company headquarters. For example:

DECLARE
  CURSOR c (location NUMBER DEFAULT 1700) IS
    SELECT d.department_name,
           e.last_name manager,
           l.city
    FROM departments d, employees e, locations l
    WHERE l.location_id = location
      AND l.location_id = d.location_id
      AND d.department_id = e.department_id
    ORDER BY d.department_id;
    dept_name  departments.department_name%TYPE;
    mgr_name   employees.last_name%TYPE;
    city_name  locations.city%TYPE;
BEGIN
  DBMS_OUTPUT.PUT_LINE ('DEPARTMENTS AT HEADQUARTERS:');
  DBMS_OUTPUT.PUT_LINE ('--------------------------------');
  OPEN c;
  LOOP
    FETCH c INTO dept_name, mgr_name, city_name;
    EXIT WHEN c%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE (dept_name || ' (Manager: ' || mgr_name || ')');
  END LOOP;
  DBMS_OUTPUT.PUT_LINE ('--------------------------------');
  CLOSE c;
 
  DBMS_OUTPUT.PUT_LINE ('DEPARTMENTS IN CANADA:');
  DBMS_OUTPUT.PUT_LINE ('--------------------------------');
  OPEN c(1800); -- Toronto
  LOOP
    FETCH c INTO dept_name, mgr_name, city_name;
    EXIT WHEN c%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE (dept_name || ' (Manager: ' || mgr_name || ')');
  END LOOP; 
  CLOSE c;
  OPEN c(1900); -- Whitehorse
  LOOP
    FETCH c INTO dept_name, mgr_name, city_name;
    EXIT WHEN c%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE (dept_name || ' (Manager: ' || mgr_name || ')');
  END LOOP;
  CLOSE c;
END;
/

If you add formal parameters to a cursor, and you specify default values for the added parameters, then you need not change existing references to the cursor. For example:

DECLARE
  CURSOR c (job VARCHAR2, max_sal NUMBER,
            hired DATE DEFAULT TO_DATE('31-DEC-1999', 'DD-MON-YYYY')) IS
    SELECT last_name, first_name, (salary - max_sal) overpayment
    FROM employees
    WHERE job_id = job
    AND salary > max_sal
    AND hire_date > hired
    ORDER BY salary;
    last_name_   employees.last_name%TYPE;
    first_name_  employees.first_name%TYPE;
    overpayment_      employees.salary%TYPE;
BEGIN
  DBMS_OUTPUT.PUT_LINE ('--------------------------------');
  DBMS_OUTPUT.PUT_LINE ('Overpaid Sales Representatives Hired After 2004:');
  DBMS_OUTPUT.PUT_LINE ('--------------------------------');
  OPEN c('SA_REP', 10000);
  LOOP
    FETCH c INTO last_name_, first_name_, overpayment_;
    EXIT WHEN c%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE (last_name_ || ', (by ' || overpayment_ || ')');
  END LOOP;  
  CLOSE c;
END;
/

42.8.2.8. Explicit Cursor Attributes

The syntax for the value of an explicit cursor attribute is cursor_name immediately followed by attribute (for example, c1%ISOPEN).

You close an open explicit cursor with the CLOSE statement, thereby allowing its resources to be reused. 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.

42.8.2.8.1. %ISOPEN Attribute: Is the Cursor Open?

%ISOPEN returns true,if its explicit cursor is open. false oterwise.

opens the explicit cursor c1 only if it is not open and closes it only if it is open. For example:

DECLARE
  CURSOR c1 IS
    SELECT last_name, salary FROM employees
    WHERE ROWNUM < 11;

  the_name employees.last_name%TYPE;
  the_salary employees.salary%TYPE;
BEGIN
  IF NOT c1%ISOPEN THEN
    OPEN c1;
  END IF;

  FETCH c1 INTO the_name, the_salary;

  IF c1%ISOPEN THEN
    CLOSE c1;
  END IF;
END;
/

To use ROWNUM,the value of lightdb_dblevel_syntax_compatible_type must be oracle.

42.8.2.8.2. %FOUND Attribute: Were Any Rows Affected?

%FOUND returns:

  • NULL after the explicit cursor is opened but before the first fetch,

  • TRUE f the most recent fetch from the explicit cursor returned a row,

  • FALSE otherwise.

loops through a result set, printing each fetched row and exiting when there are no more rows to fetch. For example:

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
      EXIT;
    END IF;
  END LOOP;
END;
/

42.8.2.8.3. %NOTFOUND Attribute: Were Any Rows Affected?

%NOTFOUND (the logical opposite of %FOUND) returns:

  • NULL after the explicit cursor is opened but before the first fetch,

  • FALSE if the most recent fetch from the explicit cursor returned a row,

  • TRUE otherwise.

%NOTFOUND is useful for exiting a loop when FETCH fails to return a row. For example:

DECLARE
  CURSOR c1 IS
    SELECT last_name FROM employees
    WHERE ROWNUM < 11
    ORDER BY last_name;
  rowcount int;
  name_1  employees.last_name%TYPE;
BEGIN
  OPEN c1;
  LOOP
    FETCH c1 INTO name_1;
    EXIT WHEN c1%NOTFOUND;
    rowcount := c1%ROWCOUNT;
    DBMS_OUTPUT.PUT_LINE (rowcount ||'. ' || name_1);
    IF c1%ROWCOUNT = 5 THEN
       DBMS_OUTPUT.PUT_LINE ('--- Fetched 5th row ---');
    END IF;
  END LOOP;
  CLOSE c1;
END;
/

42.8.2.8.4. %ROWCOUNT Attribute: How Many Rows Were Affected?

%ROWCOUNT returns:

  • Zero after the explicit cursor is opened but before the first fetch,

  • Otherwise, the number of rows fetched (an INTEGER).

numbers and prints the rows that it fetches and prints a message after fetching the fifth row. For example:

DECLARE
  CURSOR c1 IS
    SELECT last_name FROM employees
    WHERE ROWNUM < 11
    ORDER BY last_name;

  name_1  employees.last_name%TYPE;
  rowcount int;
BEGIN
  OPEN c1;
  LOOP
    FETCH c1 INTO name_1;
    EXIT WHEN c1%NOTFOUND OR c1%NOTFOUND IS NULL;
    rowcount := c1%ROWCOUNT;
    DBMS_OUTPUT.PUT_LINE (rowcount || '. ' || name_1);
    IF c1%ROWCOUNT = 5 THEN
       DBMS_OUTPUT.PUT_LINE ('--- Fetched 5th row ---');
    END IF;
  END LOOP;
  CLOSE c1;
END;
/

42.8.3. Processing Query Result Sets

In pl/sql, as in traditional database programming, you use cursors to process query result sets. However, in pl/sql, you can use either implicit or explicit cursors.

The former need less code, but the latter are more flexible. For example, explicit cursors can accept parameters.

The following pl/sql statements use implicit cursors that pl/sql defines and manages for you:

  • SELECT INTO,

  • Implicit cursor FOR LOOP.

The following pl/sql statements use explicit cursors:

  • Explicit cursor FOR LOOP,

  • OPEN, FETCH, and CLOSE.

42.8.3.1. Processing Query Result Sets With SELECT INTO Statements

Using an implicit cursor, the SELECT INTO statement retrieves values from one or more database tables (as the SQL SELECT statement does) and stores them in variables (which the SQL SELECT statement does not do).

42.8.3.1.1. Handling Single-Row Result Sets

If you expect the query to return only one row, then use the SELECT INTO statement to store values from that row in either one or more scalar variables, or one record variable.

If the query might return multiple rows, but you care about only the nth row, then restrict the result set to that row with the clause WHERE ROWNUM=n.

42.8.3.1.2. Handling Large Multiple-Row Result Sets

If you must assign a large quantity of table data to variables, LightDB recommends using the SELECT INTO statement with the BULK COLLECT clause.

42.8.3.2.  Processing Query Result Sets With Cursor FOR LOOP Statements

The cursor FOR LOOP statement lets you run a SELECT statement and then immediately loop through the rows of the result set.

This statement can use either an implicit or explicit cursor (but not a cursor variable).

If you use the SELECT statement only in the cursor FOR LOOP statement, then specify the SELECT statement inside the cursor FOR LOOP statement. Implicit Cursor FOR LOOP Statement. For example:

BEGIN
  FOR item IN (
    select last_name, job_id
    FROM employees
    WHERE job_id LIKE '%CLERK%'
    AND employee_id > 120
  )
  LOOP
    DBMS_OUTPUT.PUT_LINE ('Name = ' || item.last_name || ', Job = ' || item.job_id);
  END LOOP;
END;
/

To be compatible with Oracle databases, LightDB supports the use of the WITH clause in implicit cursor FOR LOOP statements. For example:

CREATE TABLE employees (employee_id NUMBER PRIMARY KEY, first_name VARCHAR2(50));
INSERT INTO employees VALUES (1, 'John');
INSERT INTO employees VALUES (2, 'Jane');
INSERT INTO employees VALUES (3, 'Bob');

BEGIN
  FOR emp_rec IN (WITH employeetemp AS
                    ( SELECT employee_id AS eid, first_name AS fname FROM employees
                    ) SELECT eid,fname FROM employeetemp
                ) loop
    DBMS_OUTPUT.PUT_LINE('Employee ID: ' || emp_rec.eid || ', Name: ' || emp_rec.fname);
  END LOOP;
END;
/

Explicit Cursor FOR LOOP Statement. For example:

DECLARE
  CURSOR c1 IS
    SELECT last_name, job_id FROM employees
    WHERE job_id LIKE '%CLERK%' AND employee_id > 120
    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;
/

Passing Parameters to Explicit Cursor FOR LOOP Statement. For example:

DECLARE
  CURSOR c1 (job VARCHAR2, max_wage NUMBER) IS
    SELECT * FROM employees
    WHERE job_id = job
    AND salary > max_wage;
BEGIN
  FOR person IN c1('ST_CLERK', 3000)
  LOOP
     -- process data record
    DBMS_OUTPUT.PUT_LINE ('Name = ' || person.last_name || ', salary = ' || person.salary || ', Job id= ' || person.job_id);
  END LOOP;
END;
/

Cursor FOR LOOP References Virtual Columns. For example:

BEGIN
  FOR item IN (
    SELECT first_name || ' ' || last_name AS full_name,
           salary * 10                    AS dream_salary 
    FROM employees
    WHERE ROWNUM < 6
    ORDER BY dream_salary DESC, last_name ASC
  ) LOOP
    DBMS_OUTPUT.PUT_LINE (item.full_name || ' dreams of making ' || item.dream_salary);
  END LOOP;
END;
/

42.8.3.3. Processing Query Result Sets with Subqueries

If you process a query result set by looping through it and running another query for each row, then you can improve performance by removing the second query from inside the loop and making it a subquery of the first query.

While an ordinary subquery is evaluated for each table, a correlated subquery is evaluated for each row.

Subquery in FROM Clause of Parent Query. For example:

DECLARE
  CURSOR c1 IS
    SELECT t1.department_id, t1.department_name, t2.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 t2.staff > 5
    ORDER BY t2.staff;

BEGIN
   FOR dept IN c1
   LOOP
     DBMS_OUTPUT.PUT_LINE ('Department = ' || dept.department_name || ', staff = ' || dept.staff);
   END LOOP;
END;
/

Correlated Subquery. For example:

DECLARE
  CURSOR c1 IS
    SELECT department_id, last_name, salary
    FROM employees t
    WHERE salary >= ( SELECT AVG(salary)
                     FROM employees
                     WHERE t.department_id = department_id
                   )
    ORDER BY department_id, last_name;
BEGIN
  FOR person IN c1
  LOOP
    DBMS_OUTPUT.PUT_LINE ('Making above-average salary =  ' || person.last_name);
  END LOOP;
END;
/

42.8.4. Cursor Variables

A cursor variable is like an explicit cursor, except that:

  • It is not limited to one query. You can open a cursor variable for a query, process the result set, and then use the cursor variable for another query.

  • You can assign a value to it.

  • You can use it in an expression.

  • It can be a subprogram parameter. You can use cursor variables to pass query result sets between subprograms.

  • It can be a host variable. You can use cursor variables to pass query result sets between pl/sql stored subprograms and their clients.

  • It cannot accept parameters. You cannot pass parameters to a cursor variable, but you can pass whole queries to it. The queries can include variables.

42.8.4.1. Creating Cursor Variables

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.

The basic syntax of a REF CURSOR type definition is:

TYPE type_name IS REF CURSOR RETURN return_type%TYPE

Cursor Variable Declarations. For example:

DECLARE
  TYPE empcurtyp IS REF CURSOR RETURN employees%ROWTYPE;
  TYPE genericcurtyp IS REF CURSOR; 

  cursor1  empcurtyp;
  cursor2  genericcurtyp;
  my_cursor SYS_REFCURSOR;

  TYPE deptcurtyp IS REF CURSOR RETURN departments%ROWTYPE;
  dept_cv deptcurtyp;  -- strong cursor variable
BEGIN
  NULL;
END;
/

Cursor Variable with User-Defined Return Type. For example:

DECLARE
  TYPE EmpRecTyp IS RECORD (
    employee_id NUMBER,
    last_name VARCHAR2(25),
    salary   NUMBER(8,2));

  TYPE EmpCurTyp IS REF CURSOR RETURN EmpRecTyp;
  emp_cv EmpCurTyp;
BEGIN
  NULL;
END;
/

A variable of cursor type created using CREATE DOMAIN. For example:

CREATE DOMAIN mycursor AS refcursor;
SELECT dbms_output.serveroutput(true);
DECLARE
  employee_cursor mycursor;

  v_employee_name VARCHAR2(50);
BEGIN
  OPEN employee_cursor for SELECT first_name FROM employees_curs WHERE employee_id = 1500;

  LOOP
    FETCH employee_cursor INTO v_employee_name;
    EXIT WHEN employee_cursor%NOTFOUND;

    DBMS_OUTPUT.PUT_LINE('Employee Name: ' || v_employee_name);
  END LOOP;

  CLOSE employee_cursor;
END;
/
 

Use sys_refcursor as a parameter variable. For example:

CREATE TABLE customer_address
(
    ca_address_sk             integer               not null,
    ca_address_id             char(16)              not null,
    ca_street_number          char(10)                      ,
    ca_street_name            varchar(60)                   ,
    ca_street_type            char(15)                      ,
    ca_suite_number           char(10)                      ,
    ca_city                   varchar(60)                   ,
    ca_county                 varchar(30)                   ,
    ca_state                  char(2)                       ,
    ca_zip                    char(10)                      ,
    ca_country                varchar(20)                   ,
    ca_gmt_offset             decimal(5,2)                  ,
    ca_location_type          char(20)                      ,
    PRIMARY KEY (ca_address_sk)
);

DECLARE
    i_id                   integer := 1;
    address_id             char(16);
    street_number          char(10);
    street_name            varchar(60) ;
    street_type            char(15);
    suite_number           char(10);
    city                   varchar(60);
    county                 varchar(30);
    state                  char(2);
    zip                    char(10);
    country                varchar(20);
    gmt_offset             decimal(5,2);
    location_type          char(20);
BEGIN
    -- insert
    address_id     := 'address_id';
    street_number  := '3588' ;
    street_name    := 'street_name';
    street_type    := 'street_type';
    suite_number   := '1';
    city           := 'Hang Zhou';
    county         := 'Bin Jiang';
    state          := 'CN';
    zip            := '000000';
    country        := 'China';
    gmt_offset     := 1.02;
    location_type  := 'location_type';

    INSERT INTO customer_address(ca_address_sk, ca_address_id, ca_street_number, ca_street_name, ca_street_type, ca_suite_number, ca_city, ca_county, ca_state, ca_zip, ca_country, ca_gmt_offset, ca_location_type)
    VALUES (i_id, address_id, street_number, street_name, street_type, suite_number, city, county, state, zip, country, gmt_offset, location_type);
END;
/

CREATE OR REPLACE PROCEDURE getCustomerAddress(cur OUT sys_refcursor) IS
BEGIN
    OPEN cur FOR SELECT * FROM customer_address;
END;
/

DECLARE
    ad   sys_refcursor;
    res  customer_address%rowtype;
BEGIN
    getCustomerAddress(ad);

    LOOP
    FETCH ad INTO res;
    EXIT WHEN ad%NOTFOUND;
        dbms_output.put_line('ca_address_sk:' || res.ca_address_sk);
        dbms_output.put_line('ca_address_id:' || res.ca_address_id);
        dbms_output.put_line('ca_street_number:' || res.ca_street_number);
        dbms_output.put_line('ca_street_name:' || res.ca_street_name);
        dbms_output.put_line('ca_street_type:' || res.ca_street_type);
        dbms_output.put_line('ca_suite_number:' || res.ca_suite_number);
        dbms_output.put_line('ca_city:' || res.ca_city);
        dbms_output.put_line('ca_county:' || res.ca_county);
        dbms_output.put_line('ca_state:' || res.ca_state);
        dbms_output.put_line('ca_zip:' || res.ca_zip);
        dbms_output.put_line('ca_country:' || res.ca_country);
        dbms_output.put_line('ca_gmt_offset:' || res.ca_gmt_offset);
        dbms_output.put_line('ca_location_type:' || res.ca_location_type);
    END LOOP;
END;
/
       

42.8.4.2. Opening and Closing Cursor Variables

After declaring a cursor variable, you can open it with the OPEN FOR statement, which does the following:

  • Associates the cursor variable with a query (typically, the query returns multiple rows). The query can include placeholders for bind variables, whose values you specify in the USING clause of the OPEN FOR statement.

  • Allocates database resources to process the query.

  • Processes the query; that is:

    • Identifies the result set. If the query references variables, their values affect the result set.

    • If the query has a FOR UPDATE clause, locks the rows of the result set.

  • Positions the cursor before the first row of the result set.

In the for loop, the result of the select query is given to a record type, This operation is also known as an implicit cursor operation. A field in the record type is used to receive a query statement in the query results (update, delete, insert will all report errors in this syntax), This field is used as an object for commands(open for) to dynamically open the cursor.

LightDB does not support the following two points:

  • The format between FOR and IN can be A or A.B.

    For example, the format between FOR and IN can be A:

    select dbms_output.serveroutput('t');
    
    create table employees(salary int,
        job_id varchar2(100), employee_id int,
        last_name varchar(100), first_name varchar(100),
        department_id int, hire_date date);
    
    create table V2_text (
        object_name VARCHAR2(40),
        object_id VARCHAR2(8),
        sql_text VARCHAR2(1000)
    );
    
    create table orders (id int,org_id int,user_id int
        ,order_id int,customer_id int, order_date date, price int
        ,primary key (org_id, id),status varchar(20));
    
    insert into employees values (12308, 'SA_REP', 100, 'huaxiang', 'zhao', 10, TO_DATE('31-DEC-2005', 'DD-MON-YYYY'));
    insert into employees values (12301, 'AD_FG', 12301, 'huahai', 'zhao', 10, TO_DATE('31-DEC-2004', 'DD-MON-YYYY'));
    insert into employees values (12302, 'SA_REP', 12302, 'huaduo', 'zhao', 10, TO_DATE('31-DEC-2004', 'DD-MON-YYYY'));
    
    insert into orders values (12301, 100, 2346, 456043, 23490, TO_DATE('31-DEC-2005', 'DD-MON-YYYY'), 2000, 'Completed');
    
    -- select
    insert into V2_text (object_name, object_id, sql_text)
    values ('EmployeeTable', 'OBJ0001', 'SELECT job_id FROM employees WHERE department_id = 10');
    
    DECLARE
        type t_dynamic_cursor is ref cursor;
        dynamic_cur t_dynamic_cursor;
        type t_inter_code_table is table of varchar2(128);
        rec_all_code t_inter_code_table;
    BEGIN
       FOR rec IN (
          SELECT object_name,object_id,sql_text FROM V2_text
       ) LOOP
          OPEN dynamic_cur FOR rec.sql_text;
          -- Perform cursor operations
           fetch dynamic_cur bulk collect into rec_all_code;
            for i in 1..rec_all_code.count loop
               DBMS_OUTPUT.PUT_LINE('OBJECTNAME: ' ||rec_all_code(i));
            end loop;
          close dynamic_cur;
       END LOOP;
    END;
    /
    

    For example, the format between FOR and IN can be A.B:

    declare
        type t_dynamic_cursor is ref cursor;
        dynamic_cur t_dynamic_cursor;
        type t_inter_code_table is table of varchar2(128);
        rec_all_code t_inter_code_table;
        type emp_rec_type is record
        (ename varchar2(25),
         sql_text varchar2(10),
         sal number(7,2)
        );
    
        type emp_record_type is record
        (ename varchar2(25),
         job varchar2(10),
         sal number(7,2),
         rec emp_rec_type
        );
        sys emp_record_type;
    BEGIN
       FOR sys.rec IN (
          SELECT object_name,object_id,sql_text FROM V2_text
       ) LOOP
          NULL;
       END LOOP;
    END;
    /
    

  • The format of dynamic statements after FOR does not support multiple complex types to access variables in nests, and only supports a single complex type.

    For example, a single complex type can be used to access a variable:

    DECLARE
        type t_dynamic_cursor is ref cursor;
        dynamic_cur t_dynamic_cursor;
        type t_inter_code_table is table of varchar2(128);
        rec_all_code t_inter_code_table;
    BEGIN
       FOR rec IN (
          SELECT object_name,object_id,sql_text FROM V2_text
       ) LOOP
          OPEN dynamic_cur FOR rec.sql_text;
          -- Perform cursor operations
           fetch dynamic_cur bulk collect into rec_all_code;
            for i in 1..rec_all_code.count loop
               DBMS_OUTPUT.PUT_LINE('OBJECTNAME: ' ||rec_all_code(i));
            end loop;
          close dynamic_cur;
       END LOOP;
    END;
    /
    

    For example, multiple complex types are not supported for nested access to variables:

    DECLARE
        type t_dynamic_cursor is ref cursor;
        dynamic_cur t_dynamic_cursor;
        type t_inter_code_table is table of varchar2(128);
        rec_all_code t_inter_code_table;
        type emp_rec_type is record
        (ename varchar2(25),
         sql_text varchar2(100),
         sal number(7,2)
        );
    
        type emp_record_type is record
        (ename varchar2(25),
         job varchar2(10),
         sal number(7,2),
         rec emp_rec_type
        );
    
        type empl_record_type is record
        (ename varchar2(25),
         job varchar2(10),
         sal number(7,2),
         rec_emp emp_record_type
        );
        sys empl_record_type;
    BEGIN
       FOR sys.rec_emp.rec IN (
          SELECT object_name,object_id,sql_text FROM V2_text
       ) LOOP
        NULL;
       END LOOP;
    END;
    /
    

42.8.4.3. Fetching Data with Cursor Variables

After opening a cursor variable, you can fetch the rows of the query result set with the FETCH statement. The return type of the cursor variable must be compatible with the into_clause of the FETCH statement.

The first OPEN FOR statement includes the query itself. The second OPEN FORi statement references a variable whose value is a query. For example:

DECLARE
  cv SYS_REFCURSOR;  -- cursor variable
 
  v_lastname  employees.last_name%TYPE;  -- variable for last_name
  v_jobid     employees.job_id%TYPE;     -- variable for job_id
 
  query_2 VARCHAR2(200) :=
    'SELECT * FROM employees
    WHERE REGEXP_LIKE (job_id, ''[ACADFIMKSA]_M[ANGR]'')
    ORDER BY job_id';
 
  v_employees employees%ROWTYPE;  -- record variable row of table
 
BEGIN
  OPEN cv FOR
    SELECT last_name, job_id FROM employees
    WHERE REGEXP_LIKE (job_id, 'S[HT]_CLERK')
    ORDER BY last_name;
 
  LOOP  -- Fetches 2 columns into variables
    FETCH cv INTO v_lastname, v_jobid;
    EXIT WHEN cv%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE (RPAD(v_lastname, 25, ' ') || v_jobid);
  END LOOP;
 
  DBMS_OUTPUT.PUT_LINE ('-------------------------------------');
 
  CLOSE CV;
 
  OPEN cv FOR query_2;
 
  LOOP  -- Fetches entire row into the v_employees record
    FETCH cv INTO v_employees;
    EXIT WHEN cv%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE (RPAD(v_employees.last_name, 25, ' ') || v_employees.job_id);
  END LOOP;
 
  CLOSE cv;
END;
/

42.8.4.4. Assigning Values to Cursor Variables

You can assign to a pl/sql cursor variable the value of another pl/sql cursor variable or host cursor variable.

target_cursor_variable := source_cursor_variable;

If source_cursor_variable is open, then after the assignment, target_cursor_variable is also open. The two cursor variables point to the same SQL work area.

If source_cursor_variable is not open, opening target_cursor_variable after the assignment does not open source_cursor_variable.

Fetching from Cursor Variable into Collections. For example:

DECLARE
  TYPE empcurtyp IS REF CURSOR;
  emp_cv  empcurtyp;
  emp_cf empcurtyp;
  item employees%rowtype;
BEGIN
  OPEN emp_cv FOR SELECT * FROM employees WHERE job_id = 'SA_REP' ORDER BY salary DESC;
  OPEN emp_cf FOR SELECT * FROM employees;
  emp_cf := emp_cv;
  LOOP
  	FETCH emp_cf INTO item;
  	DBMS_OUTPUT.PUT_LINE ('Name = ' || item.last_name || ', salary = ' || item.salary);
    EXIT WHEN emp_cf%notfound;
  END LOOP;
  CLOSE emp_cf;
END;
/

42.8.4.5. Variables in Cursor Variable Queries

The query associated with a cursor variable can reference any variable in its scope.

When you open a cursor variable with the OPEN FOR statement, pl/sql evaluates any variables in the query and uses those values when identifying the result set. Changing the values of the variables later does not change the result set.

Fetching from Cursor Variable into Collections. For example:

DECLARE
  sal           employees.salary%TYPE;
  sal_multiple  employees.salary%TYPE;
  factor        INTEGER := 2;
 
  cv SYS_REFCURSOR;
 
BEGIN
  OPEN cv FOR
    SELECT salary, salary*factor
    FROM employees
    WHERE job_id LIKE 'AD_%';   -- PL/SQL evaluates factor
 
  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);
    factor := factor + 1;  -- Does not affect sal_multiple
  END LOOP;
 
  CLOSE cv;
END;
/

Fetching from Cursor Variable into Collections. For example:

DECLARE
  sal           employees.salary%TYPE;
  sal_multiple  employees.salary%TYPE;
  factor        INTEGER := 2;
 
  cv SYS_REFCURSOR;
 
BEGIN
  DBMS_OUTPUT.PUT_LINE ('factor = ' || factor);
 
  OPEN cv FOR
    SELECT salary, salary*factor
    FROM employees
    WHERE job_id LIKE 'AD_%';   -- PL/SQL evaluates factor
 
  LOOP
    FETCH cv INTO sal, sal_multiple;
    EXIT WHEN cv%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE ('sal          = ' || sal);
    DBMS_OUTPUT.PUT_LINE ('sal_multiple = ' || sal_multiple);
  END LOOP;
 
  factor := factor + 1;
 
  DBMS_OUTPUT.PUT_LINE ('factor = ' || factor);

  CLOSE cv;
  
  OPEN cv FOR
    SELECT salary, salary*factor
    FROM employees
    WHERE job_id LIKE 'AD_%';   -- PL/SQL evaluates factor
 
  LOOP
    FETCH cv INTO sal, sal_multiple;
    EXIT WHEN cv%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE ('sal          = ' || sal);
    DBMS_OUTPUT.PUT_LINE ('sal_multiple = ' || sal_multiple);
  END LOOP;
 
  CLOSE cv;
END;
/

42.8.4.6. Cursor Variable Attributes

A cursor variable has the same attributes as an explicit cursor (see Explicit Cursor Attributes.). The syntax for the value of a cursor variable attribute is cursor_variable_name immediately followed by attribute (for example, cv%ISOPEN).If a cursor variable is not open, referencing any attribute except %ISOPEN raises the predefined exception INVALID_CURSOR.