Chapter 10. Cursors

In one form or another, every SQL statement in your PL/SQL program is associated with either an implicit or an explicit cursor. You can think of a cursor as a pointer into the result set of a SQL statement; its job is to allow you to access and manipulate the data inside the set. This chapter tests your ability to define cursors for use in you PL/SQL programs, to process the data within a cursor, and to create maintainable and efficient cursor definitions.

Beginner

10-1.

What does it mean to be an implicit cursor in a PL/SQL program? Which of the following SQL statements are implicit cursors?

  1. BEGIN
       UPDATE preferences
          SET ice_cream_flavor = 'CHOCOLATE'
        WHERE name = 'STEVEN';
  2. DECLARE
       CURSOR around_the_house_jobs_cur
       IS
           SELECT job, frequencey
             FROM adolescent_workload
            WHERE name = 'ELI';
  3. DECLARE
       next_key employee.employee_id%TYPE;
    BEGIN
       SELECT employee_seq.NEXTVAL
         INTO next_key
         FROM dual;
  4. SQL> ALTER TABLE ceo_compensation MODIFY options NUMBER;

  5. FOR indx IN 1 .. 12
    LOOP
       DELETE FROM ceo_compensation
        WHERE layoffs > 1000 * indx
          AND options > 100000;
    END LOOP;

10-2.

What does it mean to be an explicit cursor in a PL/SQL program?

10-3.

What predefined exceptions can be raised when a block executes an implicit cursor?

10-4.

How many times does the Oracle SQL engine attempt to fetch a row from the employee table in this block (assume that a row exists for employee_id = 150667)?

DECLARE
   my_reward NUMBER;
 BEGIN
   SELECT salary
     INTO my_reward
     FROM employee
    WHERE employee_id = 150667;
END;

10-5.

What are the possible and predictable exceptions that could occur when this block is run? How can you change this code to trap the errors (or avoid them altogether)?

DECLARE
  v_name VARCHAR2(30);
BEGIN
  SELECT last_name
    INTO v_name
    FROM employee;
END;

Intermediate

10-6.

Which of the following strings are valid cursor attributes (which means they are “attached” as a suffix to the name of the cursor)?

  1. %ROWNUM

  2. %FOUND

  3. %TOO_MANY_ROWS

  4. %NO_DATA_FOUND

  5. %NOTFOUND

  6. %ROWCOUNT

  7. %ISCLOSED

  8. %ISOPENED

  9. %ISOPEN

10-7.

Which of the following uses of cursor attributes are valid? If they are not valid, what is the problem? Assume that the following cursor is declared in the same block when referenced in the problems:

CURSOR possibly_in_danger_cur IS
   SELECT status
     FROM genetically_modified_foods
    WHERE product = 'CORN'
      AND animal = 'MONARCH BUTTERFLY';
  1. BEGIN
       IF possibly_in_danger_cur%FOUND
       THEN
          OPEN possibly_in_danger_cur;
  2. BEGIN
       UPDATE jobs_to_mexico
          SET total_count = 10000000
        WHERE nafta_status = 'IN FORCE';
       DBMS_OUTPUT.PUT_LINE (SQL%ROWCOUNT);
  3. BEGIN
       FOR big_loss IN possibly_in_danger_cur
       LOOP
          IF possibly_in_danger_cur%ROWCOUNT
          THEN
             ...
  4. BEGIN
       OPEN possibly_in_danger_cur;
       FETCH possibly_in_danger_cur INTO I_am_sad;
       IF I_am_sad%FOUND
       THEN
  5. FOR indx IN 1 .. 12
    LOOP
       DELETE FROM genetically_modified_foods
        WHERE product = 'CORN';
       DBMS_OUTPUT.PUT_LINE (
          'All gone:  ' || genetically_modified_foods%ROWCOUNT);
    END LOOP;

10-8.

What is wrong with the following code? Will you eventually run out of open cursors in the session if you execute this program, say, 100,000 times?

CREATE OR REPLACE FUNCTION totalsales (year_in IN INTEGER)
   RETURN NUMBER
