Cursor Types in PLSQL -Normal cursor and Reference cursor

Pranav Bakare - Sep 29 - - Dev Community

Let’s extend the previous example by demonstrating the difference between a normal cursor and a reference cursor, along with a scenario where the reference cursor solves a problem that the normal cursor cannot.

Basics of Cursor;

Key Differences between Normal Cursor and Reference Cursor:

Normal Cursor:

  • A normal cursor is static, meaning it is bound to a fixed SQL query at compile-time.
  • You cannot pass parameters to change the query dynamically.
  • The structure and query are defined at the time of declaration and cannot be changed.

Reference Cursor (REF CURSOR):

  • A reference cursor is dynamic, meaning it allows you to define the query at runtime.
  • You can pass a reference cursor as a parameter to a procedure or function, allowing more flexible query execution.
  • It is a pointer to a query result set, which can be opened for any SELECT statement dynamically.

Problem with Normal Cursor:

With a normal cursor, the query is fixed. Suppose we want to write a PL/SQL procedure where the query could vary based on different conditions, for example, fetching data from different tables or using dynamic conditions. A normal cursor cannot handle such cases easily.

A reference cursor solves this problem by allowing dynamic query assignment at runtime.


Scenario:

We want to create a PL/SQL block that fetches employee details based on a dynamic condition. If the condition is to fetch employees with EMPLOYEE_ID < 103, we use one query. If the condition is to fetch all employees, we use another query.

  1. Using a Normal Cursor (Fixed Query):
    This approach only works when the query is fixed and cannot be changed dynamically.

  2. Using a Reference Cursor (Dynamic Query):
    This approach allows us to dynamically pass queries based on the condition.


Step 1: Create the EMPLOYEES Table and Insert Data (As before)

Table Creation:

CREATE TABLE EMPLOYEES (
    EMPLOYEE_ID   NUMBER(5),
    FIRST_NAME    VARCHAR2(50),
    LAST_NAME     VARCHAR2(50)
);
Enter fullscreen mode Exit fullscreen mode
Insert Data:

INSERT INTO EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME)
VALUES (101, 'John', 'Doe');

INSERT INTO EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME)
VALUES (102, 'Jane', 'Smith');

INSERT INTO EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME)
VALUES (103, 'Mark', 'Taylor');

INSERT INTO EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME)
VALUES (104, 'Lucy', 'Williams');

Enter fullscreen mode Exit fullscreen mode

Step 2: Demonstrate Using a Normal Cursor (Fixed Query)

In this example, the query is fixed and cannot change dynamically.

PL/SQL Block with a Normal Cursor:

DECLARE
   -- Step 1: Declare a normal cursor (fixed query)
   CURSOR emp_cursor IS
      SELECT EMPLOYEE_ID, FIRST_NAME, LAST_NAME
      FROM EMPLOYEES
      WHERE EMPLOYEE_ID < 103; -- Fixed condition in query

   -- Step 2: Record variable to hold the result of the fetch
   emp_record EMPLOYEES%ROWTYPE;
BEGIN
   -- Step 3: Open the cursor
   OPEN emp_cursor;

   -- Step 4: Fetch data and process
   LOOP
      FETCH emp_cursor INTO emp_record;
      EXIT WHEN emp_cursor%NOTFOUND;

      DBMS_OUTPUT.PUT_LINE
('Employee ID: ' || emp_record.EMPLOYEE_ID ||
                           ', First Name: ' || emp_record.FIRST_NAME ||
                           ', Last Name: ' || emp_record.LAST_NAME);
   END LOOP;

   -- Step 5: Close the cursor
   CLOSE emp_cursor;
END;
Enter fullscreen mode Exit fullscreen mode
Output (Normal Cursor):

Employee ID: 101, First Name: John, Last Name: Doe
Employee ID: 102, First Name: Jane, Last Name: Smith

Enter fullscreen mode Exit fullscreen mode

Limitation:

The query is fixed, so we can only fetch employees with EMPLOYEE_ID < 103. To change the query, we’d need to modify the cursor declaration itself, which isn’t practical in dynamic scenarios.


Step 3: Demonstrate Using a Reference Cursor (Dynamic Query)

Here, we solve the limitation by using a reference cursor that allows us to pass dynamic queries.

PL/SQL Block with a Reference Cursor:

DECLARE
   -- Step 1: Declare a reference cursor (dynamic cursor)
   TYPE ref_cursor_type IS REF CURSOR;
   emp_ref_cursor ref_cursor_type;

   -- Step 2: Record variable to hold the result of the fetch
   emp_record EMPLOYEES%ROWTYPE;

   -- Step 3: Declare a variable to determine the condition dynamically
   emp_condition NUMBER := 103;  
-- This can be changed to different values dynamically
BEGIN
   -- Step 4: Open the reference cursor with dynamic query based on condition
   IF emp_condition < 103 THEN
      OPEN emp_ref_cursor FOR
      SELECT EMPLOYEE_ID, FIRST_NAME, LAST_NAME
      FROM EMPLOYEES
      WHERE EMPLOYEE_ID < emp_condition;
   ELSE
      OPEN emp_ref_cursor FOR
      SELECT EMPLOYEE_ID, FIRST_NAME, LAST_NAME
      FROM EMPLOYEES;
   END IF;

   -- Step 5: Fetch data and process dynamically
   LOOP
      FETCH emp_ref_cursor INTO emp_record;
      EXIT WHEN emp_ref_cursor%NOTFOUND;

      DBMS_OUTPUT.PUT_LINE
('Employee ID: ' || emp_record.EMPLOYEE_ID ||
                           ', First Name: ' || emp_record.FIRST_NAME ||
                           ', Last Name: ' || emp_record.LAST_NAME);
   END LOOP;

   -- Step 6: Close the reference cursor
   CLOSE emp_ref_cursor;
END;
Enter fullscreen mode Exit fullscreen mode
Output (Reference Cursor with Dynamic Condition):

If emp_condition is set to 103:

Employee ID: 101, First Name: John, Last Name: Doe
Employee ID: 102, First Name: Jane, Last Name: Smith
Enter fullscreen mode Exit fullscreen mode
If emp_condition is set to a higher value (e.g., 104), 
then all employees are fetched:

Employee ID: 101, First Name: John, Last Name: Doe
Employee ID: 102, First Name: Jane, Last Name: Smith
Employee ID: 103, First Name: Mark, Last Name: Taylor
Employee ID: 104, First Name: Lucy, Last Name: Williams

Enter fullscreen mode Exit fullscreen mode

Explanation:

Normal Cursor: The query is fixed at compile-time and cannot be changed. This means you cannot use it dynamically to fetch data based on runtime conditions.

Reference Cursor: The query is assigned dynamically at runtime. You can use different queries based on conditions, which makes it much more flexible.


When to Use a Reference Cursor:

  • When you need to execute different queries dynamically at runtime based on certain conditions.
  • When you need to pass a cursor as a parameter to a procedure or function for flexible query execution.
  • When you want to write more generic code that can handle different queries without changing the cursor declaration.

Conclusion:

A reference cursor offers more flexibility than a normal cursor by allowing dynamic query execution, solving the problem of static queries that normal cursors present.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .