MySQL will throw an error and refuse to create a stored function if binary logging of that functionmay be unsafe: ERROR 1418 HY000: This function has none of DETERMINISTIC, NO SQL, or RE
Trang 1continuedWhether or not a stored routine is deterministic is important due to binary logging A deterministicstored routine will replicate without a problem; however, a stored routine that is not deterministicmay have problems replicating If binary logging is set as statement-based (see Chapter 16), thebinary log contains the statements that change data, so they can be replayed during an incrementalrestore or during replication A stored routine that is not deterministic may have a different outputgiven the same input, which means that an incremental restore or slave will not have data thatmatches the original data.
Be very careful when using a stored routine that is not deterministic, because statement-basedbinary logging may not be adequate to store the data changes If data integrity is a concern, userow-based binary logging or a deterministic stored routine instead
Unfortunately, at the time of this writing, theDETERMINISTICandNOT DETERMINISTICoptionsserve only as comments, and are not verified by mysqld There are no warnings or errors if anon-deterministic routine is set asDETERMINISTIC
MySQL will throw an error and refuse to create a stored function if binary logging of that functionmay be unsafe:
ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled
(you *might* want to use the less safe log_bin_trust_function_
creators variable)
The best way to fix this problem is to declare the function to beDETERMINISTIC,NO SQL, or
READS DATA The next best way is to have a user with theSUPER privilege define the function
Turning binary logging off is another way to fix this issue, though it may not be possible Setting
log_bin_trust_function_creatorsto 1 will also fix this issue, though it is less safe becauseyou may end up attempting to replicate a function that is not safe for replication
SQL usage
The remaining option is what SQL statements the stored routine uses These values are forinformational purposes only, and do not affect the waymysqldhandles the stored routine Thepossible values are:
■ MODIFIES SQL DATA— The stored routine may update data (with aDELETE,INSERT, or
UPDATEcommand, for instance)
■ READS SQL DATA— The stored routine does not contain SQL to write data (as in FIES SQL DATA) but does contain SQL that reads data (that is,SELECTstatements) The
MODI-store_offeringsstored procedure is an example of a stored routine that qualifies as
READS SQL DATA
■ CONTAINS SQL— The stored routine does not read or write data in the database The
curr_timeandincrement_counterstored procedures are examples of stored routinesthat qualify asCONTAINS SQL
■ NO SQL— The stored routine has no SQL statements in it
Trang 2The default isCONTAINS SQL Any changes to the default are done manually by theuser; MySQL does not check whether or not the option is correct For example, the
store_offeringsstored procedure is created with the default of CONTAINS SQLeventhoughREADS SQL DATAis the correct option Because this is for informational purposesonly, there is no harm in having this option be incorrect However, if you intend to use theinformation to guide decisions, make sure you set the appropriate option
Full CREATE PROCEDURE syntax
The fullCREATE PROCEDUREsyntax is:
CREATE [DEFINER = { user | CURRENT_USER }]
PROCEDURE p_name ([parameter[, ]])
[SQL SECURITY {DEFINER | INVOKER}]
[option ]
{statement}
option is one or more of:
SQL SECURITY {DEFINER | INVOKER}
COMMENT ’comment string’
LANGUAGE SQL [NOT] DETERMINISTIC {CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA}
parameter is:
[IN|OUT|INOUT] name data_type
Note that theCREATE ROUTINEprivilege is needed in order to create a stored procedure SeeChapter 14 for more information about managing privileges
Creating a basic stored function
A stored function outputs only one scalar value, so there is no such thing as anOUTter in a stored function Therefore, the requirements for arguments to a stored function are thateach input variable is named and the type defined You must also specify the type of the returnvalue, and most of the time, create a local variable to store the value to be returned The differ-ences betweenCREATE PROCEDUREandCREATE FUNCTIONare:
parame-■ All arguments to a function are input parameters; arguments to a procedure may be inputparameters, output variables, orINOUTvariables
■ A function must use theRETURNSkeyword after the input parameters to specify the type
of the scalar value to be returned
■ A function must use theRETURNkeyword in its body to specify the value to be returned
Trang 3The following defines a basic stored function to get thestore_idof a staff member from the
stafftable given astaff_id:
mysql> DELIMITER | mysql> CREATE FUNCTION get_store_id (f_staff_id TINYINT UNSIGNED) RETURNS TINYINT UNSIGNED
-> READS SQL DATA -> BEGIN
-> DECLARE f_store_id TINYINT UNSIGNED;
-> SELECT store_id INTO f_store_id FROM staff WHERE staff_id=f_staff_id;
-> RETURN f_store_id;
-> END -> | Query OK, 0 rows affected (0.03 sec)
mysql> DELIMITER ;
Full CREATE FUNCTION syntax
The full syntax forCREATE FUNCTIONis:
CREATE [DEFINER = { user | CURRENT_USER }]
FUNCTION f_name ([parameter[, ]])
RETURNS type
[option ]
{statement}
option is one or more of:
SQL SECURITY {DEFINER | INVOKER}
COMMENT ’comment string’
LANGUAGE SQL [NOT] DETERMINISTIC {CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA}
parameter is:
name data_type
Note that both theCREATE ROUTINEandSUPERprivileges are needed in order to create astored function See Chapter 14 for more information about managing privileges
Invoking a stored function
A stored function is invoked just as a standard MySQL function is invoked — by using thefunction name and passing input parameters:
mysql> SELECT get_store_id(1);
Trang 4| get_store_id(1) | + -+
+ -+
1 row in set (0.20 sec)
Note that you need theEXECUTEprivilege in order to invoke the function; the creator of thestored function is given this privilege automatically
Changing a stored routine
MySQL supports theALTER PROCEDUREandALTER FUNCTIONstatements, by which you canchange the SQL usage, SQL SECURITYoption, andCOMMENTof a stored routine To changemore than one of these in oneALTERcommand, separate the options by a space:
mysql> ALTER PROCEDURE increment_counter COMMENT "increments the INOUT variable by 1" CONTAINS SQL;
Query OK, 0 rows affected (0.00 sec)
Note that you need theALTER ROUTINEprivilege in order to change the stored routine; the ator of the stored routine is given this privilege automatically
cre-The automatic_sp_privileges system variable can be set to 0 to change the default behavior of a stored routine’s creator getting automatic ALTER ROUTINE privilege.
To change any other part of a stored routine (theDEFINERor the code body, for example), youmust drop and re-create the stored routine
To see details of a stored procedure, use theSHOW CREATE PROCEDUREcommand:
mysql> SHOW CREATE PROCEDURE increment_counter\G
*************************** 1 row ***************************
Procedure: increment_counter sql_mode: STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER, NO_ENGINE_SUBSTITUTION
Create Procedure: CREATE DEFINER=`root`@`localhost` DURE `increment_counter`(INOUT p_count INT UNSIGNED)
PROCE-COMMENT ’increments the INOUT variable by 1’
SET p_count:=p_count+1 character_set_client: latin1 collation_connection: latin1_swedish_ci Database Collation: latin1_swedish_ci
1 row in set (0.03 sec)
To see details of a stored function, use theSHOW CREATE FUNCTIONcommand:
mysql> SHOW CREATE FUNCTION get_store_id\G
Trang 5*************************** 1 row ***************************
Function: get_store_id sql_mode: STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER, NO_ENGINE_SUBSTITUTION
Create Function: CREATE DEFINER=`root`@`localhost` FUNCTION
`get_store_id`(f_staff_id TINYINT UNSIGNED) RETURNS tinyint(3) unsigned
READS SQL DATA BEGIN
DECLARE f_store_id TINYINT UNSIGNED;
SELECT store_id INTO f_store_id FROM staff WHERE staff_id=f_staff_id; RETURN f_store_id;
END character_set_client: latin1 collation_connection: latin1_swedish_ci Database Collation: latin1_swedish_ci
1 row in set (0.00 sec)
There is no shortcut to find all the stored routines associated with a database To useSQL to find what stored routines are in the system, query theROUTINEStable in the
6 rows in set (0.09 sec)
For more about theINFORMATION_SCHEMAdatabase, see Chapter 21
Naming: stored routines
Stored routines can be named using reserved words This is an extremely bad idea to do onpurpose Because it is possible you may accidentally give your stored routine a name that is areserved word, we will explain how to name a stored routine using a reserved word
To name a stored routine with a reserved word, there must be whitespace between the routinename (reserved word) and the opening parenthesis used for the parameters:
mysql> DELIMITER | mysql> CREATE PROCEDURE count(INOUT p_count INT UNSIGNED)
Trang 6-> COMMENT ’this fails because count is a reserved word and no space exists between count and (’
-> BEGIN -> SET p_count:=p_count+1;
-> END -> | ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ’count (INOUT p_count INT UNSIGNED) COMMENT
’this will fail because count is a res’ at line 1 mysql> CREATE PROCEDURE count (INOUT p_count INT UNSIGNED) -> COMMENT ’this succeeds because of the space between count and (, but is a bad idea’
-> BEGIN -> SET p_count:=p_count+1;
-> END -> | Query OK, 0 rows affected (0.25 sec) mysql> DELIMITER ;
The same holds true for using a stored routine named with a reserved word: there must be aspace between the routine name and the opening parenthesis used for the parameters:
mysql> set @count:=123;
Query OK, 0 rows affected (0.00 sec) mysql> CALL count(@count);
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ’count (@count)’ at line 1
mysql> CALL count (@count);
Query OK, 0 rows affected (0.11 sec) mysql> select @count;
+ -+
| @count | + -+
+ -+
1 row in set (0.00 sec)
mysql> dropping the procedure right away for sanity’s sake mysql> DROP PROCEDURE IF EXISTS count;
Query OK, 0 rows affected (0.03 sec)
Note that thesql_modenamedIGNORE_SPACEhas no bearing on this rule —IGNORE_SPACE
does not apply to stored routines
Trang 7If you get an SQL syntax error when attempting to execute a stored routine, check the name
of the stored routine — you may have accidentally given your stored routine a name that is areserved word!
Stored procedure result sets
A stored procedure can return information by way ofOUTandINOUTvariables; however, astored procedure can also return information with SQL statements inside the stored procedure.For example, aSELECTstatement inside a stored procedure will execute as it would on acommand line, and when invoking the stored procedure you would see that result set
Ziesel created thestore_offeringsstored procedure because she wanted to get the count
of movies offered by a givenstore_id, whether or not that movie was in stock To return thecount, she took advantage of anOUTvariable:
DELIMITER | CREATE PROCEDURE store_offerings ( IN p_store_id INT, OUT p_count INT )
SELECT COUNT(*) INTO p_count FROM inventory
WHERE store_id = p_store_id;
| DELIMITER ;
However, Ziesel could have also returned the count by issuing a simpleSELECTstatement, ing the result set back — the same way a result set is sent back when a user directly issues a
send-SELECTstatement:
mysql> DROP PROCEDURE IF EXISTS store_offerings;
Query OK, 0 rows affected (0.02 sec)
mysql> DELIMITER | mysql> CREATE PROCEDURE store_offerings ( IN p_store_id INT ) -> SELECT COUNT(*)
-> FROM inventory -> WHERE store_id = p_store_id;
-> | Query OK, 0 rows affected (0.05 sec) mysql> DELIMITER ;
mysql> CALL store_offerings (1);
+ -+
| COUNT(*) | + -+
+ -+
1 row in set (0.25 sec) Query OK, 0 rows affected (0.25 sec)
Trang 8Ziesel modified the stored procedure, changing theSELECT COUNT(*) INTOquery to aSELECT COUNT(*)query, eliminating the need for anOUTvariable to store the result in The result
is sent back to the client program that invoked the stored procedure (in this case, themysql
command-line client)
Sending Back Result Sets
Stored procedures can send information back in two ways: by updating a variable and by runningqueries that send back result sets There is no need to choose only one method — a single storedprocedure can update variables and run queries that send back results There may be times whenyou want to minimize using user-defined variables; in these cases you may prefer to send back aresult set In other cases, perhaps when you do not want the overhead of processing a returnedresult set, user-defined variables are preferable
The programs that call the stored procedure need to be able to handle all of the returned information,whether or not it is returned via a variable.OUTvariables must be specified when the procedure
is invoked, otherwise the command will fail However, there is no error or warning from MySQL
if information is returned via a result set and your application does not process that result set, orprocesses it incorrectly (perhaps by assuming the result set has exactly one row)
In thestore_offeringsexample, only oneSELECTquery is part of the body However, a storedprocedure can issue many queries, including issuing many queries that return result sets to theclient Make sure your application can handle all possible output, including multiple result sets,errors, warnings, and unexpected result sets (empty result sets, or result sets with more rows thanexpected)
Stored routine errors and warnings
If you did not want to allowpct_increaseto accept negative values forp_incr, you wouldhave defined the input parameter asIN p_incr INT UNSIGNED:
mysql> DROP PROCEDURE IF EXISTS pct_increase;
Query OK, 0 rows affected (0.00 sec) mysql> DELIMITER |
mysql> CREATE PROCEDURE pct_increase (INOUT p_int INT, IN p_incr INT UNSIGNED, OUT p_pct_incr DECIMAL (5,2))
-> BEGIN -> DECLARE p_int_new INT;
-> SET p_int_new:=p_int+p_incr;
-> SET p_pct_incr:=(p_int_new-p_int)/p_int*100;
-> SET p_int:=p_int_new;
-> END -> | Query OK, 0 rows affected (0.00 sec)
Trang 9mysql> DELIMITER ; mysql> SET @num:=100;
Query OK, 0 rows affected (0.00 sec) mysql> CALL pct_increase (@num, -10, @pct);
ERROR 1264 (22003): Out of range value for column ’p_incr’ at row 1 mysqldhandles stored routine errors and warnings as if the client ran the commands interac-tively A user invokingpct_increasewith a negative number for the second argument receivesthe errorOut of range valueas seen in the preceding code The error message is accurate,but it is confusing There is no indication thatp_incrrefers to the second argument (-10) Theerror message specifiescolumn ’p_incr’, which is confusing because there are no columnsbeing referenced, only variables and scalar values
The user will have to examine the stored routine to figure out the problem There is no ger for stored routines, so logical errors can be difficult to detect and fix One way to debug astored procedure is to use aSELECTstatement to print variables at certain positions ASELECT
debug-statement before each line mimics stepping through the stored procedure and checking variablevalues at each step
By the way, data type incompatibilities and overflows in a stored routine are treated the same
as on the command line Warnings or errors are generated, depending on thesql_mode Thisholds true for arguments as well as local variables
Conditions and handlers
Though the error handling in stored routines is not very advanced, MySQL allows you to ify handlers that can trap conditions This allows you to handle known exceptions Using the
spec-DECLAREsyntax, you can create a handler to specify what to do when a condition is met Youcan also create a condition using theDECLAREsyntax for use in a handler
You can find a full list of MySQL error codes, SQL states, and messages in the manual at
Create Procedure: CREATE DEFINER=`root`@`localhost` DURE `pct_increase`
PROCE-(INOUT p_int INT, IN p_incr INT UNSIGNED, OUT p_pct_incr MAL (5,2))
Trang 10DECI-BEGIN DECLARE p_int_new INT;
SET p_int_new:=p_int+p_incr;
SET p_pct_incr:=(p_int_new-p_int)/p_int*100;
SET p_int:=p_int_new;
END character_set_client: latin1 collation_connection: latin1_swedish_ci Database Collation: latin1_swedish_ci
1 row in set (0.00 sec) mysql> SET @num:=100;
Query OK, 0 rows affected (0.00 sec) mysql> CALL pct_increase (@num, 10, @pct);
Query OK, 0 rows affected (0.00 sec) mysql> SELECT @num, @pct;
+ -+ -+
| @num | @pct | + -+ -+
| 110 | 10.00 | + -+ -+
1 row in set (0.00 sec)
mysql> CALL pct_increase (@num, 10, @pct);
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> SHOW WARNINGS;
| 120 | 9.09 | + -+ -+
1 row in set (0.00 sec)
There was data truncated for a variable, and a warning was generated If the stored procedurehad ansql_modethat was set toTRADITIONAL, there would have been an error generated.Note that the warning includedat row 1, so there is no way of knowing which line in the
Trang 11stored procedure the truncation occurred at Looking at the stored procedure definition, there isonly one line that could have thrown this warning:
SET p_pct_incr:=(p_int_new-p_int)/p_int*100;
When the stored procedure was invoked,p_intwas given a value of @num, which had a value
of 110.p_int_newwas set top_int+p_incr, and had been given 10 as a value ofp_incr So
p_int_newhad a value of 120 What did the calculation actually produce?
mysql> SELECT (120-110)/110*100;
+ -+
| (120-110)/110*100 | + -+
+ -+
1 row in set (0.00 sec) p_pct_incrwas defined as aDECIMAL(5,2), and the stored procedure tried to put 9.0909intop_pct_incr The warning is justified, as data was truncated However, you might not careabout data truncation in this calculation
To not be notified, create aHANDLERfor the warning A handler is specified with the syntax:
DECLARE { CONTINUE | EXIT | UNDO } HANDLER FOR condition statement
conditionis one of the following values:
■ SQLWARNING— The SQL state of all warnings
■ NOT FOUND— The SQL state that occurs when a cursor has reached the end of a data set.For more information on cursors, see the ‘‘Using Cursors’’ section later in this chapter
■ SQLEXCEPTION— Any SQL state other thanOK,SQLWARNING, andNOT FOUND
■ mysql_error_code— Replacemysql_error_codewith the error number (that is,1265) to handle
■ condition_name— A user-defined condition name See the ‘‘Conditions’’ subsection formore information
■ SQLSTATE [VALUE] sqlstate— Replacesqlstatewith the SQL state to handle (that
is,SQLSTATE VALUE 01000)
statementis the SQL statement to run when the condition is met
In the example, if data truncation happens, the stored procedure should continue There is noneed to exit or undo anything done previously in the stored procedure Thus, specifyCON- TINUE There are two different ways to specify the condition to handle — eitherSQLWARNING
or 1265, the error code for data truncation Because theSQLWARNINGcondition covers allwarnings, not just data truncation, it is a good idea to limit the handler only to the conditionwhere data is truncated Note that the handler for error code 1265 will be invoked when anydata is truncated, not just for thep_pct_incrvariable
Trang 12The following example increments a user variable when the data truncation handler is invoked.The scope of the user variable is outside of the stored routine, so the stored procedure will dis-play how many times the warning was issued.
mysql> DROP PROCEDURE IF EXISTS pct_increase;
Query OK, 0 rows affected (0.00 sec) mysql> DELIMITER |
mysql> CREATE PROCEDURE pct_increase (INOUT p_int INT,
IN p_incr INT, OUT p_pct_
incr DECIMAL (5,2)) -> BEGIN
-> DECLARE p_int_new INT UNSIGNED;
-> DECLARE CONTINUE HANDLER FOR 1265 SET @warn_count:=@warn_count+1;
Note the lastSELECTquery to show the variable values This is for teaching purposesonly There is no need to see the values of the variables by callingSELECT @num, @pct,
@warn_count
When debugging a stored routine, use SELECT statements to print out information to help find the problem.
Here is an example of using the stored procedure with the handler for data truncation:
mysql> SET @warn_count:=0;
Query OK, 0 rows affected (0.00 sec)
mysql> SET @num:=100;
Query OK, 0 rows affected (0.00 sec)
mysql> CALL pct_increase (@num, 10, @pct);
Trang 13mysql> CALL pct_increase (@num, 10, @pct);
ing would happen?
mysql> SET @num:=0;
Query OK, 0 rows affected (0.00 sec) mysql> CALL pct_increase (@num, 10, @pct);
Trang 141 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec) mysql> CALL pct_increase (@num, 10, @pct);
Trang 15Indeed, the handler is handling the condition of data truncation (MySQL error code 1265)appropriately To truly ignore the condition and not do any action, you could have specified anempty statement:
DECLARE CONTINUE HANDLER FOR 1265 BEGIN END;
Conditions
In the previous example, the ‘‘data truncated’’ error code (1265) was handled with thisDECLARE
statement:
DECLARE CONTINUE HANDLER FOR 1265 SET @warn_count=@warn_count+1;
The DECLARE syntax can be used to create and name a custom condition for use in a handler.The syntax is:
DECLARE condition_name CONDITION FOR { mysql_error_code | SQLSTATE [VALUE] sqlstate }
To name error code 1265 instead of creating a handler that specifies the error code directly, usethe following syntax:
DECLARE data_truncation CONDITION FOR 1265;
And the handler declaration changes to:
DECLARE CONTINUE HANDLER FOR data_truncation SET @warn_count=@warn_count+1;
This may seem like extra work; however, appropriate naming of conditions can make errorhandling much easier Consider the difference between HANDLER FOR data_truncation andHANDLER FOR 1265 — which is more readable? If a stored routine has 100 error handlers,should they be declared by number? Named conditions are extremely useful when conditionsare named appropriately Conditions namederror_handler and ignore_meare not useful
Consider a database policy of always naming conditions explicitly and appropriately.
Order of DECLARE Statements
AllDECLAREconditions must occur before anyDECLAREhandler That makes sense; if a handleruses a condition, the condition must be defined before the handler is MySQL takes this onestep further and requires all conditions be defined before any handlers are defined If a handler orcondition uses a local variable, the local variable must be defined first MySQL requires that all
DECLAREvariable statements must occur before anyDECLAREcondition statement
In short, the required order ofDECLAREstatements is: variables, conditions, handlers If a storedroutine definition violates these requirements,mysqldraises an error and rejects the stored routinedefinition with the following error:
ERROR 1337 (42000): Variable or condition declaration after cursor or dler declaration
Trang 16han-Stored routine flow control
Many common flow control statements are supported in stored routines These statements may
be nested to create blocks of code and code loops The flow control syntax is documented here,though advanced usage of flow control is out of the scope of this book For more information
about using flow control in stored routines, refer to Guy Harrison’s book MySQL Stored
Proce-dure Programming (O’Reilly, ISBN 0-596-10089-2).
Many commands discussed in this chapter require aBEGIN ENDblock to allow multiplestatements However, most flow control statements allow multiple statements without using
aBEGIN ENDblock When more than one statement can be defined in this manner,
statement_listwill denote that more than one statement can be used For flow controlstatements that allow a statement list without aBEGIN ENDblock, the end of the statementlist is implied — for example, in anIFstatement, the end of the statement list is implied by thenextELSEIF,ELSE, orEND IF
IF
The syntax for theIFstatement is:
IF condition THEN statement_list
[ELSEIF condition THEN statement_list]
[ELSE statement_list]
END IF
The IF flow control statement is different from the IF flow control function The IF flow control statement described here is a procedural device The IF flow control function has the syntax IF(test_expr, expr_if_true, expr_if_false)
CASE
As with other procedural code, it is often desirable to replace a nestedIFstatement with aCASE
statement TheCASEstatement has two acceptable syntaxes:
The second syntax is similar, except that the condition includes the value:
CASE
WHEN condition THEN statement_list [ ]
[ELSE statement_list]
END CASE
Trang 17The CASE flow control statement and the CASE flow control function have the same syntax.
To clarify, using the first syntax to see whether the flag is set:
CASE @flag WHEN 0 THEN SELECT "flag is 0"
WHEN 1 THEN SELECT "flag is 1"
ELSE SELECT "flag is not 0 or 1, it is ", @flag;
END CASE;
And using the second syntax:
CASE WHEN @flag=0 THEN SELECT "flag is 0"
WHEN @flag=1 THEN SELECT "flag is 1"
ELSE SELECT "flag is not 0 or 1, it is ", @flag;
END CASE;
In this case, either syntax can be used There are times when only one of the syntaxes canexpress the conditional statement you want — when comparing different values, for example,the second syntax is required
CASEstatements must match on a condition;ELSEis the catch-all condition to use if youbelieve there might be a condition that does not match If a condition is not matched — whichmeans that none of theWHENblocks matched and there is noELSEblock — an error isgenerated:
ERROR 1339 (20000): Case not found for CASE statement
END REPEAT [label]
Similar to other procedural languages, the biggest difference betweenWHILEandREPEAT
(’’until’’) is that withREPEATthe code executes at least once, because the test condition is afterthe code
Trang 18TheREPEATblock does not have to be labeled However, the labels at the beginning and end ofthe loop must match, and if the label at the end is defined, the label at the beginning must also
The loop does not have to be labeled — labels are required for use with theLEAVEor ATEstatements However, the labels at the beginning and end of the loop must match, and ifthe label at the end of the loop is defined, the label at the beginning of the loop must also bedefined
ITER-Note that the name is somewhat misleading — aLOOPis simply a code block To actually form
a loop, other statements must be used to go back to the beginning of aLOOPand exit aLOOP
without reachingEND LOOP In essence,LOOP END LOOPis just a label To make a loop ally loop, use theITERATEandLEAVEcommands
Recursion
MySQL allows recursive stored procedures, but not recursive stored functions The parameter
max_sp_recursion_depthlimits the number of times a procedure can be called recursively.This parameter is set to 0 by default, which disables the use of recursive stored procedures Ifthe value of max_sp_recursion_depthis not changed, a recursive stored procedure may becreated, but when attempting to execute the stored procedure an error will occur:
ERROR 1456 (HY000): Recursive limit 0 (as set by the max_sp_recursion_depth variable) was exceeded for routine your_routine_name
Trang 19The maximum value formax_sp_recursion_depthis 255 Note that this uses space in thethread stack, so increasing this value may require an increase of thethread_stackvariable aswell See Chapter 10 for more information about thethread_stacksystem variable.
Stored routines and replication
The row-based and mixed replication modes write data changes to the binary logs, and are thesafest way to obtain a copy of your data In statement-based replication mode, statements thatmay change data are written to the binary logs, and the statements are applied to the data on theslave server A small discrepancy in the data on a slave and master can have disastrous results instatement-based replication
There are several important properties of stored routines combined with replication If a storedroutine is not replicating as you think it should, consider the following points:
■ TheCREATE,ALTER, andDROPstatements for stored procedures and functions are written
to the binary logs
■ Be careful when using statement-based replication — deterministic stored routines maynot replicate properly.mysqldtries to prevent the creation of non-deterministic routineswhen binary logging is disabled, but is not always successful
■ Stored procedures and functions that call other stored procedures and functions may notreplicate how you think they should using statement-based replication
■ A stored procedure is not invoked on a slave with aCALLstatement; it replicates the actualSQL used by the stored procedure The exception is when a stored procedure is called by astored function (see previous point) If that happens, the stored procedureCALLis indeedreplicated
■ If a function is defined differently on the master and on the slave, and statement-basedreplication is being used, there may be different results on the master and on the slave
■ Though a user invokes a stored function on a master, a slave using statement-based cation will invoke the stored function with full privileges
repli-Stored function limitations
Unlike stored procedures, stored functions cannot:
■ Return a result set
Trang 20■ LOCK TABLESandUNLOCK TABLESwhen there were locked tables
■ SET AUTOCOMMIT=1when it was not already set to 1
■ TRUNCATE TABLE
■ MostALTER,CREATE,DROP, andRENAMEcommands cause an implicitCOMMIT(seeChapter 4)
■ MostCREATEcommands cause an implicitCOMMIT(see Chapter 4)
Stored routine backup and storage
Stored routines are stored in theproctable of themysqldatabase Manual manipulation of the
proctable is not advised Stored routine backup can be done by usingmysqldump, and duringhot and cold backups For more information on backing up stored routines, see Chapter 13
It is possible, but not recommended, to see a list of stored routines by querying theproctable:
mysql> SELECT db,name,type FROM mysql.proc;
+ -+ -+ -+
+ -+ -+ -+
| sakila | get_customer_balance | FUNCTION |
| sakila | inventory_held_by_customer | FUNCTION |
+ -+ -+ -+
9 rows in set (0.00 sec)
We recommend using theROUTINEStable of theINFORMATION_SCHEMAdatabase to get thisinformation, because using theproctable of themysqldatabase sets a bad example
mysql> SELECT ROUTINE_SCHEMA, ROUTINE_NAME, ROUTINE_TYPE -> FROM INFORMATION_SCHEMA.ROUTINES;
+ -+ -+ -+
+ -+ -+ -+
+ -+ -+ -+
6 rows in set (0.01 sec)
Trang 21For more information on theINFORMATION_SCHEMA, see Chapter 21.
Using Cursors
As a declarative language, SQL can be difficult for developers used to procedural code tounderstand For folks who are experts at algorithms, it can be unnerving to have to specify what
data they would like to see, but not be able to specify how the database should get that data.
One of the most common limitations that procedural programmers feel is not being able to gothrough all the data in a set, one by one, and apply a function to it A cursor can be thought of
as a position in a list; you can use a cursor to iterate through each entry in a result set Cursorscan be used in triggers, stored procedures, and stored functions
As an example, Ziesel asks two employees to come up with a way to ensure that all of the actors
in theactortable have at least one corresponding film to their credit Samuel Benjamin, theSQL expert, comes up with:
mysql> SELECT actor.*
-> FROM actor LEFT JOIN film_actor USING (actor_id) -> WHERE film_actor.actor_id IS NULL;
Empty set (0.00 sec)
However, Eli Solnik, the procedural expert, wants to specify an algorithm: go through everyentry in theactortable and see if there is at least one entry in thefilm_actortable This can
be accomplished entirely in SQL by using a cursor in a stored procedure There are a few steps
to using cursors:
■ Define the cursor
■ Open the cursor
■ Retrieve value(s) from the cursor
■ Close the cursor
A cursor is defined using aDECLAREstatement:
DECLARE cursor_name CURSOR FOR select_statement
In the example, Eli’s cursor definition is:
DECLARE c_all_actors CURSOR FOR SELECT actor_id FROM actor;
Cursor declaration must occur after declaring variables and conditions, but before declaringhandlers
Opening a cursor is done withOPEN cursor_name Eli opens his cursor with:
OPEN c_all_actors;
When he is finished with the cursor, he closes it similarly withCLOSE cursor_name:
CLOSE c_all_actors;
Trang 22To retrieve values from the cursor, Eli has to use theFETCH INTOsyntax:
FETCH cursor_name INTO variable_name [, variable_name ]
In this case, Eli’s cursor only selected one field,actor_id, so he only needs one variable Hewill create a variablecur_actorto be the target for the cursor’s current value ofactor_id
The full stored procedure definition is:
mysql> DELIMITER | mysql> CREATE PROCEDURE check_actors() -> BEGIN
-> DECLARE cur_actor SMALLINT UNSIGNED;
-> DECLARE film_count INT UNSIGNED;
-> DECLARE done,actor_count INT UNSIGNED DEFAULT 0;
-> DECLARE c_all_actors CURSOR FOR SELECT actor_id FROM actor; -> DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1;
-> OPEN c_all_actors;
-> WHILE done=0 DO -> FETCH c_all_actors into cur_actor;
-> SET actor_count=actor_count+1;
-> SELECT COUNT(*) INTO film_count FROM film_actor WHERE actor_id=cur_actor;
-> IF film_count=0 THEN -> SELECT * FROM actor WHERE actor_id=cur_actor;
Eli has put a sanity check in to make sure all the actors are in fact being checked — the ableactor_countis incremented with each newactor_id This stored procedure continues
vari-toFETCHtheactor_idof the cursor until it gets an error of NOT FOUND SET, indicating thatthere are no more values in the set
mysql> DELIMITER ; mysql> CALL check_actors;
+ -+
| actor_count | + -+
+ -+
1 row in set (0.02 sec) Query OK, 0 rows affected (0.02 sec)
Trang 23Theactor_countis 201, even though the number of actors is 200 This occurs because the
FETCHcommand that triggers the handler to change thedoneflag happens after the conditioncheck in theWHILEloop The condition to exit theWHILEloop is not triggered until the nexttime the condition is checked, at the beginning of theWHILEloop
Cursors are very powerful tools They can be indispensable when used properly However, usingstored procedures instead of learning proper SQL is not recommended The cursor example weprovided can be done more efficiently using aLEFT JOIN
Using Events
An event is a set of SQL commands that can be scheduled to run once or at regular intervals,similar to the Unix cron daemon On Windows systems the Task Scheduler provides similarfunctionality The event scheduler inmysqldis platform-independent — it works exactly thesame on these two platforms The tight integration with the server allows you store almost all ofyour database maintenance tasks in the database, which means the data and maintenance taskscan be backed up at the same time with the same command Using the event scheduler gets rid
of the need to run a script that has login credentials saved to it Not having to store a usernameand password in a file is very good for security purposes
Events provide a powerful way to perform scheduled database administrator tasks such as forming backups or logging events Events can be executed once, at a specific date/time, or theycan occur at a defined interval You have flexibility of setting a start date and end date too
per-Turning on the event scheduler
Before an event will run, the event scheduler must be turned on The event scheduler is the cess thread that determines when it is time to run this event Because each event has its ownthread, parallel execution of events is allowed You can schedule any number of events to occursimultaneously without any problem Be warned that there is no guarantee that the event willhappen exactly when scheduled The server will attempt to run the event as close to the timescheduled as possible Typically it will be the same time but this cannot be assumed
pro-ExecutingSET GLOBAL event_scheduler = ONstarts the event scheduler Setting
event_scheduler = OFFwill disable the event scheduler While stopped, no new eventswill be run until the scheduler is turned back on However, if any events are running when thescheduler is stopped, the currently running events will finish
mysql> SHOW GLOBAL VARIABLES LIKE ’event_scheduler’;
+ -+ -+
| Variable_name | Value | + -+ -+
| event_scheduler | OFF | + -+ -+
1 row in set (0.00 sec)
Trang 24mysql> SET GLOBAL event_scheduler=’ON’;
Query OK, 0 rows affected (0.00 sec)
mysql> SHOW GLOBAL VARIABLES LIKE ’event_scheduler’;
+ -+ -+
| Variable_name | Value | + -+ -+
| event_scheduler | ON | + -+ -+
1 row in set (0.00 sec)
As with all SET GLOBAL statements you will need SUPER privileges to run this command.
You can see if the event scheduler is working properly by checking the processlist:
mysql> SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO -> FROM INFORMATION_SCHEMA.PROCESSLIST
-> WHERE USER=’event_scheduler’\G
*************************** 1 row ***************************
ID: 10 USER: event_scheduler HOST: localhost DB: NULL COMMAND: Daemon TIME: 48 STATE: Waiting for next activation INFO: NULL
1 row in set (0.00 sec)
In addition toONandOFF, theevent_schedulersystem variable can have a value of ABLED This system variable is not dynamic; it can only be set from an option file or as an argu-ment directly tomysqld, as in the following:
DIS-shell> mysqld event_scheduler=DISABLED.
If the event scheduler is disabled, it cannot be modified from the command-line client If you try
to change the value ofevent_schedulerwhen it is set toDISABLED, you will see the ing error:
follow-mysql> SET GLOBAL event_scheduler=’ON’;
ERROR 1290 (HY000): The MySQL server is running with the event-scheduler=DISABLED or skip-grant-tables option so it cannot execute this statement
Events can be created, dropped, and modified when theevent_scheduleris disabled
Trang 25Creating an event
Creating an event requires theEVENTprivilege See Chapter 14 for more information aboutmanaging privileges
The simplestCREATE EVENTstatement has the following syntax:
CREATE EVENT event_name
ON SCHEDULE schedule
DO statement;
wherescheduledefines either a one-time event usingAT datetime_expror a periodic eventusingEVERY n interval The valid values forintervalare all the interval data types inMySQL except those involving microseconds See Chapter 5 for more detail on the availableinterval data types
In this example, Ziesel wants to know when the event scheduler is working So she creates atable:
mysql> USE test;
Database changed mysql> CREATE TABLE event_scheduler_log ( -> event_time DATETIME NOT NULL -> );
Query OK, 0 rows affected (0.39 sec)
And then she creates the event itself:
mysql> CREATE EVENT event_scheduler_test -> ON SCHEDULE EVERY 1 MINUTE
-> DO INSERT INTO event_scheduler_log (event_time) -> VALUES (NOW());
Query OK, 0 rows affected (0.00 sec)
When the event scheduler is on, every minute the current timestamp is inserted into
event_scheduler_log If the event scheduler is on when the event is created, by default thefirst row is inserted immediately after the event is created, and the second row is inserted oneminute later:
mysql> SELECT SLEEP(60); SELECT event_time from event_scheduler_log; + -+
| SLEEP(60) | + -+