IS
   CURSOR sales_cur IS SELECT SUM (amt) FROM ...;
   total NUMBER;
BEGIN
   OPEN sales_cur;
   FETCH sales_cur INTO total;
   RETURN total;
   CLOSE sales_cur;
END;

10-9.

Which employee will become more and more loyal to the company when this function is run for everyone at year’s end?

CREATE OR REPLACE FUNCTION increment_salary (
   employee_id employee.employee_id%TYPE)
   RETURN BOOLEAN
/*
|| Give the applicable employee a 10% Raise
*/
IS
   -- cursor to get the employees current salary
   CURSOR curs_get_curr_sal (
      employee_id employee.employee_id%TYPE)
   IS
      SELECT salary FROM employee
       WHERE employee_id = employee_id
         FOR UPDATE OF salary;
   v_curr_sal  employee.salary%TYPE;
   v_ret_val   BOOLEAN := FALSE;   -- return value

BEGIN
   /*
   || Query the employee's current salary and increment it by 10%
   */
   OPEN curs_get_curr_sal (employee_id);
   FETCH curs_get_curr_sal INTO v_curr_sal;
   IF curs_get_curr_sal%found
   THEN
      UPDATE employee SET salary = salary +  (salary / 10)
       WHERE CURRENT OF curs_get_curr_sal;
      v_ret_val := TRUE;
   END IF;
   CLOSE curs_get_curr_sal;

   -- Return TRUE if update was successful, FALSE if not
   RETURN  (v_ret_val);
END;

10-10.

What is the output of the following code snippet, assuming there is an employee whose ID is 7566?

DECLARE
   v_name employee.last_name%TYPE;

   PROCEDURE bpl (str IN VARCHAR2, bool IN BOOLEAN) IS
   BEGIN
      IF bool THEN DBMS_OUTPUT.PUT_LINE (str || '-TRUE'),
      ELSIF NOT bool THEN DBMS_OUTPUT.PUT_LINE (str || '-FALSE'),
      ELSE DBMS_OUTPUT.PUT_LINE (str || '-NULL'),
      END IF;
   END;

BEGIN
   SELECT last_name INTO v_name FROM employee
    WHERE employee_id = 7566;

   bpl ('%FOUND', SQL%FOUND);
   bpl ('%NOTFOUND', SQL%NOTFOUND);
   bpl ('%ISOPEN', SQL%ISOPEN);

   DBMS_OUTPUT.PUT_LINE ('%ROWCOUNT-' || SQL%ROWCOUNT);
END;
/

10-11.

What is required in this code snippet to validate that indeed no rows were returned?

DECLARE
  CURSOR curs_failure IS
     SELECT null
       FROM employee
      WHERE 1 = 2;
  v_dummy VARCHAR2(1);
BEGIN
  OPEN curs_failure;
  FETCH curs_failure INTO v_dummy;
  CLOSE curs_failure;
END;

10-12.

Write a function that uses a cursor to find the total of the three highest salaries in the employee table using the following cursor:

CURSOR curs_get_salary IS
  SELECT salary
    FROM employee
  ORDER BY salary desc;

10-13.

Given the following cursor declaration:

CURSOR curs_get_emps IS
  SELECT employee_id, last_name, salary
    FROM employee;

Which of the following approaches is preferable from the standpoint of maintainability and robustness?

  1.   v_employee_id  employee.employee_id%TYPE;
      v_name  employee.last_name%TYPE;
      v_salary employee.salary%TYPE;
    BEGIN
      OPEN curs_get_emps;
      FETCH curs_get_emps INTO v_employee_id, v_last_name, v_salary;
  2.   v_emprec curs_get_emps%ROWTYPE;
    BEGIN
      OPEN curs_get_emps;
      FETCH curs_get_emps INTO v_emprec;

10-14.

What do you see in your session when the following procedure is executed (the employee table has 32 rows; the person with the lowest salary is named MURRAY)?

CREATE OR REPLACE PROCEDURE show_lots
IS
   CURSOR by_sal_cur IS
      SELECT last_name
        FROM employee
       ORDER BY salary DESC;
   rec by_sal_cur%ROWTYPE;
