CPSC-110 Spring 2009
Lab 10
Tuesday, April 15, 2009
Professor Heidi Ellis

Be sure that the professor or TA checks off your work before leaving!!!

Objectives:

In this lab we will write and execute stored procedures in MySQL that use looping statements to process data that we have retrieved from the database. We will once again be working with our police database.

Cursors

There are many times in stored procedures when we want to use a SELECT statement to retrieve data and then process that data within the stored procedure. For instance, totaling the fines that are below some given value. (Yes, I understand that this could be done using a simple SQL query, but we will look at how to do this ourselves within a stored procedure as a simple illustration of my point.)

The mechanism used in stored procedures to handle a SELECT statement that returns more than one row is a cursor. A cursor is an object that allows you to access the result set returned by a SELECT statement. A cursor lets the SQL select statement return more than one row and supports looping through that result set. The cursor is used to iterate through the rows in a result set and take action for each row individually. Cursors must be declared in the same location where other variables are declared and the definition of a query contains the SQL query that provides the result set that the cursor operates over. A cursor must be "opened" in order to operate over the result set and the cursor must be closed after the result set is processed. For example, the query
	SELECT fine 
	FROM incidents
results in the result set shown below (when applied to my incidents table):



A cursor defined on this set would start by pointing to the first row, then the next, then the next and so on. The cursor would be defined by the statement:
 DECLARE fineCursor CURSOR FOR
          select fine from incidents;
This cursor declaration must follow all variable declarations and must contain a select statement. There are three operations that may be performed on a cursor: Below is an example of a stored procedure which retrieves the first fine from the set of fines using a cursor. Note that the first row is fetched into the variable tempFine.

-- This procedure shows the use of a cursor to retrieve
-- the first row of a result set.
CREATE PROCEDURE `police`.`selectFirstFine` ()
BEGIN

  DECLARE tempFine FLOAT;         -- Variable to hold a single fine

  -- Declare a cursor to iterate through a table. Only retrieves a single column.
  DECLARE fineCursor CURSOR FOR
          select fine from incidents;
  OPEN fineCursor;                -- Open the cursor
  FETCH fineCursor into tempFine; -- Retrieve first row in table
  CLOSE fineCursor;               -- Close the cursor

  SELECT tempFine;                -- Display the first fine.
  END $$
Pair Programming:
  1. Locate your partner and introduce yourself:
  2. Abby AldermanAlyssa Cockrell 
    Cory DiBenedettoPreston Maddock 
    Briggs ElwellAndrea Titone 
    Whitney MerrillNikki Lustig 
    Ryan PiacentiniElise Galbo 
    Matt VibertBrooke Haynes 
    Robert PerrymanBrenna Fox 
    Ben McDanielShanice SmithRichard Curtin
  3. Select one person's account in which to work.
  4. Select one person to start as "driver". Change driver for each new procedure created.
  5. Using KWrite (or another text editor), create a new file called lab10.txt. Put both team members' names in the document.
  6. Copy the procedure code for each procedure created into the lab10.txt file.
  7. Email the completed file to the partner in whose account you were not working so that they have a copy.
  8. Print the lab10.txt file and hand it in at the end of lab.

Try it:

Now lets look at how we can combine cursors and loops to iterate through a result set table.

Error Handlers

In order to loop through the result set, we need a way to know when we've reached the end of the result set. We can't use a counter such as i as we don't know the number of rows in the result set and therefore don't know how big to set i. SQL provides a way around this using an error handler. Handlers are a MySQL construct that sets the value of a variable to be 1 when the end of a result set is encountered. Declaring a handler is a two-step process:
  1. Declared an INT variable to hold the value indicating the end of the result set.
  2. Declare the variable declared in the previous step as being a handler.
The handler defined below will equal one (the system automatically sets the value to one) when there are no more rows to be processed in a result set operated on by a cursor:
DECLARE noMoreFines INT;
-- Declaration of other variables and cursor in this space
DECLARE CONTINUE HANDLER FOR NOT FOUND SET noMoreFines=1;
Now we can write our program so that it loops and checks the value of the noMoreFines variable as the exit condition of the loop (noMoreFines = 1):
  OPEN fineCursor;        -- Open the cursor to point to first row in table

  fine_loop:LOOP                       -- Open the loop
    FETCH fineCursor into tempFine;    -- Retrieve the next fine from the table
    IF noMoreFines=1                   -- Check if there are any more fines.
       THEN LEAVE fine_loop;           -- If so, leave the loop
    END IF;
  END LOOP;

  CLOSE fineCursor;       -- Close the cursor
Note that this loop doesn't do anything other than step its way through the result set.

Try it:

A Full Example

Below is an example of a stored procedure that totals the fines for all fines below a provided value. Note the use of the parameter f which is the "provided value". Also note the use of the if statement to check if the current fine is greater than the provided value and if so, it adds the value to the total of the fines.
-- This procedure returns the total of the fines that are
-- below some provided figure.
CREATE PROCEDURE `totalFinesUnder`(f FLOAT)
BEGIN
  DECLARE totalFine FLOAT;      -- Total of the fines
  DECLARE tempFine FLOAT;       -- Variable to hold a single fine
  DECLARE noMoreFines INT;      -- Variable to check for no more data in the table

  -- Declare a cursor to iterate through a table. Only retrieves a single column.
  DECLARE fineCursor CURSOR FOR
          select fine from incidents;

  -- Declare a handler to detect when the last row in the table is reached
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET noMoreFines=1;


  SET totalFine=0;        -- Initialize the total of the fines to zero
  OPEN fineCursor;        -- Open the cursor to point to first row in table

  fine_loop:LOOP                       -- Open the loop
    FETCH fineCursor into tempFine;    -- Retrieve the next fine from the table
    IF noMoreFines=1                   -- Check if there are any more fines.
       THEN LEAVE fine_loop;           -- If so, leave the loop
    END IF;
    IF tempFine < f                    -- Otherwise check to see if the fine is
                                       -- greater than the parameter. If so, add the
                                       -- fine to the total
       THEN SET totalFine = totalFine + tempFine;
    END IF;
  END LOOP;

  CLOSE fineCursor;       -- Close the cursor
  SELECT totalFine;       -- Display the total of the fines

  END $$

Try it:

Development Hints for Stored Procedures

Try it on your own

Now write a stored procedure that will use a cursor and a handler to calculate the average age of the people in the incidents database. Copy the procedure into a separate file and hand in at the end of class. The steps of what you need to do are as follows:
  1. Declare variables to hold:
  2. Declare a cursor to iterate through the incidents table. Note that you only need to retrieve the age column.
  3. Declare a handler to detect when the last row in the table is reached.
  4. Initialize the total age and number of people variables to zero.
  5. Open the cursor.
  6. Enter a loop to:
    1. Retrieve the next age from the table
    2. Check to see if there are any more ages. If not, leave the loop.
    3. If there are more ages, add the current age to the age total.
    4. Increment the counter for the number of people by one.
  7. Close the age cursor.
  8. Set the average age to be the total of the ages divided by the number of people. (The division operator is the forward slash.)
  9. Display the average age.
You're done!! Be sure to print and hand in your lab10.txt file before you leave!!