In Example 2-7 we show a stored procedure that uses all the features of the stored program language we have covered so far in this tutorial.
Figure 2-15. Example of calling one stored procedure from another
Example 2-7. A more complex stored procedure
1 CREATE PROCEDURE putting_it_all_together(in_department_id INT) 2 MODIFIES SQL DATA
3 BEGIN
4 DECLARE l_employee_id INT;
5 DECLARE l_salary NUMERIC(8,2);
6 DECLARE l_department_id INT;
7 DECLARE l_new_salary NUMERIC(8,2);
8 DECLARE done INT DEFAULT 0;
9
10 DECLARE cur1 CURSOR FOR
11 SELECT employee_id, salary, department_id 12 FROM employees
13 WHERE department_id=in_department_id;
14 15
This is the most complex procedure we have written so far, so let’s go through it line by line:
16 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1;
17
18 CREATE TEMPORARY TABLE IF NOT EXISTS emp_raises
19 (employee_id INT, department_id INT, new_salary NUMERIC(8,2));
20
21 OPEN cur1;
22 emp_loop: LOOP 23
24 FETCH cur1 INTO l_employee_id, l_salary, l_department_id;
25
26 IF done=1 THEN /* No more rows*/
27 LEAVE emp_loop;
28 END IF;
29
30 CALL new_salary(l_employee_id,l_new_salary); /*get new salary*/
31
32 IF (l_new_salary<>l_salary) THEN /*Salary changed*/
33
34 UPDATE employees
35 SET salary=l_new_salary 36 WHERE employee_id=l_employee_id;
37 /* Keep track of changed salaries*/
38 INSERT INTO emp_raises (employee_id,department_id,new_salary) 39 VALUES (l_employee_id,l_department_id,l_new_salary);
40 END IF;
41
42 END LOOP emp_loop;
43 CLOSE cur1;
44 /* Print out the changed salaries*/
45 SELECT employee_id,department_id,new_salary from emp_raises 46 ORDER BY employee_id;
47 END;
Line(s) Explanation
1 Create the procedure. It takes a single parameter—in_department_id. Since we did not specify theOUT or INOUTmode, the parameter is for input only (that is, the calling program cannot read any changes to the param- eter made within the procedure).
4-8 Declare local variables for use within the procedure. The final parameter,done, is given an initial value of 0.
10-13 Create a cursor to retrieve rows from theemployees table. Only employees from the department passed in as a parameter to the procedure will be retrieved.
16 Create an error handler to deal with “not found” conditions, so that the program will not terminate with an error after the last row is fetched from the cursor. The handler specifies theCONTINUE clause, so the program execu- tion will continue after the “not found” error is raised. The hander also specifies that the variabledonewill be set to 1 when this occurs.
Example 2-7. A more complex stored procedure (continued)
When this stored procedure is executed from the MySQL command line with the parameter ofdepartment_idset to 18, a list of updated salaries is printed as shown in Example 2-8.
Stored Functions
Stored functions are similar to stored procedures: they are named program units that contain one or more MySQL statements. They differ from procedures in the follow- ing ways:
18 Create a temporary table to hold a list of rows affected by this procedure. This table, as well as any other tempo- rary tables created in this session, will be dropped automatically when the session terminates.
21 Open our cursor to prepare it to return rows.
22 Create the loop that will execute once for each row returned by the stored procedure. The loop terminates on line 42.
24 Fetch a new row from the cursor into the local variables that were declared earlier in the procedure.
26-28 Declare anIF condition that will execute theLEAVE statement if the variabledone is set to 1 (accomplished through the “not found” handler, which means that all rows were fetched).
30 Call thenew_salary procedure to calculate the employee’s new salary. It takes as its arguments the employee_id and anOUT variable to accept the new salary (l_new_salary).
32 Compare the new salary calculated by the procedure called on line 30 with the existing salary returned by the cur- sor defined on line 10. If they are different, execute the block of code between lines 32 and 40.
34-36 Update the employee salary to the new salary as returned by thenew_salary procedure.
38 and 39 Insert a row into our temporary table (defined on line 21) to record the salary adjustment.
43 After all of the rows have been processed, close the cursor.
45 Issue an unboundedSELECT (e.g., one without aWHERE clause) against the temporary table, retrieving the list of employees whose salaries have been updated. Because theSELECT statement is not associated with a cursor or anINTO clause, the rows retrieved will be returned as a result set to the calling program.
47 Terminate the stored procedure.
Example 2-8. Output from the “putting it all together” example mysql> CALL cursor_example2(18) //
+---+---+---+
| employee_id | department_id | new_salary | +---+---+---+
| 396 | 18 | 75560.00 |
| 990 | 18 | 118347.00 | +---+---+---+
2 rows in set (0.23 sec)
Query OK, 0 rows affected (0.23 sec) Line(s) Explanation
• The parameter list of a function may contain onlyINparameters.OUTandINOUT parameters are not allowed. Specifying the INkeyword is neither required nor allowed.
• The function itself must return a single value, whose type is defined in the header of the function.
• Functions can be called from within SQL statements.
• A function may not return a result set.
Generally, you should consider using a stored function rather than a stored procedure when you have a program whose sole purpose is to compute and return a single value or when you want to create a user-defined function for use within SQL statements.
Figure 2-16 shows a function that implements the same functionality found in the discount_price stored procedure we created earlier in this chapter.
Figure 2-16. A stored function
The following table explains a few things that set apart this function from its stored procedure equivalent:
Example 2-9 shows calling this function from within a SQL statement.
We can also call this function from within another stored program (procedure, func- tion, or trigger), or any place that we could use a built-in MySQL function.
Triggers
A trigger is a special type of stored program that fires when a table is modified by an INSERT, UPDATE, or DELETE (DML) statement. Triggers implement functionality that must take place whenever a certain change occurs to the table. Because triggers are attached directly to the table, application code cannot bypass database triggers.
Typical uses of triggers include the implementation of critical business logic, the denormalization of data for performance reasons, and the auditing of changes made to a table. Triggers can be defined to fire before or after a specific DML statement executes.
In Figure 2-17, we create a trigger that fires before any INSERTstatement completes against thesalestable. It automatically applies free shipping and discounts to orders of a specified value.
Line Explanation
7 Specify aRETURNS clause as part of the function definition. This specifies the type of data that the function will return.
8 MySQL applies stricter rules to stored functions than it does to procedures. A function must either be declared not to modify SQL (using theNO SQL orREADS SQL DATA clauses) or be declared to beDETERMINISTIC (if it is to be allowed in servers that have binary logging enabled). This restriction is designed to prevent inconsistencies between replicated databases caused by functions that return an unpredictable value (see Chapter 10 for more details). Our example routine is “deterministic” —we can guarantee that it will return the same result if it is pro- vided with the same input parameter.
21 Use theRETURN statement to pass back the discount price calculated by the IF statement.
Example 2-9. Calling a stored function from a SELECT statement mysql> SELECT f_discount_price(300) $$
+---+
| f_discount_price(300) | +---+
| 270.0 | +---+
Here is an explanation of the trigger definition:
The effect of the trigger is to automatically set the value of the free_shippingand discount columns. Consider theINSERT statement shown in Example 2-10.
Figure 2-17. A database trigger
Line(s) Explanation
5 Specify the trigger name.
6 Specify that the trigger fires before an insert on thesales table.
7 Include the (currently) mandatoryFOR EACH ROWclause, indicating that the statements within the trigger will be executed once for every row inserted into thesales table.
8 UseBEGIN to start the block containing statements to be executed by the trigger.
9-13 If thesale_valueis greater than $500, set the value of thefree_shippingcolumn to'Y'. Otherwise, set it to'N'.
15-19 If thesale_value is greater than $1000, calculate a 15% discount and insert that value into thediscount column. Otherwise, set the discount to 0.
The sale is valued at $10,034 and, as such, is eligible for a 15% discount and free shipping. Example 2-11 demonstrates that the trigger correctly set these values.
Using a trigger to maintain thefree_shippinganddiscountcolumns ensures that the columns are correctly maintained regardless of the SQL statements that might be executed from PHP, C#, or Java, or even from the MySQL command-line client.