BEGIN
   OPEN by_sal_cur;
   FOR rows IN 1 .. 10000
   LOOP
      FETCH by_sal_cur INTO rec;
   END LOOP;
   DBMS_OUTPUT.PUT_LINE (by_sal_cur%ROWCOUNT);
   DBMS_OUTPUT.PUT_LINE (rec.last_name);
   CLOSE by_sal_cur;
END;
/

10-15.

Which statement can be removed from the following block of code without changing the behavior or effect of the code?

DECLARE
   CURSOR brought_to_you_by_unions
   IS
      SELECT weekends, forty_hour_weeks, overtime_pay
        FROM business_practices;
   social_benefits brought_to_you_by_unions%ROWTYPE;
BEGIN
   OPEN brought_to_you_by_unions;
   FETCH brought_to_you_by_unions INTO social_benefits;
   CLOSE brought_to_you_by_unions;
END;
/

10-16.

Rewrite the following program to reduce code volume to an absolute minimum (without changing the names of the elements):


/* Filename on web page: unions.sql */
DECLARE
   CURSOR brought_to_you_by_unions
   IS
      SELECT weekends, forty_hour_weeks, overtime_pay
        FROM business_practices;
   social_benefits brought_to_you_by_unions%ROWTYPE;
BEGIN
   OPEN brought_to_you_by_unions;
   LOOP
      FETCH brought_to_you_by_unions INTO social_benefits;
      EXIT WHEN brought_to_you_by_unions%NOTFOUND;
      calculate_impact (social_benefits);
   END LOOP;
   CLOSE brought_to_you_by_unions;
END;

10-17.

Modify the script in the unions.sql file (see 10-16) so that (1) it uses a cursor FOR loop, and (2) after all rows have been processed, the block displays how many rows were fetched. Does the following implementation do the trick?


/*  Filename on web page: unions.sql */
DECLARE
   CURSOR brought_to_you_by_unions
   IS
      SELECT weekends, forty_hour_weeks, overtime_pay
        FROM business_practices;
BEGIN
   FOR social_benefits IN brought_to_you_by_unions
   LOOP
      calculate_impact (social_benefits);
   END LOOP;
   DBMS_OUTPUT.PUT_LINE (
      'Calculated impact of ' || brought_to_you_by_unions%ROWCOUNT
      || ' benefits brought about by unions.'),
END;

Expert

10-18.

What is the output from the following code snippet?

BEGIN
  UPDATE employee SET salary = salary;
  FOR v_rec IN ( SELECT employee_id FROM employee )
  LOOP
    IF SQL%ISOPEN THEN
      DBMS_OUTPUT.PUT_LINE('TRUE - ' || SQL%ROWCOUNT);
    ELSE
      DBMS_OUTPUT.PUT_LINE('FALSE - ' || SQL%ROWCOUNT);
    END IF;
  END LOOP;
END;
/

10-19.

What is the output from the following code snippet?

DECLARE
   CURSOR cur IS SELECT employee_id FROM employee;
BEGIN
  FOR rec IN cur
  LOOP
    IF cur%ROWCOUNT = 1
    THEN
       IF cur%ISOPEN THEN
         DBMS_OUTPUT.PUT_LINE('TRUE'),
       ELSE
         DBMS_OUTPUT.PUT_LINE('FALSE'),
       END IF;
    END IF;
  END LOOP;
END;

10-20.

How would you change the following block of code to make better use of the FOR UPDATE clause and also improve the program’s performance?

DECLARE
   CURSOR upd_all_cur
      IS SELECT * FROM employee FOR UPDATE;
BEGIN
   /*
   || Double everyone's commisson
   */
   FOR rec IN upd_all_cur
   LOOP
      IF rec.commission IS NOT NULL
      THEN
         UPDATE employee
            SET commission = commission * 2
          WHERE employee_id = rec.employee_id;
      END IF;
   END LOOP;
END;

10-21.

Which row in the employee table is locked when this block is executed?

DECLARE
   CURSOR upd_of_sal_cur
   IS
      SELECT * FROM employee FOR UPDATE OF salary;
BEGIN
   FOR rec IN upd_of_sal_cur
   LOOP
      IF rec.commission IS NOT NULL
      THEN
         UPDATE employee
            SET commission = commission * 2
          WHERE CURRENT OF upd_of_sal_cur;
      END IF;
   END LOOP;
END;

10-22.

Suppose you create the following package; it contains three cursors (door_number_one, door_number_two and door_number_three) that have the same query but are not identical in the way they are defined in the package:

CREATE OR REPLACE PACKAGE lets_make_a_deal
AS
   CURSOR door_number_one IS
      SELECT useless_information
        FROM consumer_products
       WHERE the_price = 'YOUR BEST GUESS';

   PROCEDURE open_door_number_two;

   PROCEDURE open_door_number_three;

END lets_make_a_deal;
/
CREATE OR REPLACE PACKAGE BODY lets_make_a_deal
AS
  CURSOR door_number_two IS
     SELECT useless_information
       FROM consumer_products
      WHERE the_price = 'YOUR BEST GUESS';

  PROCEDURE open_door_number_two
  IS
  BEGIN
     OPEN door_number_two;
  END;

  PROCEDURE open_door_number_three
  IS
     CURSOR door_number_three IS
        SELECT useless_information
          FROM consumer_products
         WHERE the_price = 'YOUR BEST GUESS';
  BEGIN
     OPEN door_number_three;
  END;
END lets_make_a_deal;
/

Suppose you then want to execute the following block, which defines yet a fourth door (cursor):

DECLARE
   CURSOR door_number_four IS
      SELECT useless_information
        FROM consumer_products
       WHERE the_price = 'YOUR BEST GUESS';
BEGIN
   OPEN lets_make_a_deal.door_number_one;
   lets_make_a_deal.open_door_number_two;
   lets_make_a_deal.open_door_number_three;
   OPEN door_number_four;
END;
/

When you run this block the first time, what happens? What happens if you run it a second time in the same session?

10-23.

What two structures can make up a valid cursor return clause in a package specification?

10-24.

Rewrite the following to maximize code reuse and improve performance:


/* Filename on web page: jobinfo.sql */
CREATE OR REPLACE PROCEDURE show_jobs
IS
   CURSOR around_the_house_jobs_cur
   IS
       SELECT job
         FROM adolescent_workload
        WHERE name = 'ELI';
BEGIN
   FOR rec IN around_the_house_jobs_cur
   LOOP
      DBMS_OUTPUT.PUT_LINE (rec.job);
   END LOOP;
END;
/
CREATE OR REPLACE FUNCTION job_for (name_in IN VARCHAR2) RETURN VARCHAR2
IS
   CURSOR around_the_house_jobs_cur
   IS
       SELECT job FROM adolescent_workload
        WHERE name = name_in;
    retval adolescent_workload.job%type;
BEGIN
   OPEN around_the_house_jobs_cur;
   FETCH around_the_house_jobs_cur INTO retval;
   RETURN retval;
END;
/

10-25.

(Oracle8i only) The following function retrieves all employees for a given department, returning them in the form of a nested table. Each row is fetched individually and placed in the nested table (see the bulksel.sql file for the statements that create the various types).


/* Filename on web page: bulksel.sql */
CREATE OR REPLACE FUNCTION get_a_mess_o_emps80
   (deptno_in IN dept.deptno%TYPE)
RETURN emplist_t
IS
   emplist emplist_t := emplist_t();
   oneEmp emp_t;

   CURSOR ecur IS
      SELECT emp_t(empno, ename, hiredate)
        FROM emp2
       WHERE deptno = deptno_in;
   counter PLS_INTEGER := 1;
BEGIN
   OPEN ecur;
   LOOP
      FETCH ecur INTO oneEmp;
      EXIT WHEN ecur%NOTFOUND;
      emplist.EXTEND;
      emplist(counter) := oneEmp;
      counter := counter + 1;
   END LOOP;
   CLOSE ecur;
   RETURN emplist;
END;
/

Rewrite this function using new capabilities in Oracle8i to improve the performance of this multirow select.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.250.223