You are on page 1of 98

MySQLStoredProcedures

ByPeterGulutzan Copyright(c)2004,2006byMySQLAB. Allrightsreserved. Book1intheMySQL5.0NewFeaturesSeries SecondEditionRevisedforMySQL5.1

Contents
TechnicalMatters Introduction Adefinitionandanexample Whystoredprocedures? SetupwithMySQL5.0 CREATEPROCEDUREexample Whatstatementsarelegalinaprocedurebody Calltheprocedure Characteristicsclauses Digressions Exercise Parameters Compoundstatements Variables ConditionsandIFTHENELSE CASE Loops ErrorHandling Cursors Security DynamicPREPAREandEXECUTE Functions Metadata Details OracleComparison SQLServerComparison DB2Comparison StandardComparison Style StoredProcedureexample:tables_concat() Functionexample:rno() Functionexample:running_total() Procedureexample:MyISAM"foreignkey"insertion Procedureexample:Errorpropagation Procedureexample:Library Procedureexample:Hierarchy Tipswhenwritinglongroutines Bugs Featurerequests Resources Books TheEnd

TechnicalMatters3 Acknowledgments
Fordesignofstoredprocedures:theANSI/ISOSQLStandardscommittee. Fortheoriginalcode:PerErikMartinofUppsala,Sweden. Forenhancingandfixingthecode:AntonyCurtis,SergeyGlukhov,DmitriLenev. Formanytechnicalsuggestionsandreviewingofthisbook:TrudyPelzer.

AbouttheAuthor
PeterGulutzan,MySQLABSoftwareArchitect,workedwithstoredproceduresfora differentDBMSforseveralyears.HeiscoauthorofthreeSQLbooks.HisjobatMySQL ABinvolveslookingforanydeviationsfromorthodoxSQL,findingtestprocedures,and beingthepioneerwhotriestousethenewcodewithrealdatabases.Helivesin Edmonton,Canada. TechnicalProductionMatters OriginallywritteninDecember2004withOpenOffice,usingthesefonts: Regulartext:Times,regular,12 Computeroutput:Courier,bold,12 Title:Times,bold,36 Partheader:Times,bold,20 Subpartheader:Times,bold,16 Dateoflastrevision:June12006

Introduction4
ThisbookisforlongtimeMySQLuserswhowanttoknow"what'snew"inversion5. Theshortansweris"storedprocedures,triggers,views,information_schema".Thelong answeristheMySQL5.0NewFeaturesseries,andthisbookisthefirstintheseries. Thisistherevisededition,thebookisuptodateforMySQL5.1. WhatI'mhopingtodoismakethislooklikeahandsonsessionwhereyou,asifyou're workingitoutyourselfonyourkeyboard,canwalkthroughsampleproblems. I'llgothrougheachlittleitem,buildingupslowly.Bytheend,I'llbeshowinglarger routinesthatdosomethinguseful,somethingthatyoumighthavethoughtwastough.

ConventionsandStyles
WheneverIwanttoshowactualcode,suchassomethingthatcomesdirectlyfromthe screenofmymysqlclientprogram,IswitchtoaCourierfont,whichlooksdifferentfrom theregulartextfont.Forexample: mysql> DROP FUNCTION f; Query OK, 0 rows affected (0.00 sec) WhentheexampleislargeandIwanttodrawattentiontoaparticularlineorphrase,I highlightwithadoubleunderlineandasmallarrowontherightofthepage.Forexample: mysql> CREATE PROCEDURE p () -> BEGIN -> /* This procedure does nothing */ -> END;// Query OK, 0 rows affected (0.00 sec)

<--

SometimesIwillleaveoutthemysql>and->promptssothatyoucancutthe examplesandpastethemintoyourcopyofthemysqlclientprogram.(Ifyouaren't readingthetextofthisbookinamachinereadableform,trylookingforthescriptonthe mysql.comwebsite.) ItestedalltheexampleswiththepubliclyavailablebetaversionofMySQL5.1.12on Linux,SUSE10.0.Bythetimeyoureadthis,theMySQLversionnumberwillbehigher. TheavailableoperatingsystemsincludeWindows,Linux,Solaris,BSD,AIX,NetWare, MacOSX,andHPUX.SoI'mconfidentthatyou'llbeabletoruneveryexampleonyour computer.Butifnot,well,asanexperiencedMySQLuseryouknowthathelpand supportisalwaysavailable.

Adefinitionandanexample5
Astoredprocedureisaprocedure(likeasubprograminaregularcomputinglanguage) thatisstored(inthedatabase).Correctlyspeaking,MySQLsupports"routines"andthere aretwokindsofroutines:storedprocedureswhichyoucall,orfunctionswhosereturn valuesyouuseinotherSQLstatementsthesamewaythatyouusepreinstalledMySQL functionslikePI().I'llusetheword"storedprocedures"morefrequentlythan"routines" becauseit'swhatwe'veusedinthepast,andwhatpeopleexpectustouse. Astoredprocedurehasaname,a(possiblyempty)parameterlist,andanSQLstatement, whichcancontainmanymoreSQLstatements.Thereisnewsyntaxforlocalvariables, errorhandling,loopcontrol,andIFconditions.Hereisanexampleofastatementthat createsastoredprocedure. CREATE PROCEDURE procedure1 /* name */ (IN parameter1 INTEGER) /* parameters */ BEGIN /* start of block */ DECLARE variable1 CHAR(10); /* variables */ IF parameter1 = 17 THEN /* start of IF */ SET variable1 = 'birds'; /* assignment */ ELSE SET variable1 = 'beasts'; /* assignment */ END IF; /* end of IF */ INSERT INTO table1 VALUES (variable1);/* statement */ END /* end of block */ WhatI'mgoingtodoisexplainindetailallthethingsyoucandowithstoredprocedures. We'llalsogetintoanothernewdatabaseobject,triggers,becausethere isatendencytoassociatetriggerswithstoredprocedures.

WhyStoredProcedures?6
StoredprocedureshavebeenpartoftheofficiallysupportedMySQLreleaseforayear,so nowthere'satrackrecordandalargebodyofuserexperiencethatprovesthey'retheway togo.Understandably,you'llstillbecautious.Nevertheless,youshouldstarttothinknow aboutmovingyourcodeoutofwhereitisnow(anapplicationhostprogram,aUDF,a script),andintoastoredprocedure.Thereasonsforusingproceduresarecompelling. Storedproceduresareproventechnology!Yes,theyarenewinMySQL,butthesame functionalityexistsinotherDBMSs,andoftenpreciselythesamesyntaxtoo.Sothereare conceptsthatyoucansteal,therearepeoplewithexperiencewhomyoucanconsultor hire,thereisthirdpartydocumentation(booksorwebpages)thatyoucanread. Storedproceduresarefast!Well,wecan'tprovethatforMySQLyet,andeveryone's experiencewillvary.WhatwecansayisthattheMySQLservertakessomeadvantageof caching,justaspreparedstatementsdo.Thereisnocompilation,soanSQLstored procedurewon'tworkasquicklyasaprocedurewrittenwithanexternallanguagesuchas C.Themainspeedgaincomesfromreductionofnetworktraffic.Ifyouhavearepetitive taskthatrequireschecking,looping,multiplestatements,andnouserinteraction,doit withasinglecalltoaprocedurethat'sstoredontheserver.Thentherewon'tbemessages goingbackandforthbetweenserverandclient,foreverystepofthetask. Storedproceduresarecomponents!Supposethatyouchangeyourhostlanguageno problem,thelogicisinthedatabasenottheapplication. Storedproceduresareportable!WhenyouwriteyourstoredprocedureinSQL,youknow thatitwillrunoneveryplatformthatMySQLrunson,withoutobligingyoutoinstallan additionalruntimeenvironmentpackage,orsetpermissionsforprogramexecutioninthe operatingsystem,ordeploydifferentpackagesifyouhavedifferentcomputertypes. That'stheadvantageofwritinginSQLratherthaninanexternallanguagelikeJavaorC orPHP.Don'tgetmewrongaboutthis:Iknowtherearesometimesexcellentreasonsto supportexternallanguageroutines,theyjustlackthisparticularadvantage. Storedproceduresarestored!Ifyouwriteaprocedurewiththerightnamingconventions, forexamplesayingchequing_withdrawalforabanktransaction,thenpeoplewhowantto knowaboutchequingcanfindyourprocedure.It'salwaysavailableas'sourcecode'inthe databaseitself.Anditmakessensetolinkthedatawiththeprocessesthatoperateonthe data,asyoumighthaveheardinyourprogrammingtheoryclasses. Storedproceduresaremigratory!MySQLadheresfairlycloselytotheSQL:2003 standard.Others(DB2,Mimer)alsoadhere.Others(Oracle,SQLServer)don'tadherebut I'llbeprovidingtipsandtoolsthatmakeiteasiertotakecodewrittenforanotherDBMS andplunkingitintoMySQL.

SetupwithMySQL5.07
mysql_fix_privilege_tables or ~/mysql-5.1/scripts/mysql_install_db Aspartofthepreparationforourexercises,I'llassumeMySQL5.0orMySQL5.1is installed.Ifyou'renotworkinginaplacewheredatabaseadministratorsinstallsoftware anddatabasesforyou,you'llhavetoinstallityourself.Onethingthat'seasytoforgetis thatyouneedtohaveatablenamedmysql.proc.Afteryou'veinstalledthelatestversion, youshouldruneithermysql_fix_privilege_tablesormysql_install_dborstored procedureswon'tworkforyou.IalsorunanundocumentedSQLscriptafterstartingas user=root.

Startingthemysqlclient
ThisishowIstartthemysqlclient.Youmightdoitdifferently,forexampleyoumight runfromadifferentsubdirectoryifyouhaveabinaryversionoraWindowscomputer. pgulutzan@mysqlcom:~> /usr/local/mysql/bin/mysql --user=root Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 1 to server version: 5.1.12beta-debug Type 'help;' or '\h' for help. Type '\c' to clear the buffer. Duringthisdemonstration,Iwillbeshowingyouwhattheresultslooklikefromthe mysqlclient,whichIstartedafterestablishingmyselfastherootuser.ThismeansIhave lotsofprivileges.

CheckfortheCorrectVersion
ToensurethatI'musingtherightMySQLversion,Idisplaytheversioninuse.Iwantto besurethatIhaveversion5.Ihavetwowaystocheckthis: SHOW VARIABLES LIKE 'version'; or SELECT VERSION();

SetupwithMySQL5.0(continued)8
Forexample: mysql> SHOW VARIABLES LIKE 'version'; +---------------+-------------------+ | Variable_name | Value | +---------------+-------------------+ | version | 5.1.12-beta-debug | +---------------+-------------------+ 1 row in set (0.03 sec) mysql> SELECT VERSION(); +-------------------+ | VERSION() | +-------------------+ | 5.1.12-beta-debug | +-------------------+ 1 row in set (0.01 sec) WhenIsee'5.0.x'or'5.1.x',Iknowthatstoredprocedureswillwork.Thedifferences between5.0and5.1,forstoredproceduresatleast,areslightandrare.

TheSample"Database"
ThefirstthingIdoiscreateanewdatabaseandmakeitmydefaultdatabase. TheSQLstatementsthatIneedforthisare: CREATE DATABASE db5; USE db5; Forexample: mysql> CREATE DATABASE db5; Query OK, 1 row affected (0.00 sec) mysql> USE db5; Database changed Iavoidusingarealdatabasethatmighthaveimportantdatainit.

SetupwithMySQL5.0(continued)9
AndnowI'mmakingasimpletabletoworkwith.ThestatementsthatIuseforthisare: mysql> CREATE DATABASE db5; Query OK, 1 row affected (0.01 sec) mysql> USE db5; Database changed mysql> CREATE TABLE t (s1 INT); Query OK, 0 rows affected (0.01 sec) mysql> INSERT INTO t VALUES (5); Query OK, 1 row affected (0.00 sec) You'llnoticethatI'monlyinsertingonerowintothetable.Iwantmysampletabletobe simple.I'mnotshowingofftablemanipulationhere,I'mshowingoffstoredprocedures, andthey'recomplexenoughwithoutworryingaboutbigtables. Sothesampledatabasethatwe'llstartwithisatablenamedtwithonerowinit.

Pickadelimiter
NowIneedadelimiter.ThestatementI'lluseis DELIMITER //. Forexample: mysql> DELIMITER // Thedelimiteristhecharacterorstringofcharactersthatyou'llusetotellthemysqlclient thatyou'vefinishedtypinginanSQLstatement.Forages,thedelimiterhasalwaysbeena semicolon.Thatcausesaproblembecause,inastoredprocedure,onecanhavemany statements,andeveryonemustendwithasemicolon. Soforyourdelimiter,pickastringwhichisveryunlikelytooccurwithinanystatement, orwithinanyprocedure.I'veusedslashslash.Othersuseaverticalbar.I'veseen'@'in DB2proceduresbutIdon'tlikeit.Youcanusewhateveryouwant(except'\'),butduring thissessionit'sprobablyeasiertocopyme.Toresumeusing;asadelimiterlatersay DELIMITER ;

CREATEPROCEDUREexample10
CREATE PROCEDURE p1 () SELECT * FROM t; // Perhapsthisisthefirststoredprocedurethatyou'veevermadewithMySQL.Ifso,be suretomarkthiseventinyourdiary. CREATE PROCEDURE p1 () SELECT * FROM t; // <--

ThefirstpartoftheSQLstatementthatcreatesastoredprocedureisthewords"CREATE PROCEDURE". CREATE PROCEDURE p1 () SELECT * FROM t; // Thesecondpartistheprocedurename.Thenameofthisnewprocedurewillbep1. <--

DIGRESSION:LegalIdentifiers
ProcedureNamesarenotcasesensitive,so'p1'and'P1'arethesamename. Youcannotusetwoprocedureswiththesamenameinthesamedatabase.Thatwouldbe overloading.SomeDBMSsallowoverloading.MySQLdoesn't.Youcanusequalified namesoftheformdatabasename.procedurename,forexampledb5.p1. Procedurenamescanbedelimited.Ifthenameisdelimited,itcancontainspaces.The maximumnamelengthis64characters.Butavoidusingusingnameswhicharealready namesofbuiltinMySQLfunctions.Here'swhatcanhappenifyoudo: mysql> CALL pi(); Error 1064 (42000): You have a syntax error. mysql> CALL pi (); Error 1305 (42000): PROCEDURE does not exist. Inthefirstexample,Iusedafunctionnamedpi.It'slegal,butyoumustputaspaceafter thenamewhenyou'recallingit,asIdidinthesecondexample.

CREATEPROCEDUREexample(continued)11
CREATE PROCEDURE p1 () SELECT * FROM t; // ()isthe'parameterlist'. ThethirdpartoftheCREATEPROCEDUREstatementistheparameterlist.It'salways necessarytohaveaparenthesizedparameterlisthere.Inthisprocedure,thereareno parameters,sotheparameterlistisemptysoallIhadtotypeistheparentheses.But theyarecompulsory. CREATE PROCEDURE p1 () SELECT * FROM t; // SELECT*FROMt;isthebody. Andfinally,thelastpartofthestatementistheprocedurebody,whichisanSQL statement.HerethebodyisSELECT*FROMt;whichincludesasemicolon;.The semicolonisoptionalwhenthereisarealstatementender(the//)followingit. It'sprobablygoodifyourememberthatI'mcallingthispartthebodyoftheprocedure, becausebodyisatechnicaltermthateverybodyuses. Ordinarily,it'snotnormaltoputSELECTstatementsinstoredprocedures,thisisfor illustration.Idecidedthatsomeprocedureshouldsimplyselectfromourtable,sothat whenyoucalltheprocedureitwillbeobviousthatit'sworking. <-<--

WhatSQLStatementsAreLegalInAProcedureBody12
WhatSQLstatementsarelegalinthebodyofaMySQLprocedure?Youcanmakea procedurethatcontainsprettywellanything,includingINSERT,UPDATE,DELETE, SELECT,DROP,CREATE,REPLACE,andsoon.Theonlyimportantpointto rememberisthatyourcodewon'tbeportableifyouincludeaMySQLextension. AsinStandardSQL:anydatabasemanipulationlanguagestatementsarelegal: CREATE PROCEDURE p () DELETE FROM t; // Also,SETandCOMMITandROLLBACKarelegal: CREATE PROCEDURE p () SET @x = 5; // MySQLextrafeature:anydatabasedefinitionlanguagestatementsarelegal: CREATE PROCEDURE p () DROP TABLE t; // MySQLextension:adirectSELECTislegal: CREATE PROCEDURE p () SELECT 'a'; // Bytheway,IcalltheabilitytoincludeDDLstatementsinaprocedureaMySQL"extra feature"becausetheSQLStandardsaysthatthisisnoncorewhichmeansit'soptional. Onerestrictionisthat,insideaprocedurebody,youcan'tputdatabasedefinition statementsthataffectaroutine.Forexamplethisisillegal: CREATE PROCEDURE p1 () CREATE PROCEDURE p2 () DELETE FROM t; // Sothesestatements,whichareallnewinMySQL5.0,areillegalinaprocedurebody: CREATEPROCEDURE,ALTERPROCEDURE,DROPPROCEDURE,CREATE FUNCTION,DROPFUNCTION,CREATETRIGGER.SoonCREATEEVENT,which isnewinMySQL5.1,willbeillegaltoo.Ontheotherhand,youcansayCREATE PROCEDUREdb5.p1()DROPDATABASEdb5//. StatementslikeUSEdatabasearealsoillegal.MySQLassumesthatthedefault databaseistheonethattheprocedureisin. AndLOADDATAINFILEisillegal.AndLOCKTABLESisillegal.AndCHECKis illegal.

Calltheprocedure13 (1)
Now,tocallaprocedure,allyouhavetodoisenterthewordCALLandthenthenameof theprocedureandthentheparentheses.InMySQLIcanomittheparenthesesbutInever do. Whenyoudothisforourexampleprocedurep1,theresultwillbethatyouseethe contentsofthetabletonyourscreen. mysql> CALL p1() // +------+ | s1 | +------+ | 5 | +------+ 1 row in set (0.03 sec) Query OK, 0 rows affected (0.03 sec) That'sreasonable.Thebodyoftheprocedureisa"SELECT*FROMt;"statement.

(2)
Letmesaythatagain,anotherway. mysql> CALL p1() // doesthesameas: mysql> SELECT * FROM t; // So,whenyoucalltheprocedure,it'sexactlyasifyou'dexecuted "SELECT*FROMt;". Okay,thefundamentalpointthatyoucanCREATEaprocedurethenCALLitisclear now,eh?Ihopethatyouareevensayingtoyourselfthatthisisrathersimple.Butnow we'regoingtodoaseriesofexerciseswhereweaddoneclauseatatime,orvaryoneof theexistingclauses.Therewillbemanysuchclauses,evenbeforewegettocomplex parts.

CharacteristicsClauses14 (1)
CREATE PROCEDURE p2 () LANGUAGE SQL NOT DETERMINISTIC CONTAINS SQL SQL SECURITY DEFINER COMMENT 'A Procedure' SELECT CURRENT_DATE, RAND() FROM t // <-<-<-<-<--

Let'smakeaprocedurethathassomeclauseswhichdescribethecharacteristicsofthe procedure.Theclausescomeaftertheparentheses,butbeforethebody.Theseclausesare alloptional.Whatgoodarethey?

(2)
CREATE PROCEDURE p2 () LANGUAGE SQL NOT DETERMINISTIC CONTAINS SQL SQL SECURITY DEFINER COMMENT 'A Procedure' SELECT CURRENT_DATE, RAND() FROM t // <--

Well,theLANGUAGESQLclauseisnogoodatall.Itsimplymeansthatthebodyofthe procedureiswritteninSQL.Andthat'salwaystrue.Butit'snotabadideatoputthis clausein,becausethere'sanotherDBMSwhichrequiresitnamelyIBMDB2.Souse thisifyoucareaboutDB2compatibility.Besides,inthefuture,MySQLmaysupport callingproceduresinotherlanguagesbesidesSQL.

CharacteristicsClauses(continued)15 (3)
CREATE PROCEDURE p2 () LANGUAGE SQL NOT DETERMINISTIC CONTAINS SQL SQL SECURITY DEFINER COMMENT 'A Procedure' SELECT CURRENT_DATE, RAND() FROM t //

<--

Thenextclause,NOTDETERMINISTIC,isinformational.Adeterministicprocedure, somethinglikethereligiousconceptofpredestination,isaprocedurewhichwillalways returnthesameoutputsgiventhesamedatainputs.Inthiscase,sincethebodyofthe procedurehasaSELECTwhoseresultsarenotpredictable,I'vecalleditNOT DETERMINISTIC.ButtheMySQLoptimizerdoesnotcare,atleastatthistime.

(4)
CREATE PROCEDURE p2 () LANGUAGE SQL NOT DETERMINISTIC CONTAINS SQL SQL SECURITY DEFINER COMMENT 'A Procedure' SELECT CURRENT_DATE, RAND() FROM t //

<--

Thenextclause,CONTAINSSQL,isinformational.InfactallprocedurescontainSQL statements,butiftheyalsoreaddatabases(SELECT)thenitwouldbepropertosay READSSQLDATA,andiftheyalsowritedatabases(UPDATEetc.)thenitwouldbe propertosayMODIFIESSQLDATA.ItisneverpropertosayNOSQL,although MySQLallowsthattoo.

(5)16
CREATE PROCEDURE p2 () LANGUAGE SQL NOT DETERMINISTIC CONTAINS SQL SQL SECURITY DEFINER COMMENT 'A Procedure' SELECT CURRENT_DATE, RAND() FROM t //

<--

Thenextclause,SQLSECURITY,canbeeitherSQLSECURITYDEFINERorSQL SECURITYINVOKER.Thisgetsusintotherealmofprivileges.We'regoingtohavean exercisethattestsprivilegeslateron. SQLSECURITYDEFINERmeans'atCALLtime,checkprivilegesofuserwhocreated theprocedure'(theotherchoiceisSQLSECURITYINVOKER) Forthemoment,it'senoughtonotethatSQLSECURITYDEFINERisaninstruction thattellstheMySQLservertochecktheprivilegesoftheuserwhocreatedtheprocedure atthetimethattheprocedureiscalled,regardlessofwhichuserisdoingtheCALL. Theotheroptiontellstheservertocheckthecaller'sprivilegesinstead.

CharacteristicsClauses(continued)17 (5)
CREATE PROCEDURE p2 () LANGUAGE SQL NOT DETERMINISTIC CONTAINS SQL SQL SECURITY DEFINER COMMENT 'A Procedure' SELECT CURRENT_DATE, RAND() FROM t // COMMENT'Aprocedure'isanoptionalremark. Andfinally,thecommentclauseissomethingthatwillbestoredalongwiththeprocedure definition.Itisnonstandard.I'llpointoutanythingnonstandardasIgoalong,but luckilythatwillberare.

<--

(6)
CREATE PROCEDURE p2 () LANGUAGE SQL NOT DETERMINISTIC CONTAINS SQL SQL SECURITY DEFINER COMMENT '' SELECT CURRENT_DATE, RAND() FROM t // isthesameas: CREATE PROCEDURE p2 () SELECT CURRENT_DATE, RAND() FROM t // Thecharacteristicsclauseshavedefaults.IfIomitthemall,that'sthesameassaying LANGUAGESQLNOTDETERMINISTICCONTAINSSQLSQLSECURITY DEFINERCOMMENT''.

Digressions18

Digression:TheeffectofCALLp2()//
mysql> call p2() // +--------------+-----------------+ | CURRENT_DATE | RAND() | +--------------+-----------------+ | 2006-06-01 | 0.7822275075896 | +--------------+-----------------+ 1 row in set (0.26 sec) Query OK, 0 rows affected (0.26 sec) Andwhenwecallprocedurep2,aSELECTisexecutedwhichreturnsthecurrentdate andarandomnumber,whichiswhatweexpect.

Digression:sql_modeunchanging
mysql> set sql_mode='ansi' // mysql> create procedure p3()select'a'||'b'// mysql> set sql_mode=''// mysql> call p3()// +------------+ | 'a' || 'b' | +------------+ | ab | +------------+ MySQLsilentlypreservestheenvironmentatthetimethattheprocedureiscreatedtoo. Forexample,supposeweusetwobarswhenweareconcatenatingstrings.Thatisonly legalwhilethesqlmodeisansi.Ifwechangethesqlmodetononansi,thatdoesn't matter.Thecallwillstillwork,asiftheoriginalsettingisstilltrue.

Exercise19 Question
Ifyoudon'tmindexercise,askyourselfwhetheryoucouldhandlethisrequest withoutpeekingattheAnswerbelow. Createaprocedure.Theprocedureshoulddisplay'Hello,world'. Pleasetakeaboutfivesecondstocontemplatetheproblem. Givenwhatyouhavereadsofar,thisshouldbeeasy.Whileyouthinkaboutit,I'lljust reviewsomerandomchoicethingsthatI'vesaidalready:TheDETERMINISTICclauseis acharacteristicsclausethatmeanstheinputdeterminestheoutputreliably...The statementforcallingaprocedureisCALLprocedure_name(parameterlist).Well,Iguess that'saboutenoughtime.

Answer
Okay.Theansweristhatyou'dmakeaprocedurewithabodythatcontainsaa"SELECT 'Hello,world'"statement. mysql> CREATE PROCEDURE p4 () SELECT 'Hello, world' // Query OK, 0 rows affected (0.00 sec) mysql> CALL p4()// +--------------+ | Hello, world | +--------------+ | Hello, world | +--------------+ 1 row in set (0.00 sec) Query OK, 0 rows affected (0.00 sec)

Parameters20
Let'slookmorecloselyathowyoucandefineparametersinastoredprocedure. 1.CREATE PROCEDURE p5 () ... 2.CREATE PROCEDURE p5 ([IN] name data-type) ... 3.CREATE PROCEDURE p5 (OUT name data-type) ... 4.CREATE PROCEDURE p5 (INOUT name data-type) ... Recallthattheparameterlistiswhat'sbetweentheparenthesesjustafterthestored procedurename. Inthefirstexampletheparameterlistisempty. Inthesecondexamplethereisoneinputparameter.ThewordINisoptionalbecause parametersareIN(input)bydefault. Inthethirdexamplethereisoneoutputparameter. Inthefourthexamplethereisoneparameterwhichisbothinputandoutput.

INexample
mysql> CREATE PROCEDURE p5(p INT) SET @x = p // Query OK, 0 rows affected (0.00 sec) mysql> CALL p5(12345)// Query OK, 0 rows affected (0.00 sec) mysql> SELECT @x// +-------+ | @x | +-------+ | 12345 | +-------+ 1 row in set (0.00 sec)

Parameters(continued)21
TheINexampleshowsaprocedurewithaninputparameter.Inthebodyoftheprocedure, IsaythatIwanttosetasessionvariablenamedxtothevalueofwhateverthevalueofthe parameterpis.ThenIcalltheprocedureandpass12345astheargumentforparameterp. ThenIselectthesessionvariable,toprovethatitgotthevaluethatIpassed,12345.

OUTexample
mysql> CREATE PROCEDURE p6 (OUT p INT) -> SET p = -5 // mysql> CALL p6(@y)// mysql> SELECT @y// +------+ | @y | +------+ | -5 | +------+ Nowhere'sanexamplewhereIgotheotherway.Thistimepisthenameofanoutput parameterandI'mpassingitsvaluetoasessionvariablenamed@y,intheCALL statement. Inthebodyoftheprocedure,Isaythattheparameterwillgetthevalueminus5.And afterIcalltheprocedure,Iseethatiswhathappened.SothewordOUTtellstheDBMS thatthevaluegoesoutfromtheprocedure. TheeffectisthesameasifIhadexecutedthestatementSET@y=5;.

Compoundstatements22
Let'sexpandwhat'sintheprocedurebodynow. CREATE PROCEDURE p7 () BEGIN SET @a = 5; SET @b = 5; INSERT INTO t VALUES (@a); SELECT s1 * @a FROM t WHERE s1 >= @b; END; // /* I won't CALL this. */ Theconstructyouneed,inordertodothis,isaBEGIN/ENDblock.Thisissimilartothe BEGIN/ENDblocksthatappearinlanguageslikePascal,ortothebracesthatappearin languageslikeC.Youusetheblocktoenclosemultiplestatements.Inthisexample,I've putinacoupleofstatementsthatchangethevalueofsomesessionvariables,andthen I'vedonesomeinsertandselectstatements. YouneedaBEGIN/ENDblockwhenyouhavemorethanonestatementintheprocedure. Butthat'snotall.TheBEGIN/ENDblock,alsocalledacompoundstatement,istheplace whereyoucandefinevariablesandflowofcontrol.

TheNewSQLStatements
Thenewstatements: *arepartoftheANSI/ISOPersistentStoredModules(PSM)language *arelegalwithincompound(BEGIN...END)statements *includeaDECLAREstatementforvariabledeclaration *includeIF,LOOP,WHILE,REPEAT,etc.forflowofcontrol. ThereareseveralnewstatementsthatyoucanusewithinaBEGINENDblock.They weredefinedbytheSQLstandarddocumentcalledPersistentStoredModules,orPSM. Thenewstatementsletyoudefinevariablesandloops,justasyoucandoinanyordinary proceduralprogramminglanguage. InthenextpagesI'lltalkmoreaboutthenewstatements.

Variables23
TheDECLAREstatementisusedtodefinelocalvariablesinaBEGIN..ENDstatement.

(1)ExamplewithtwoDECLAREstatements
CREATE PROCEDURE p8 () BEGIN DECLARE a INT; DECLARE b INT; SET a = 5; SET b = 5; INSERT INTO t VALUES (a); SELECT s1 * a FROM t WHERE s1 >= b; END; // /* I won't CALL this */ Youdon'treallydefinevariableswithinthestoredprocedure.Youdefinethemwithinthe BEGIN/ENDblock. Noticethatthesevariablesarenotlikesessionvariables: Youdon'tstartthemwithanatsign(@). YoumustdeclarethemexplicitlyatthestartoftheBEGIN/ENDblock,alongwiththeir datatypes. Onceyou'vedeclaredavariable,youcanuseitanywherethatyouwouldotherwiseuse anysessionvariable,orliteral,orcolumnname.

(2)ExamplewithnoDEFAULTclauseandSETstatement
CREATE PROCEDURE p9 () BEGIN DECLARE a INT /* there is no DEFAULT clause */; DECLARE b INT /* there is no DEFAULT clause */; SET a = 5; /* there is a SET statement */ SET b = 5; /* there is a SET statement */ INSERT INTO t VALUES (a); SELECT s1 * a FROM t WHERE s1 >= b; END; // /* I won't CALL this */

Variables(continued)24
Thereareseveralwaystoinitializeavariable.WhendeclaredwithoutaDEFAULT clause,theinitialvalueofavariableisalwaysNULL.YoucanusetheSETstatementto assignanothervaluetoavariable.

(3)ExamplewithDEFAULTclause
CREATE PROCEDURE p10 () BEGIN DECLARE a, b INT DEFAULT 5; INSERT INTO t VALUES (a); SELECT s1 * a FROM t WHERE s1 >= b; END; // Here'savariationthatdoesthesamething.ThistimeI'mputtingbothvariable declarationsonthesamelineandusingaDEFAULTclausetosettheinitialvalue,rather thandoingtwoseparateDECLAREandSETstatements.BothaandbareDEFAULT5.

(4)ExampleofCALL
mysql> CALL p10() // +--------+ | s1 * a | +--------+ | 25 | | 25 | +--------+ 2 rows in set (0.00 sec) Query OK, 0 rows affected (0.00 sec) Onceagain,I'mjustcallingtheproceduretoshowyouthatitworks.

Variables(continued)25 (5)Scope
CREATE PROCEDURE p11 () BEGIN DECLARE x1 CHAR(5) DEFAULT 'outer'; BEGIN DECLARE x1 CHAR(5) DEFAULT 'inner'; SELECT x1; END; SELECT x1; END; // Nowlet'sthinkaboutscope.ThisScopeExampleprocedurehasaBEGIN/ENDblock withinanotherBEGIN/ENDblock.That'sfine,perfectlylegal. Italsohastwovariables,bothnamedx1.That'sstilllegal.Inthiscase,theinnervariable declarationtakesprecedenceaslongasit'sinscope. ThepointisthattheinnervariabledisappearswhenthefirstENDisreached.That'swhat "outofscope"means.Thevariableceasestobevisibleatthispoint.Thereforeyoucan neverseeadeclaredvariableoutsideastoredprocedure;however,youcanassignittoan OUTparameterorassignittoasessionvariable. SonowI'llcalltheScopeExampleprocedure. mysql> CALL p11()// +-------+ | x1 | +-------+ | inner | +-------+ +-------+ | x1 | +-------+ | outer | +-------+ Whatyouseeintheoutputis:thatthefirstSELECTretrievestheinnervariable,andthe secondSELECTretrievestheoutervariable.

ConditionsandIFTHENELSE26 (1)
Nowwecandosomethingthatinvolvesconditions. CREATE PROCEDURE p12 (IN parameter1 INT) BEGIN DECLARE variable1 INT; SET variable1 = parameter1 + 1; IF variable1 = 0 THEN INSERT INTO t VALUES (17); END IF; IF parameter1 = 0 THEN UPDATE t SET s1 = s1 + 1; ELSE UPDATE t SET s1 = s1 + 2; END IF; END; // Here'sastoredprocedurethatcontainsanIFstatement.Well,itcontainstwo,actually. OneisjustIFdahdahdahENDIF.TheotherisIFdahdahdahELSEdahdahdahEND IF. I'mtryingtoshowthatit'spossibletohavesomethingcomplex,butatthesametimeI'm tryingtokeepitsimpleenoughthatyoucanfigureoutwhat'shappening.

(2)
CALL p12 (0) // Supposewecallthisprocedureandpassazero.Soparameter1willbezero.

ConditionsandIFTHENELSE(continued)27 (3)
CREATE PROCEDURE p12 (IN parameter1 INT) BEGIN DECLARE variable1 INT; SET variable1 = parameter1 + 1; IF variable1 = 0 THEN INSERT INTO t VALUES (17); END IF; IF parameter1 = 0 THEN UPDATE t SET s1 = s1 + 1; ELSE UPDATE t SET s1 = s1 + 2; END IF; END; //

<--

Thefirstthingthathappensisthatvariable1getssettoparameter1plusone,whichmeans itgetssettozeroplusonesoitwillbeone.

(4)
CREATE PROCEDURE p12 (IN parameter1 INT) BEGIN DECLARE variable1 INT; SET variable1 = parameter1 + 1; IF variable1 = 0 THEN INSERT INTO t VALUES (17); END IF; IF parameter1 = 0 THEN UPDATE t SET s1 = s1 + 1; ELSE UPDATE t SET s1 = s1 + 2; END IF; END; //

<--

Thenextthingthathappensisnothing.Sincevariable1isone,thecondition"variable1= 0"isn'ttrue.ThereforeeverythingbetweenthefirstIFandthematchingENDIFgets skipped.

ConditionsandIFTHENELSE(continued)28 (5)
CREATE PROCEDURE p12 (IN parameter1 INT) BEGIN DECLARE variable1 INT; SET variable1 = parameter1 + 1; IF variable1 = 0 THEN INSERT INTO t VALUES (17); END IF; IF parameter1 = 0 THEN UPDATE t SET s1 = s1 + 1; ELSE UPDATE t SET s1 = s1 + 2; END IF; END; //

<--

SonowwegoontothesecondIFstatement.Andforthisstatement,theconditionistrue, becauseweknowthatparameter1equalszero.That'swhatwepassed.

(6)
CREATE PROCEDURE p12 (IN parameter1 INT) BEGIN DECLARE variable1 INT; SET variable1 = parameter1 + 1; IF variable1 = 0 THEN INSERT INTO t VALUES (17); END IF; IF parameter1 = 0 THEN UPDATE t SET s1 = s1 + 1; ELSE UPDATE t SET s1 = s1 + 2; END IF; END; //

<--

Andsinceit'struethatparameter1equalszero,thisUPDATEisexecuted.Ifithadbeen false,orifparameter1hadbeennull,theotherUPDATEwouldhavebeenexecuted instead.

ConditionsandIFTHENELSE(continued)29
Therearecurrentlytworowsintablet,andtheybothcontainthevalue5,sonowifwe callp12,bothrowsshouldcontain6.

(7)
mysql> CALL p12(0)// Query OK, 2 rows affected (0.28 sec) mysql> SELECT * FROM t// +------+ | s1 | +------+ | 6 | | 6 | +------+ 2 rows in set (0.01 sec) Andwow,theydo.

CASE30 (1)
CREATE PROCEDURE p13 (IN parameter1 INT) BEGIN DECLARE variable1 INT; SET variable1 = parameter1 + 1; CASE variable1 WHEN 0 THEN INSERT INTO t VALUES (17); WHEN 1 THEN INSERT INTO t VALUES (18); ELSE INSERT INTO t VALUES (19); END CASE; END; // InadditiontotheIFstatement,thereisanotherwaytocheckforconditionsandtakepaths accordingtowhetherexpressionsaretrueornontrue.It'stheCASEstatement. ThisCASEstatementcouldjustaseasilyhavebeenexpressedwithIFstatements.Ifthe valueofvariable1iszero,theninsertseventeenintotablet.Ifit'sone,theninsert eighteen.Ifit'sneitherzeronorone,theninsertnineteen.

(2)
mysql> CALL p13(1)// Query OK, 1 row affected (0.00 sec) mysql> SELECT * FROM t// +------+ | s1 | +------+ | 6 | | 6 | | 19 | +------+ 3 rows in set (0.00 sec) Soweexecutetheprocedure,passingaone.Weexpectthattheresultisthatavalueof nineteenwillbeinsertedintotablet. Andthereitis.It'snicetoseehowpredictableproceduresare.

CASE(continued)32 Question
QUESTION:WHATDOESCALLp13(NULL)//do? Andnow,anotherassignment.Thequestionis:whatwillthisCALLstatementdo?You caneitherfindoutbyexecutingitandthenseeingwhattheSELECTdoes,oryoucan figureitoutbylookingatthecode.Giveyourselffivesecondstofigureitout.

Answer
mysql> CALL p13(NULL)// Query OK, 1 row affected (0.00 sec) mysql> SELECT * FROM t// +------+ | s1 | +------+ | 6 | | 6 | | 19 | | 19 | +------+ 4 rows in set (0.00 sec) Okay,theansweristhatwhenyoucallprocedurep13,MySQLinsertsonemorerow containing19.Thereasonis,ofcourse,thatsincethevalueofvariable1isNULL,the ELSEportionoftheCASEstatementisprocessed.Iamhopingthatmakessenseto everyone.Rememberthatifyoucan'tunderstandananswer,it'sokay,we'lljustkeep goingforwardasifyoudo.

Loops33
WHILE ... END WHILE LOOP ... END LOOP REPEAT ... END REPEAT Nowwe'regoingtomakesomeloops.Thereareactuallythreestandardwaystoloop: WHILEloops,LOOPloops,andREPEATloops.Weusedtohaveanonstandardway, GOTO,butit'sgonenow.

WHILE...ENDWHILE
CREATE PROCEDURE p14 () BEGIN DECLARE v INT; SET v = 0; WHILE v < 5 DO INSERT INTO t VALUES (v); SET v = v + 1; END WHILE; END; // Here'soneway,theWHILEloop.It'smyfavouritebecauseitlooksmuchlikeanIF statement,sothere'slessnewsyntaxtolearn. TheINSERTandtheSETstatementshere,whicharebetweentheWHILEandtheEND WHILE,happenoverandoveruntilthevalueinvariablevbecomesgreaterthanorequal tofive.IsaidSETv=0;firsttoavoidacommontrap:ifIdon'tinitializeavariable withDEFAULTorSET,thenit'sNULL,andadding1toNULLalwaysyieldsNULL.

WHILE...ENDWHILEexample
mysql> CALL p14()// Query OK, 1 row affected (0.00 sec) Here'swhathappenswhenweCALLprocedurep14. Trynottoworrythatitsays"onerowaffected"insteadof"fiverowsaffected".Thecount appliesonlyforthelastINSERT.

Loops(continued)34 WHILE...ENDWHILEexample:CALL
mysql> select * from t; // +------+ | s1 | +------+ .... | 0 | | 1 | | 2 | | 3 | | 4 | +------+ 9 rows in set (0.00 sec) AndhereyouseethatfiveinsertshappenedwhenwedidtheCALL.

REPEAT...ENDREPEAT
CREATE PROCEDURE p15 () BEGIN DECLARE v INT; SET v = 0; REPEAT INSERT INTO t VALUES (v); SET v = v + 1; UNTIL v >= 5 END REPEAT; END; // Here'sanotherwayofdoingaloop,theREPEATloop.It'sdoingthesamethingasthe WHILEloopdid,exceptthatitcheckstheconditionafterdoingthestatements,instead ofcheckingtheconditionbeforedoingthestatements,astheWHILEloopdid.

Loops(continued)35 REPEAT...ENDREPEAT:lookattheUNTIL
CREATE PROCEDURE p15 () BEGIN DECLARE v INT; SET v = 0; REPEAT INSERT INTO t VALUES (v); SET v = v + 1; UNTIL v >= 5 END REPEAT; END; //

<--

NotethatthereisnosemicolonaftertheUNTILclauseinthisprocedurestatement.This isoneofthefewtimesthatyoucanhavetroublewithsemicolonrules.Normallyit'sokay ifyouputanextrasemicoloninsomewhere,whichiswhatItendtodo.

REPEAT...ENDREPEAT:calling
mysql> CALL p15()// Query OK, 1 row affected (0.00 sec) mysql> SELECT COUNT(*) FROM t// +----------+ | COUNT(*) | +----------+ | 14 | +----------+ 1 row in set (0.00 sec) Andhereyouseethatfivemoreinsertshappenedwhenwecalledprocedurep15.

LOOPS(continued)36 LOOP...ENDLOOP
CREATE PROCEDURE p16 () BEGIN DECLARE v INT; SET v = 0; loop_label: LOOP INSERT INTO t VALUES (v); SET v = v + 1; IF v >= 5 THEN LEAVE loop_label; END IF; END LOOP; END; // Here'sanotherwaytowritealoop:theLOOPloop.TheLOOPloopdoesn'thavetohave aconditionatthestart,likeaWHILEloopdoes.Anditdoesn'thavetohaveacondition attheend,likeaREPEATloopdoes.

LOOP...ENDLOOP:withIFandLEAVE
CREATE PROCEDURE p16 () BEGIN DECLARE v INT; SET v = 0; loop_label: LOOP INSERT INTO t VALUES (v); SET v = v + 1; IF v >= 5 THEN LEAVE loop_label; END IF; END LOOP; END; // Instead,youputanIFstatementsomewhereinsidetheloop. AndinsidethatIFstatement,youputaLEAVEstatement.TheLEAVEstatementmeans "exittheloop".TheactualsyntaxoftheLEAVEstatementisthewordLEAVEanda statementlabel.I'lltalkmoreaboutthisstatementlabelinamoment.

<--

LOOPS(continued)37 LOOP...ENDLOOP:calling
mysql> CALL p16()// Query OK, 1 row affected (0.00 sec) mysql> SELECT COUNT(*) FROM t// +----------+ | COUNT(*) | +----------+ | 19 | +----------+ 1 row in set (0.00 sec) Let'scallprocedurep16.Theresultisthatanother5rowsareinsertedintotablet.

Labels
CREATE PROCEDURE p17 () label_1: BEGIN label_2: WHILE 0 = 1 DO LEAVE label_2; END WHILE; label_3: REPEAT LEAVE label_3; UNTIL 0 =0 END REPEAT; label_4: LOOP LEAVE label_4; END LOOP; END; // InthelastLOOPexample,Iusedastatementlabel.Nowhereisanexampleofa procedurewithfourstatementlabels.It'spossibletouseastatementlabelbeforeBEGIN, beforeWHILE,beforeREPEAT,orbeforeLOOP.Thosearetheonlyplaceswherea statementlabelisalegalstarttoastatement. ThusthestatementLEAVElabel_3doesn'tmeanjumptolabel_3.Rather,itmeans leavethe(compoundorlooping)statementwhichisidentifiedbylabel_3.

Loops(continued)38 EndLabels
CREATE PROCEDURE p18 () label_1: BEGIN label_2: WHILE 0 = 1 DO LEAVE label_2; END WHILE label_2; label_3: REPEAT LEAVE label_3; UNTIL 0 =0 END REPEAT label_3 ; label_4: LOOP LEAVE label_4; END LOOP label_4 ; END label_1 ; // Onecanalsohavestatementlabelsattheend,aswellasatthestart,ofthesesame statements.Theseendinglabelsaren'tterriblyuseful.They'reoptional.Ifyouputonein, ithastobethesameasthestartinglabel.Inalongpieceofcode,theendlabelcan preventconfusionit'seasiertoseewhatendgoeswithwhatstart.

LEAVEandLabels
CREATE PROCEDURE p19 (parameter1 CHAR) label_1: BEGIN label_2: BEGIN label_3: BEGIN IF parameter1 IS NOT NULL THEN IF parameter1 = 'a' THEN LEAVE label_1; ELSE BEGIN IF parameter1 = 'b' THEN LEAVE label_2; ELSE LEAVE label_3; END IF; END; END IF; END IF; END; END; END;// LEAVEstatementsgetmeoutofdeeplynestedcompoundstatements,too.

Loops(continued)39 ITERATE
Thereisoneotherthingthatlabelsarenecessaryfor,astargetsforITERATEstatements. CREATE PROCEDURE p20 () BEGIN DECLARE v INT; SET v = 0; loop_label: LOOP IF v = 3 THEN SET v = v + 1; ITERATE loop_label; END IF; INSERT INTO t VALUES (v); SET v = v + 1; IF v >= 5 THEN LEAVE loop_label; END IF; END LOOP; END; // TheITERATEstatement,liketheLEAVEstatement,appearswithinloopsandrefersto looplabels.Inthatcase,ITERATEmeans"starttheloopagain". ITERATEisabitlikecontinueintheClanguage. (Also:TheITERATEstatement,liketheLEAVEstatement,appearswithincompound statementsandreferstocompoundstatementlabels.Inthatcase,ITERATEmeansstart thecompoundstatementagain.ButIhaven'tillustratedthathere.) So,forthisexample,let'sstartwalkingthroughthisloop.We'lldoitafewtimes.

Loops(continued)40 ITERATE:Walkingthroughtheloop
CREATE PROCEDURE p20 () BEGIN DECLARE v INT; SET v = 0; loop_label: LOOP IF v = 3 THEN SET v = v + 1; ITERATE loop_label; END IF; INSERT INTO t VALUES (v); SET v = v + 1; IF v >= 5 THEN LEAVE loop_label; END IF; END LOOP; END; // We'reloopingthroughalooplabelledwithloop_label.

<--

ITERATE:Walkingthroughtheloop
CREATE PROCEDURE p20 () BEGIN DECLARE v INT; SET v = 0; loop_label: LOOP IF v = 3 THEN SET v = v + 1; ITERATE loop_label; END IF; INSERT INTO t VALUES (v); SET v = v + 1; IF v >= 5 THEN LEAVE loop_label; END IF; END LOOP; END; // Eventuallythevalueofvwillbethree.Thenwe'llincreaseittofour.

<--

Loops(continued)41 ITERATE:walkingthroughtheloop
CREATE PROCEDURE p20 () BEGIN DECLARE v INT; SET v = 0; loop_label: LOOP IF v = 3 THEN SET v = v + 1; ITERATE loop_label; END IF; INSERT INTO t VALUES (v); SET v = v + 1; IF v >= 5 THEN LEAVE loop_label; END IF; END LOOP; END; // Thenwe'lldotheITERATE.

<--

ITERATE:walkingthroughtheloop
CREATE PROCEDURE p20 () BEGIN DECLARE v INT; SET v = 0; loop_label: LOOP IF v = 3 THEN SET v = v + 1; ITERATE loop_label; END IF; INSERT INTO t VALUES (v); SET v = v + 1; IF v >= 5 THEN LEAVE loop_label; END IF; END LOOP; END; // Whichcausesexecutiontostartagainatthebeginningoftheloop.

<--

Loops(continued)42 ITERATE:walkingthroughtheloop
CREATE PROCEDURE p20 () BEGIN DECLARE v INT; SET v = 0; loop_label: LOOP IF v = 3 THEN SET v = v + 1; ITERATE loop_label; END IF; INSERT INTO t VALUES (v); SET v = v + 1; IF v >= 5 THEN LEAVE loop_label; END IF; END LOOP; END; // Thenvariablevbecomesfive,sowereachtheLEAVEstatement.

<--

ITERATE:walkingthroughtheloop
CREATE PROCEDURE p20 () BEGIN DECLARE v INT; SET v = 0; loop_label: LOOP IF v = 3 THEN SET v = v + 1; ITERATE loop_label; END IF; INSERT INTO t VALUES (v); SET v = v + 1; IF v >= 5 THEN LEAVE loop_label; END IF; END LOOP; END; //

<--

AsaresultoftheLEAVE,weexittheloopandreachtheendofthecompoundstatement.

Grandcombination43
CREATE PROCEDURE p21 (IN parameter_1 INT, OUT parameter_2 INT) LANGUAGE SQL DETERMINISTIC SQL SECURITY INVOKER BEGIN DECLARE v INT; start_label: LOOP IF v = v THEN LEAVE start_label; ELSE ITERATE start_label; END IF; END LOOP start_label; REPEAT WHILE 1 = 0 DO BEGIN END; END WHILE; UNTIL v = v END REPEAT; END;// Here'sastatementthathaseverythinginitthatyou'veseensofar.Thisisaprocedure withparameters,characteristics,aBEGIN/ENDblock,statementswithinstatements,a variabledeclaration,alabel,anIF,aWHILE,aLOOP,aREPEAT,aLEAVE,andan ITERATE. It'saridiculousprocedureandIwouldn'trunitbecauseitcontainsinfiniteloops.Butit doescontainalllegalsyntax. Theseareallthenewflowofcontrolandvariabledeclarationstatements.Now,I'mgoing tomoveontoadifferentthing.

ErrorHandling44
Summaryofthenextfewpages: SampleProblem Handlers Conditions Okay,thethingwe'removingontonowiserrorhandling.

SampleProblem:LogOfFailures(1)
WhenINSERTfails,Iwanttorecordthefailureinalogfile. Thesampleproblemwe'llusetoshowerrorhandlingisacommonone.Iwanttohavea logoferrors.WhenanINSERTfails,Iwanttoputanoticeintoaseparatefile somewherethattheinsertfailed,whenitfailed,andwhyitfailed.Let'ssaythatI'm particularlyinterestedininsertsbecauseofviolationsofforeignkeyconstraints.

SampleProblem:LogOfFailures(2)
mysql> CREATE TABLE t2 s1 INT, PRIMARY KEY (s1)) engine=innodb;// mysql> CREATE TABLE t3 (s1 INT, KEY (s1), FOREIGN KEY (s1) REFERENCES t2 (s1)) engine=innodb;// mysql> INSERT INTO t3 VALUES (5);// ... ERROR 1216 (23000): Cannot add or update a child row: a foreign key constraint fails Istartoffbymakingaprimarykeytable,andaforeignkeytable.I'musingInnoDB,so foreignkeychecksshouldwork.ThenItrytoinsertavalueintheforeignkeytablethat isn'tintheprimarykeytable.Thatwillfail,Iknow.Thisisjustaquickwaytofindout whattheerrornumberisforthecondition.It'sErrorNumberonetwoonesix.

Errorhandling(continued)45 SampleProblem:LogOfFailures(3)
CREATE TABLE error_log (error_message CHAR(80))// Next,ImakeatableinwhichIamgoingtostoreerrorsthatoccurwhenI'mtryingtodo myinserts.

SampleProblem:LogOfErrors(4)
CREATE PROCEDURE p22 (parameter1 INT) BEGIN DECLARE EXIT HANDLER FOR 1216 INSERT INTO error_log VALUES (CONCAT('Time: ',current_date, '. Foreign Key Reference Failure For Value = ',parameter1)); INSERT INTO t3 VALUES (parameter1); END;// Here'smyprocedure.Thefirststatementhere,DECLAREEXITHANDLER,iswhat handlesexceptions.Thisissayingthatiferroronetwoonesixhappens,thenthe procedurewillinsertarowintotheerrorlogtable.ThewordEXITmeans"we'llexit fromthecompoundstatementwhenwe'redone".

SampleProblem:LogOfErrors(5)
CALL p22 (5) // Nowlet'scalltheprocedure.Itwillfail,ofcourse,becausethevaluefivedoesn'tappear intheprimarykeytable.Butthereisnoerrorreturn,becausetheerrorishandledinside theprocedure. WhenIlookatthecontentsoftablet3,Iseethatnothinggotinsertedintotablet3.But whenIlookatthecontentsoftableerror_log,Iseethatsomethingdidgetaddedinto tableerror_log.ThistellsmethatanINSERTintotablet3failed.

ErrorHandling(continued)46 DECLAREHANDLERsyntax
DECLARE { EXIT | CONTINUE } HANDLER FOR { error-number | { SQLSTATE error-string } | condition } SQL statement Okay,sothat'showahandlerworks.It'sapieceofcodethatgetstriggeredwhenanerror happens.MySQLinfactallowstwokindsofhandlers.OneistheEXIThandler,which wejustsaw. Nextwe'lllookattheotherkindofhandler,theCONTINUEhandler.It'ssimilartothe EXIThandler,butafteritexecutes,theplaycontinues.There'snoexitfromthe compoundstatement.

DECLARECONTINUEHANDLERexample(1)
CREATE TABLE t4 (s1 int,primary key(s1));// CREATE PROCEDURE p23 () BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET @x2 = 1; SET @x = 1; INSERT INTO t4 VALUES (1); SET @x = 2; INSERT INTO t4 VALUES (1); SET @x = 3; END;// There'sanexampleofaCONTINUEhandlerintheMySQLReferenceManual,andit's sogoodthatI'lljustcopyithere. Thiswilldemonstratehowacontinuehandlerworks.

Errorhandling(continued)47 DECLARECONTINUEHANDLERexample(2)
CREATE TABLE t4 (s1 int,primary key(s1));// CREATE PROCEDURE p23 () BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET @x2 = 1; SET @x = 1; INSERT INTO t4 VALUES (1); SET @x = 2; INSERT INTO t4 VALUES (1); SET @x = 3; END;//

<--

I'lldefinethehandlerforanSQLSTATEvaluethistime.RememberthatIusedaMySQL errorcodelasttime,erroronetwoonesix.Actually,SQLSTATEtwothreezero zerozeroisalittlemoregeneral.Ithappensforeitheraforeignkeyviolationora primarykeyviolation.

DECLARECONTINUEHANDLERexample(3)
CREATE TABLE t4 (s1 int,primary key(s1));// CREATE PROCEDURE p23 () BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET @x2 = 1; SET @x = 1; INSERT INTO t4 VALUES (1); SET @x = 2; INSERT INTO t4 VALUES (1); SET @x = 3; END;// Thefirststatementofthisprocedurethatgetsexecutedis"SET@x=1".

<--

Errorhandling(continued)48 DECLARECONTINUEHANDLERexample(4)
CREATE TABLE t4 (s1 int,primary key(s1));// CREATE PROCEDURE p23 () BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET @x2 = 1; SET @x = 1; INSERT INTO t4 VALUES (1); SET @x = 2; INSERT INTO t4 VALUES (1); SET @x = 3; END;// Thenthevalueonegetsinsertedintotheprimarykeytable.

<--

DECLARECONTINUEHANDLERexample(5)
CREATE TABLE t4 (s1 int,primary key(s1));// CREATE PROCEDURE p23 () BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET @x2 = 1; SET @x = 1; INSERT INTO t4 VALUES (1); SET @x = 2; INSERT INTO t4 VALUES (1); SET @x = 3; END;// Thenthevalueof@xbecomestwo.

<--

Errorhandling(continued)49 DECLARECONTINUEHANDLERexample(6)
CREATE TABLE t4 (s1 int,primary key(s1));// CREATE PROCEDURE p23 () BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET @x2 = 1; SET @x = 1; INSERT INTO t4 VALUES (1); SET @x = 2; INSERT INTO t4 VALUES (1); SET @x = 3; END;//

<--

Thenanattemptismadetoinsertthesamevalueintotheprimarykeytableagain.Butit fails,becauseprimarykeysmustbeunique.

DECLARECONTINUEHANDLERexample(7)
CREATE TABLE t4 (s1 int,primary key(s1));// CREATE PROCEDURE p23 () BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET @x2 = 1; SET @x = 1; INSERT INTO t4 VALUES (1); SET @x = 2; INSERT INTO t4 VALUES (1); SET @x = 3; END;//

<--

Now,becausetheinsertfails,thehandlertakesover.Thenextstatementthatgetsexecuted isthehandler'sstatement,so@x2getssettoone.

Errorhandling(continued)50 DECLARECONTINUEHANDLERexample(8)
CREATE TABLE t4 (s1 int,primary key(s1));// CREATE PROCEDURE p23 () BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET @x2 = 1; SET @x = 1; INSERT INTO t4 VALUES (1); SET @x = 2; INSERT INTO t4 VALUES (1); SET @x = 3; END;//

<--

Butthat'snottheend.ThisisaCONTINUEhandler.Sonowtheproceduregoesbackto wherewewere,afterthefailedinsertstatement.Anditsets@xtothree.

DECLARECONTINUEHANDLERexample(9)
mysql> CALL p23()// Query OK, 0 rows affected (0.00 sec) mysql> SELECT @x, @x2// +------+------+ | @x | @x2 | +------+------+ | 3 | 1 | +------+------+ 1 row in set (0.00 sec) Runtheprocedure. Herewelookatthevalueof@x.Andsureenough,itisthree. Nextwelookatthevalueof@x2.Andsureenough,itisone. Sothat'sourproofthattheoperationwasprocessedinthewaythatIdescribed. Peoplecantakeabitoftimetoadjusttohandlers.Thecheckisatthestartofthe statementblock,insteadofrightafterthestatementthatmightcausetheerror,soitlooks asifwearejumpingaround.Butit'squiteasafeandcleanwaytocode.

Errorhandling(continued)51 DECLARECONDITION(1)
CREATE PROCEDURE p24 () BEGIN DECLARE `Constraint Violation` CONDITION FOR SQLSTATE '23000'; DECLARE EXIT HANDLER FOR `Constraint Violation` ROLLBACK; START TRANSACTION; INSERT INTO t2 VALUES (1); INSERT INTO t2 VALUES (1); COMMIT; END; // There'sanothervariationontheerrorhandlertheme.Youcan,ineffect,givethe SQLSTATEortheerrorcodeanothername.Andthenyoucanusethatnameinahandler. Let'sseehowthatworks. RecallthatIdefinedtablet2asanInnoDBtable,sotheINSERTsthatImakeintothis tablewillbesubjecttoROLLBACK.AndaROLLBACKispreciselywhatwillhappen. BecauseinsertingthesamevaluetwiceintoaprimarykeywillcauseSQLSTATE'23000' tohappen,andIsayherethatSQLSTATE'23000'isaconstraintviolation.(Theformal syntaxdescriptionisCONDITIONFORSQLSTATE[VALUE]'sqlstatestring'or CONDITIONFORmysqlerrornumbersosomevariationsexist.)

DECLARECONDITION(2)
CREATE PROCEDURE p24 () BEGIN DECLARE `Constraint Violation` CONDITION FOR SQLSTATE '23000'; DECLARE EXIT HANDLER FOR `Constraint Violation` ROLLBACK; START TRANSACTION; INSERT INTO t2 VALUES (1); INSERT INTO t2 VALUES (1); COMMIT; END; //

AndIsayherethataconstraintviolationcausesROLLBACK.

Errorhandling(continued)53 DECLARECONDITION(3)
mysql> CALL p24()// Query OK, 0 rows affected (0.28 sec) mysql> SELECT * FROM t2// Empty set (0.00 sec) Solet'scalltheprocedure.Andlet'sseewhattheresultwas. Yes,weseethatnothinggotINSERTedintotablet2.Theentiretransactionwasrolled back.That'stheresultIwanted.

DECLARECONDITION(4)
mysql> CREATE PROCEDURE p9 () -> BEGIN -> DECLARE EXIT HANDLER FOR NOT FOUND BEGIN END; -> DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN END; -> DECLARE EXIT HANDLER FOR SQLWARNING BEGIN END; -> END;// Query OK, 0 rows affected (0.00 sec) Therearethreepredeclaredconditions:NOTFOUND(nomorerows),SQLEXCEPTION (error),SQLWARNING(warningornote).Sincethey'repredefined,youcanusethem withoutsayingDECLARECONDITION.Indeed,ifyoutriedtosaysomethinglike DECLARESQLEXCEPTIONCONDITION...you'dgetanerrormessage.

CURSORS54
Summaryofthingsthatwecandowithcursors: DECLARE cursor-name CURSOR FOR SELECT ...; OPEN cursor-name; FETCH cursor-name INTO variable [, variable]; CLOSE cursor-name; Nowlet'slookatcursors.Wehaveasomewhatincompleteimplementationofthesyntax forcursorsinsidestoredprocedures.Butwecandothemainthings.Wecandeclarea cursor,wecanopenit,wecanfetchfromit,andwecancloseit.

CursorExample
CREATE PROCEDURE p25 (OUT return_val INT) BEGIN DECLARE a,b INT; DECLARE cur_1 CURSOR FOR SELECT s1 FROM t; DECLARE CONTINUE HANDLER FOR NOT FOUND SET b = 1; OPEN cur_1; REPEAT FETCH cur_1 INTO a; UNTIL b = 1 END REPEAT; CLOSE cur_1; SET return_val = a; END;// Let'swalkthroughanewexamplewithaprocedurethatcontainscursorstatements.

Cursors(continued)55 CursorExample(2)
CREATE PROCEDURE p25 (OUT return_val INT) BEGIN DECLARE a,b INT; DECLARE cur_1 CURSOR FOR SELECT s1 FROM t; DECLARE CONTINUE HANDLER FOR NOT FOUND SET b = 1; OPEN cur_1; REPEAT FETCH cur_1 INTO a; UNTIL b = 1 END REPEAT; CLOSE cur_1; SET return_val = a; END;//

<--

TheprocedurestartswiththreeDECLAREstatements.Incidentally,theorderis important.Firstdeclarevariables.Thendeclareconditions.Thendeclarecursors.Then, declarehandlers.Ifyouputtheminthewrongorder,youwillgetanerrormessage.

CursorExample(3)
CREATE PROCEDURE p25 (OUT return_val INT) BEGIN DECLARE a,b INT; DECLARE cur_1 CURSOR FOR SELECT s1 FROM t; DECLARE CONTINUE HANDLER FOR NOT FOUND SET b = 1; OPEN cur_1; REPEAT FETCH cur_1 INTO a; UNTIL b = 1 END REPEAT; CLOSE cur_1; SET return_val = a; END;//

<--

Wenexthaveadeclarationofacursor.Thislooksalmostexactlylikewhat'sinembedded SQL,foranyonewhohasworkedwiththat.

Cursors(continued)56 CursorExample(4)
CREATE PROCEDURE p25 (OUT return_val INT) BEGIN DECLARE a,b INT; DECLARE cur_1 CURSOR FOR SELECT s1 FROM t; DECLARE CONTINUE HANDLER FOR NOT FOUND SET b = 1; OPEN cur_1; REPEAT FETCH cur_1 INTO a; UNTIL b = 1 END REPEAT; CLOSE cur_1; SET return_val = a; END;//

<-<--

Thefinaldeclaration,asIsaid,isofahandler.Thiscontinuehandlerdoesnotrefertoa MySQLerrornumberortoanSQLSTATEvalue.InsteaditusesNOTFOUND.This happenstobethesameaserroronethreeonesixorSQLSTATE'02000'.

CursorExample(5)
CREATE PROCEDURE p25 (OUT return_val INT) BEGIN DECLARE a,b INT; DECLARE cur_1 CURSOR FOR SELECT s1 FROM t; DECLARE CONTINUE HANDLER FOR NOT FOUND SET b = 1; OPEN cur_1; REPEAT FETCH cur_1 INTO a; UNTIL b = 1 END REPEAT; CLOSE cur_1; SET return_val = a; END;//

<--

ThefirstexecutablestatementisOPENcur_1.Sincecur_1isassociatedwith"SELECT s1FROMt",theprocedurewillSELECTs1FROMt,producingaresultset.

Cursors(continued)57 CursorExample(6)
CREATE PROCEDURE p25 (OUT return_val INT) BEGIN DECLARE a,b INT; DECLARE cur_1 CURSOR FOR SELECT s1 FROM t; DECLARE CONTINUE HANDLER FOR NOT FOUND SET b = 1; OPEN cur_1; REPEAT FETCH cur_1 INTO a; UNTIL b = 1 END REPEAT; CLOSE cur_1; SET return_val = a; END;//

<--

ThefirstFETCHstatementherewillcauseasinglerowtoberetrievedfromtheresultset thattheSELECTproduced.Sinceweknowthatthereareseveralrowsintablet,weknow thatthisstatementwillhappenseveraltimesthatis,itisinaloop.

CursorExample(7)
CREATE PROCEDURE p25 (OUT return_val INT) BEGIN DECLARE a,b INT; DECLARE cur_1 CURSOR FOR SELECT s1 FROM t; DECLARE CONTINUE HANDLER FOR NOT FOUND SET b = 1; OPEN cur_1; REPEAT FETCH cur_1 INTO a; UNTIL b = 1 END REPEAT; CLOSE cur_1; SET return_val = a; END;//

<--

Eventually,MySQLwillhavenomorerowstofetch.Atthatpoint,wewillreachthe statementthat'sintheCONTINUEhandler.Thatis,setvariablebtoone.

Cursors(continued)58 CursorExample(8)
CREATE PROCEDURE p25 (OUT return_val INT) BEGIN DECLARE a,b INT; DECLARE cur_1 CURSOR FOR SELECT s1 FROM t; DECLARE CONTINUE HANDLER FOR NOT FOUND SET b = 1; OPEN cur_1; REPEAT FETCH cur_1 INTO a; UNTIL b = 1 END REPEAT; CLOSE cur_1; SET return_val = a; END;// ThereforetheconditionUNTILb=1isnowtrue.Thereforetheloopends.SonowI closethecursor.IfIdidn't,itwouldbeclosedautomaticallywhenthecompound statementends.ButIthinkdependingonautomaticbehaviourlooksatadsloppy.

<--

CursorExample(9)
CREATE PROCEDURE p25 (OUT return_val INT) BEGIN DECLARE a,b INT; DECLARE cur_1 CURSOR FOR SELECT s1 FROM t; DECLARE CONTINUE HANDLER FOR NOT FOUND SET b = 1; OPEN cur_1; REPEAT FETCH cur_1 INTO a; UNTIL b = 1 END REPEAT; CLOSE cur_1; SET return_val = a; END;// Nowwe'llassignourlocalvariabletoanoutputparameter,sothattheresultswillbe visibleaftertheprocedurefinishes.

<--

Cursors(continued)59 CursorExample(10)
CREATE PROCEDURE p25 (OUT return_val INT) BEGIN DECLARE a,b INT; DECLARE cur_1 CURSOR FOR SELECT s1 FROM t; DECLARE CONTINUE HANDLER FOR NOT FOUND SET b = 1; OPEN cur_1; REPEAT FETCH cur_1 INTO a; UNTIL b = 1 END REPEAT; CLOSE cur_1; SET return_val = a; END;// mysql> CALL p25(@return_val)// Query OK, 0 rows affected (0.00 sec) mysql> SELECT @return_val// +-------------+ | @return_val | +-------------+ | 5 | +-------------+ 1 row in set (0.00 sec) Let'scallthisprocedure. Hereyouseethatthereturn_valparametergotavalueof5,whichisright,becausethatis whatisinthelastrowoftablet.Soweknowthatthecursorworked,andweknowthat thehandlerworked.

Cursors(continued)60 CursorCharacteristics
Summary: READ ONLY NOT SCROLLABLE ASENSITIVE InversionfiveofMySQL,youcanonlyfetchfromcursors,youcannotupdatethem.That is,cursorsareREADONLY.Youcannotsay: FETCH cursor1 INTO variable1; UPDATE t1 SET column1 = 'value1' WHERE CURRENT OF cursor1; CursorsarealsoNOTSCROLLABLE.Thatis,youcanonlyfetchthenextrow,you cannotmovebackandforthintheresultset.Youcannotsay: FETCH PRIOR cursor1 INTO variable1; FETCH ABSOLUTE 55 cursor1 INTO variable1; Andyoushouldavoiddoingupdatesonatablewhileyouhaveacursoropenonthesame table,becausecursorsareASENSITIVE.Thatis,ifyoudon'tavoiddoingupdates,we don'tguaranteewhattheresultswillbe.Iknowthattheycanbedifferentifyouusethe InnoDBstorageengineinsteadoftheMyISAMstorageengine.

Security61
Privileges (1) CREATE ROUTINE Privileges (2) EXECUTE Privileges (3) INVOKERS AND DEFINERS I'mgoingtosayafewthingsaboutprivilegesandsecuritynow.Butthissectionwillhave tobeshort,sincethere'snotmuchtoit.WejustusefamiliarGRANTsandREVOKEs.

Privileges(1)CREATEROUTINE
GRANT CREATE ROUTINE ON database-name . * TO user(s) [WITH GRANT OPTION]; * But right now, just use 'root' Thereisaprivilegeforcreatingproceduresorfunctions,justasthereisaprivilegefor creatingviewsortables.It'stheCREATEROUTINEprivilege.Therootuserhasit.

Privileges(2)ALTERROUTINE
GRANT ALTER ROUTINE { ON PROCEDURE procedure-name } | { ON database-name . *} TO user(s) [WITH GRANT OPTION]; ThereisalsoanALTERROUTINEprivilege.

Privileges(3)EXECUTE
GRANT EXECUTE { ON PROCEDURE procedure-name } | { ON database-name . *} TO user(s) [WITH GRANT OPTION]; Alsothereisaspecialprivilegethatsayswhetheryouareallowedtouse,orexecute,a procedure. Aprocedure'screatorhasALTERROUTINEandEXECUTEprivilegesautomatically.

Security(continued)62 Privileges(4)InvokersandDefiners
CREATE PROCEDURE p26 () SQL SECURITY INVOKER SELECT COUNT(*) FROM t // CREATE PROCEDURE p27 () SQL SECURITY DEFINER SELECT COUNT(*) FROM t // GRANT EXECUTE ON db5.* TO peter; // Nowlet'stryouttheSQLSECURITYclause.Securityisoneofthecharacteristicsofa procedurethatImentionedatthestart. Youare'root'.Grantexecuterightsonyourdatabase(db5)topeter.Thenstartupanew jobaspeter,andlet'sseewhatpetercandowithstoredprocedures. Notice:peterdoesnothavetherighttoselectfromt.Onlyroothastherighttoselect fromt.

Privileges(5)InvokersandDefiners
/* Logged on with current_user = peter */ mysql> CALL p26(); ERROR 1142 (42000): select command denied to user 'peter'@'localhost' for table 't' mysql> CALL p27(); +----------+ | COUNT(*) | +----------+ | 1 | +----------+ 1 row in set (0.00 sec) Sowhenpetertriestocallprocedurep26,theonewithINVOKERsecurity,hefails. That'sbecausepetercan'tselectfromthetable.However,whenPetertriestocall procedurep27,theonewithDEFINERsecurity,hesucceeds.That'sbecauserootcan selectfromthetable,andPeterhasroot'srightsaslongastheprocedureisbeing executed.

Privileges(6)InvokersandDefiners63
Hmm,Ihadbetterqualifythatlastremark.IsaidPeterhasroot'srightsaslongasthe procedureisbeingexecuted.ButitwouldbebettertosaythatPeterimpersonatesroot oractsasroot.YoucanseethisbyselectingCURRENT_USERinsideandoutsidean SQLSECURITYDEFINERprocedure. /* Logged on with current_user = root */ mysql> CREATE PROCEDURE p27a () /* DEFINER is default */ -> SELECT CURRENT_USER // Query OK, 0 rows affected (0.00 sec) /* Logged on with current_user = peter */ mysql> SELECT CURRENT_USER; +-----------------+ | CURRENT_USER | +-----------------+ | peter@localhost | +-----------------+ 1 row in set (0.00 sec) mysql> CALL p27a(); +----------------+ | CURRENT_USER | +----------------+ | root@localhost | +----------------+ 1 row in set (0.01 sec)

DynamicPREPAREandEXECUTE64
Thisisarelativelynewfeature.YoucanmanipulateastringwhichcontainsanSQL statement,thenyoucanPREPAREandEXECUTEthatstatement.Thisonthefly generationiscalledDynamicSQLinsometexts. mysql> CREATE PROCEDURE p30 (search_value INT) -> BEGIN -> SET @sql = CONCAT( -> 'SELECT * FROM t WHERE s1 = ',search_value); -> PREPARE stmt1 FROM @sql; -> EXECUTE stmt1; -> END; // Query OK, 0 rows affected (0.01 sec) mysql> CALL p30(0)// +------+ | s1 | +------+ | 0 | +------+ 1 row in set (0.00 sec) TheexampleshowshowflexibleourdynamicSQLis.Buildastring,prepareit,execute it.Tada. Intheory,youcouldbuildanystatementausertypesin,oranystatementthatan algorithmcangenerate.Thelimitationsare: Somestatementsarenotpreparable.(ThenumberisdecreasingrapidlysoIwon'tlist themhere.) YouloseabitofspeedbecauseMySQLhastoparsethestatementsyoucreate. It'shardertotestanddebugastoredprocedurewhichcanchangedrastically. Youcan'tuseadeclaredvariableforthestatement,youhavetouseasession @variable.Sideeffectscanbeunfortunateforrecursiveprocedures(procedureswhich callthemselves),orfortwodifferentprocedureswhichbychanceusethesame variablenames.

Functions65
Summary: CREATEFUNCTION Limitationsoffunctions You'venowseenprettywelleverythingthatcanbeinastoredprocedure.Whatyou haven'tseen,whatI'veleftunmentioneduptillnow,iswhatastoredFUNCTIONlooks like.Soit'sfunctiontime.

(1)CREATEFUNCTION
CREATE FUNCTION factorial (n DECIMAL(3,0)) RETURNS DECIMAL(20,0) DETERMINISTIC BEGIN DECLARE factorial DECIMAL(20,0) DEFAULT 1; DECLARE counter DECIMAL(3,0); SET counter = n; factorial_loop: REPEAT SET factorial = factorial * counter; SET counter = counter - 1; UNTIL counter = 1 END REPEAT; RETURN factorial; END // (Source:UnderstandingSQL'sstoredprocedures.Usedforexamplepurposesonly.) Functionslookalotlikeprocedures.Theonlynoticeablesyntaxdifferencesarethatwe saycreatefunctioninsteadofcreateprocedure,andweneedaRETURNSclausetosay whatthedatatypeofthefunctionis,andweneedaRETURNstatementbecausea functionmustreturnavalue. ThisexamplecomescourtesyofabookbyJimMelton,amemberoftheSQLstandard committee.Thebookiscalled"UnderstandingSQL'sstoredprocedures".Thisexampleis onpage223ofthatbook.IdecidedtouseitbecauseMySQLusesstandardSQL,andso theexampleinthebookworksoutofthebox.Ionlyhadtoclearuptwotypos.

Functions(continued)66 (2)Examples
INSERT INTO t VALUES (factorial(pi())) // SELECT s1, factorial (s1) FROM t // UPDATE t SET s1 = factorial(s1) WHERE factorial(s1) < 5 // NowthatI'vegotmyfunction,IcanjustplopitintoanySQLstatement,anditwilllook likeanyotherfunction. Toputitmildly,thisisbeautiful.NowIcanexpandhugelythesetofbuiltinfunctions thatcomewithMySQL.ThenumberofthingsIcandowiththisislimitedonlybymy imagination.And,regrettably,limitedbecausethereafewlittlethingsMySQLwon't allow.

(3):Limitations
Somestatementscannotbeinstoredprocedures,andotherswon'trun. ThesestatementswillcauseanotallowedmessageatCREATEtime: ALTER ANALYZE BACKUP 'CACHE INDEX' CHECKSUM COMMIT CREATE DEALLOCATE DROP EXECUTE EXPLAIN 'FLUSH PRIVILEGES' LOAD LOCK OPTIMIZE PREPARE RENAME REPAIR RESTORE ROLLBACK 'SELECT [without INTO]' SHOW 'START TRANSACTION' TRUNCATE USE ThesestatementswillcauseanotlockedmessageatCALLtime: GRANT REVOKE Ingeneral,MySQLtriestostopfunctionsfromdoingcommits(explicitlyorimplicitly), orfromreturningresultsets.

(4):Limitations67
Ifyourefertoatablefrombothinsideandoutsideafunction,yougetanerror.Example: mysql> CREATE FUNCTION f3 () -> RETURNS INT -> BEGIN -> INSERT INTO t VALUES (0); -> RETURN 5; -> END; // Query OK, 0 rows affected (0.01 sec) mysql> UPDATE t SET s1 = f3()// ERROR 1442 (HY000): Can't update table 't' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.

(4):NonLimitations
Legal: 'BEGIN END' CLOSE DECLARE DELETE FETCH HANDLER IF INSERT ITERATE LEAVE LOOP OPEN REPEAT REPLACE RETURN 'SET declared variable' UPDATE WHILE Youcansetvariables,andusethemwiththenewflowofcontrolstatements.Youcanuse cursors.Youcanusemostdatamanipulationstatements.Thisisactuallyquitepowerful, andisaboutwhatreasonablepeopleexpect.

Metadata68
Summary: SHOWCREATEPROCEDURE/SHOWCREATEFUNCTION SHOWPROCEDURESTATUS/SHOWFUNCTIONSTATUS SELECTfrommysql.proc SELECTfrominformation_schema Atthispoint,we'vemademanyprocedures.They'restoredinaMySQLdatabase.Let's lookattheinformationMySQLhasactuallystored.Therearecurrentlyfourwaystodo this.TwooftheminvolveSHOWstatements.TwooftheminvolveSELECTstatements.

(1)SHOW
mysql> show create procedure p6// +-----------+----------+-------------------+ | Procedure | sql_mode | Create Procedure | +-----------+----------+-------------------+ | p6 | | CREATE PROCEDURE | | | | `db5`.`p6`(out p | | | | int) set p = -5 | +-----------+----------+-------------------+ 1 row in set (0.00 sec) ThefirstwaytogetthemetadatainformationistoexecuteSHOWCREATE PROCEDURE.It'slikeSHOWCREATETABLEandothersimilarMySQLstatements. Itdoesn'treturnexactlywhatyoutypedinwhenyoumadetheprocedure,butit'sclose enoughformostpurposes.

(2)SHOW
mysql> SHOW PROCEDURE STATUS LIKE 'p6'// +------+------+-----------+----------------+ | Db | Name | Type | Definer | ... +------+------+-----------+----------------+ | db5 | p6 | PROCEDURE | root@localhost | ... +------+------+-----------+----------------+ 1 row in set (0.01 sec) ThesecondwaytogetmetadatainformationistoexecuteSHOWPROCEDURE STATUS.Thisgivesyouabitmoredetail.

Metadata(continued)69 (3)SELECTfrommysql.proc
SELECT * FROM mysql.proc WHERE name = 'p6'// +------+------+-----------+---------------+ | db | name | type | specific_name | ... +------+------+-----------+---------------+ | db5 | p6 | PROCEDURE | p6 | ... +------+------+-----------+---------------+ 1 row in set (0.00 sec) ThethirdwaytogetmetadatainformationistoexecuteaSELECTstatement.This methodprovidesthemostdetail,andit'sdonewithSELECTinsteadofwithSHOW.

(4)SELECTfrominformation_schema:MyFavourite
ThefourthwaytogetmetadataiswithSELECT...FROMinformation_schema..... IhaveastrongbiastowardsdoingthingstheANSI/ISOstandardway,soIbelievethat thisistheonlygoodwaytheotherwaysaredesignerrors.Ofcourse,youmighthave guessedthatthereareotherMySQLpeoplewhostronglyholdotheropinions.Soyoucan takethesereasonsseriously,orskeptically: 1. OtherDBMSs,e.g.SQLServer2005,useinformation_schema.MySQL'sSHOWis proprietary. 2. Thereisnoguaranteethatyouhaveprivilegestoaccessmysql.proc,whilethereisa guaranteethatyoucanaccessinformation_schemaviews.Everybodyalwayshasan implicitSELECTprivilegeonalltablesintheinformation_schemadatabase. 3. SELECThasamultitudeofoptionslikegettingexpressions,grouping,ordering,and ofcourseproducingaresultsetthatyoucanFETCHfrom.SHOWhasnosuchthings. Well,nowthatyouknowthatIlikeit,youare(Ihope)asking:okay,sowhatisit? Asusual,I'lltrytoanswerthatwithafewexamples.

Metadata(continued)70
First,I'lluseaninformation_schemaSELECTtoshowwhatcolumnsarein information_schema.routines. mysql> SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE FROM INFORMATION_SCHEMA.COLUMNS -> WHERE TABLE_NAME = 'ROUTINES';// +------------+--------------------+---------------+ | TABLE_NAME | COLUMN_NAME | COLUMN_TYPE | +------------+--------------------+---------------+ | ROUTINES | SPECIFIC_NAME | varchar(64) | | ROUTINES | ROUTINE_CATALOG | longtext | | ROUTINES | ROUTINE_SCHEMA | varchar(64) | | ROUTINES | ROUTINE_NAME | varchar(64) | | ROUTINES | ROUTINE_TYPE | varchar(9) | | ROUTINES | DTD_IDENTIFIER | varchar(64) | | ROUTINES | ROUTINE_BODY | varchar(8) | | ROUTINES | ROUTINE_DEFINITION | longtext | | ROUTINES | EXTERNAL_NAME | varchar(64) | | ROUTINES | EXTERNAL_LANGUAGE | varchar(64) | | ROUTINES | PARAMETER_STYLE | varchar(8) | | ROUTINES | IS_DETERMINISTIC | varchar(3) | | ROUTINES | SQL_DATA_ACCESS | varchar(64) | | ROUTINES | SQL_PATH | varchar(64) | | ROUTINES | SECURITY_TYPE | varchar(7) | | ROUTINES | CREATED | varbinary(19) | | ROUTINES | LAST_ALTERED | varbinary(19) | | ROUTINES | SQL_MODE | longtext | | ROUTINES | ROUTINE_COMMENT | varchar(64) | | ROUTINES | DEFINER | varchar(77) | +------------+--------------------+---------------+ 20 rows in set (0.01 sec) Nifty,eh?WheneverIwanttoknowaboutaninformation_schemaview,Iselectfrom anotherinformation_schemaviewsuchasTABLESorCOLUMNS.It'smetadataofthe metadata.

Metadata(continued)71
Soherewe'llseehowmanyproceduresIhavedefinedindatabasedb5: mysql> SELECT COUNT(*) FROM INFORMATION_SCHEMA.ROUTINES -> WHERE ROUTINE_SCHEMA = 'db5';// +----------+ | COUNT(*) | +----------+ | 28 | +----------+ 1 row in set (0.02 sec) Andnowwe'llhaveacloserlookatprocedure'p1'whichwasthefirstprocedureImade.I reformattedthemysqlclientdisplay,buttheinformationiswhatmysqlsays. | SPECIFIC_NAME | ROUTINE_CATALOG | ROUTINE_SCHEMA +---------------+-----------------+-------------- p19 | NULL | p19 | ROUTINE_NAME | ROUTINE_TYPE | DTD_IDENTIFIER +---------------+-----------------+--------------| p19 | PROCEDURE | NULL | ROUTINE_BODY | ROUTINE_DEFINITION | EXTERNAL_NAME +--------------+--------------------+-------------| SQL | select * from t | NULL | EXTERNAL_LANGUAGE | PARAMETER_STYLE | IS_DETERMINISTIC |-------------------+-----------------+----------------| NULL | SQL | NO | SQL_DATA_ACCESS | SQL_PATH | SECURITY_TYPE | CREATED +-----------------+----------+---------------+-------| CONTAINS SQL | NULL | DEFINER | 2006-06-01 | CREATED | LAST_ALTERED | SQL_MODE +---------------------+---------------------+--------| 2006-06-01 15:00:26 | 2006-06-01 15:00:26 | | ROUTINE_COMMENT | DEFINER +-----------------+--------------| | root@localhost

Metadata(continued)72 AccesscontrolfortheROUTINE_DEFINITIONcolumn
TheROUTINE_DEFINITIONcolumninINFORMATION_SCHEMA.ROUTINEShas thebodyoftheprocedureorfunction,forexampleselect*fromt.Thatmightbe sensitiveinformation,sothecolumnvalueisinvisibletoeveryonebutthecreator. Thatis:ifthepersondoingtheSELECTisn'tthesameasthethepersonwhocreatedif CURRENT_USER<>INFORMATION_SCHEMA.ROUTINES.DEFINERthen MySQLreturnsablankfortheROUTINE_DEFINITIONcolumn.

AdditionalclauseinSHOWPROCEDURESTATUS
NowthatI'velistedwhatthecolumnsareinINFORMATION_SCHEMA.ROUTINES,I cangobackandexplainanewdetailaboutSHOWPROCEDURESTATUS.Syntax: SHOW PROCEDURE STATUS [WHERE condition]; TheconditionworkslikeaconditioninaSELECTstatement:ifit'strue,therowappears intheoutputofSHOWPROCEDURESTATUS.Buthere'stheweirdpart:inthe WHEREclauseyoumustuseINFORMATION_SCHEMAcolumnnames,butinthe displayyou'llseeSHOWPROCEDURESTATUSfieldnames.Forexample: mysql> SHOW PROCEDURE STATUS WHERE Db = 'db6';// Empty set (0.03 sec) mysql> SHOW PROCEDURE STATUS WHERE ROUTINE_NAME = 'p1';// +------+------+-----------+----------------+ | Db | Name | Type | Definer | ... +------+------+-----------+----------------+ | db5 | p | PROCEDURE | root@localhost | ... +------+------+-----------+----------------+ 1 row in set (0.00 sec) Perhaps,knowingthis,IcanscorehigherontheMySQLCertificationExam.Iwould neverconsiderputtingthisfeaturetopracticaluse,though.

Details73
Summaryofnextpages: ALTERandDROP Recursive Definer Oracle/SQLServer/DB2/ANSIcomparison Style Bugs FeatureRequests Resources AndnowI'vecrammedalotofdetailsattheend.Wewon'tlookatallofthesewiththe degreeofattentionthattheydeserve,butthat'sokay,trialanderrorwillrevealtheir secrets.

ALTERandDROP
ALTER PROCEDURE p6 COMMENT 'Unfinished' // DROP PROCEDURE p6 //

DEFINER
CREATE DEFINER=user_name PROCEDURE p31 () BEGIN END; //

Recursive
Suppose,insideprocedurep1,wehaveastatementCALLp1();.Thatis,theprocedure callsitself.Thisiscalledrecursion.MySQLlimitsrecursionsothaterrorswillbeless severe.Sobeforeusingrecursion,checkandadjustthelimit. mysql> SHOW VARIABLES LIKE '%recur%'; +------------------------+-------+ | Variable_name | Value | +------------------------+-------+ | max_sp_recursion_depth | 0 | +------------------------+-------+ 1 row in set (0.00 sec) mysql> SET @@max_sp_recursion_depth = 100; Query OK, 0 rows affected (0.00 sec)

OracleComparison74
Summary: OracleallowsDECLAREafterOPEN MySQLrequiresDECLAREbeatthestart

OracleallowsCURSORcursornameIS MySQLrequiresDECLAREcursornameCURSOR Oracledoesn'trequire'()' MySQLrequires'()' Oracleallowsaccessingthesametablesinandoutoffunctions MySQLdoesnotallowaccessingthesametablesinandoutoffunctions Oraclesupportspackages. MySQLdoesnotsupportpackages.

IfyouhaveusedOracle'sPL/SQLstoredproceduresinthepast,you'llhavenoticedthat therearesomedifferencesbetweenOracle'sandMySQL'sstoredprocedures.Thereare, infact,quiteafewdifferences.I'veonlynotedthecommonones. Seealso:http://www.mysql.com/products/tools/migrationtoolkit/and http://www.ispirer.com.ThesearewebsitesthatdescribetoolsforconvertingOracle storedprocedurestoMySQLstoredprocedures.Idonotsaythattheseproductsaregood. Ihavenottriedthem.Ijustthoughtitisinterestingthatsomebodyhasalreadymadea packageformigratingstoredproceduresfromotherDBMSstoMySQL. Tipsformigration... Changeassignmentstatementslikea:=btoSETa=b. ChangeRETURNstatementsinsideprocedureswithLEAVElabel_at_startwhere label_at_startisthefirsttokeninthestoredprocedure.Forexample: [Oracleprocedure] CREATE PROCEDURE ... RETURN; ... [MySQLprocedure] CREATE PROCEDURE () label_at_start: BEGIN ... LEAVE label_at_start; END Thisisonlynecessarywithprocedures,sinceMySQLsupportsRETURNinafunction.

OracleComparison(continued)75 SideBySide
Oracle CREATE PROCEDURE sp_name AS variable1 INTEGER MySQL CREATE PROCEDURE sp_name

variable1 := 55 END

BEGIN DECLARE variable1 INTEGER; SET variable1 = 55; END

SQLServerComparison76
Summary: SQLServerparameternamesbeginwith'@' MySQLparameternamesareregularidentifiers SQLServercansay"DECLAREv1datatype,v2data_type" MySQLcanonlysayDECLAREv1data_type;DECLAREv2data_type

SQLServerdoesn'thaveBEGIN...ENDfortheprocedurebody MySQLrequiresBEGIN...ENDtosurroundmultiplestatements SQLServerdoesn'trequirethatstatementsendwith';' MySQLrequiresthatstatementsendwith';',exceptlaststatement SQLServerhas"SETNOCOUNT"and"IF@@ROWCOUNT" SQLServerhasWHILE...BEGINstatements MySQLhasWHILE...DOstatements SQLServerallowsSELECTforassignment MySQLusesSETforassignment,exclusively "WHILE...BEGIN"not"WHILE...DO" SELECTinsteadofSET(optionally) SQLServerallowsaccessingthesametabletableinandoutoffunctions MySQLdoesnotallowallowaccessingthesametableinandoutoffunctions

MySQLdoesnothavethosethings,butFOUND_ROWS()works

WithMicrosoftSQLServer,thelistofdifferencesisconsiderablylonger.Converting MicrosoftorSybaseprogramstoMySQLisgoingtobetedious.Butthedifferencesare mostlysyntax,itrequiresnospecialintelligencetomakethechanges. Somemigrationtips... IfSQLServerhasavariablenamed@xxx,youhavetochangeitbecauseinMySQLa variablethatstartswith@isnotastoredprocedurevariable,it'sasessionvariable.Don't justchangeittoxxx,becausethenitmightbecomeambiguousforexample,theremight beacolumnnamedxxxinoneofthedatabasetables.Useaconventionalprefixofyour ownchoice,forexamplechange@xxxtovar_xxx.

SQLServercomparison(continued)77 SideBySide
SQL Server CREATE PROCEDURE sp_procedure1 AS MySQL CREATE PROCEDURE sp_procedure1 () BEGIN DECLARE v__x VARCHAR(100); CALL sp_procedure2(v__x); DECLARE c CURSOR FOR SELECT * FROM t; END

DECLARE @x VARCHAR(100) EXECUTE sp_procedure2 @x DECLARE c CURSOR FOR SELECT * FROM t END

DB2Comparison78

DB2allowsPATHstatement MySQLdoesnotallowPATHstatements DB2allowsSIGNALstatement MySQLdoesnotallowSIGNALstatement DB2allowsoverloadingofroutinenamessoithasSPECIFICNAMEclause MySQLdoesnotallowoverloadingofroutinenames DB2haslabel_x:...GOTOlabel_xsyntax MySQLdoesnothavelabellabel_x;...GOTOlabel_xsyntax(it'sgone)

DB2allowsaccessingthesametableinandoutofafunction MySQLdoesnotallowaccessingthesametableinandoutofafunction DB2storedprocedureslookalmostexactlythesameasMySQLstoredprocedures.The onlythingsthatyouhavetowatchforareafewstatementsthatwehavenotimplemented yet,andalsothefactthatDB2allowsoverloading.Thatis,withDB2youcanhavetwo routineswiththesamename,andDB2distinguishesbetweenthembylookingattheir parameters,orbyusingaspecificnameclause.DB2storedproceduresareupward compatiblewithMySQLstoredprocedures. Migrationtips:ifyou'regoingtomigrate,youneedhardlyanytipsatall.ForMySQL's lackofaSIGNALstatement,weelsewherediscussatemporaryworkaround.ForDB2's GOTO,weusedtohaveasimilarMySQLGOTOtomakethemigrationeasier,butwe decidedit'sinappropriate.ForPATH(whichdetermineswheretheDBMSlooksforthe routineinthedatabasecatalog),justavoidtheproblembyaddingaprefixtotheroutine name.Forthedifficultywithaccessingtableswithinfunctions,youcanoftenwrite proceduresinsteadandlettheOUTparameteroftheproceduresubstituteforafunction result.

DB2comparison(continued)79 SideBySide
DB2 CREATE PROCEDURE sp_name (parameter1 INTEGER) LANGUAGE SQL BEGIN DECLARE v INTEGER; IF parameter1 >=5 THEN CALL p26(); SET v = 2; END IF; INSERT INTO t VALUES (v); END @ MySQL CREATE PROCEDURE sp_name (parameter1 INTEGER) LANGUAGE SQL BEGIN DECLARE v INTEGER; IF parameter1 >=5 THEN CALL p26(); SET v = 2; END IF; INSERT INTO t VALUES (v); END //

StandardComparison80
Summary: StandardSQLrequires: [samethingsasinDB2] MySQL'sgoalistosupportthesetwostandardSQLfeatures: FeatureP001"StoredModules" FeatureP002"Computationalcompleteness" ThereasonthatDB2issomuchlikeMySQListhatbothDBMSssupportstored proceduresasdefinedbytheSQLStandard.SoMySQL'sdifferenceswithDB2arepretty wellthesameasourdeparturesfromANSI/ISOstandardsyntax.Butwearemuch,much closertothestandardthanOracleorSQLServer. MySQL'sgoalistosupportthesetwofeaturesofstandardSQL:FeatureP001"Stored Modules",FeatureP002"Computationalcompleteness".

Style81
CREATE PROCEDURE p () BEGIN /* Use comments! */ UPDATE t SET s1 = 5; CREATE TRIGGER t2_ai ... END;// InmyexamplesI'veusedacertainstyletowritemystoredprocedures. Keywordsareinuppercase. Fornamingconventions,Ibelieveit'sbetterinamanualtosaythatatableist somethingandacolumnisssomething.Butinreallife,withmyDBAhaton,Itryto gowiththeguidelineshintedatinthearticleSQLNamingConventions: http://dbazine.com/gulutzan5.shtml CommentsalwayslooklikeCcomments.Don'tuse#commentorcomment.Incidentally commentsarenotpreservedinthemetadata(themysqlclientstripsthem),butyoucan insertremarksbyaddingastringinanonexecutablestatement,suchasDECLARE. IindentafterthewordBEGIN. IindentbackbeforethewordEND.ActuallyIdislikethisdetail.I'dprefertosay: CREATE PROCEDURE p () BEGIN /* Use comments! */ UPDATE t SET s1 = 5; CREATE TRIGGER t2_ai ... END;// <-Thatis,theindentingshouldendafterEND,notbeforeit.ButItrytocurbmyabnormal impulses. Idon'tsuggestthatyoufollowmystyle,butIdosuggestthatyouchooseastyleofyour own,andsticktoitwhenwritingyourownstoredprocedures.Itmakesthecodesomuch easiertofollow.

StoredProcedureExample:tables_concat()82
Thisisaprocedurethatlistsallthetablenamesinasinglestring.ComparetheMySQL builtinfunctiongroup_concat(). CREATE PROCEDURE tables_concat (OUT parameter1 VARCHAR(1000)) BEGIN DECLARE variable2 CHAR(100); DECLARE c CURSOR FOR SELECT table_name FROM information_schema.tables; DECLARE EXIT HANDLER FOR NOT FOUND BEGIN END; /* 1 */ SET sql_mode='ansi'; /* 2 */ SET parameter1 = ''; OPEN c; LOOP FETCH c INTO variable2; /* 3 */ SET parameter1 = parameter1 || variable2 || '.'; END LOOP; CLOSE c; END; /*1*/:BEGINENDisastatementthatdoesnothingatall,liketheNULLstatementin otherDBMSlanguages. /*2*/:Changingthesql_modesettingwithintheproceduresothatconcatenationwith|| willwork.Thesettingwillstillbe='ansi'afterexitfromthestoredprocedure. /*3*/:AnotherwaytogetoutofaLOOP:bydeclaringanEXIThandlerthatwill eventuallycatchthefailureofaFETCHwhentherearenomorerows. Here'swhathappensifwecallthisprocedurelater: mysql> CALL tables_concat(@x); Query OK, 0 rows affected (0.05 sec) mysql> SELECT @x; +----------------------------------------------------+ | SCHEMATA.TABLES.COLUMNS.CHARACTER_SETS.COLLATIONS.C OLLATION_CHARACTER_SET_APPLICABILITY.ROUTINES.STATIST ICS.VIEWS.USER_PRIVILEGES.SCHEMA_PRIVILEGES.TABLE_PRI VILEGES.COLUMN_PRIVILEGES.TABLE_CONSTRAINTS.KEY_COLUM N_USAGE.TABLE_NAMES.columns_priv.db.fn.func.help_cate gory.help_keyword.help_relation.help_topic.host.proc. tables_priv.time_zone.time_zone_leap_second.time_zone 1 row in set (0.00 sec)

FunctionExample:rno()83
Theobjectiveistogetarownumberwithinresultsetinteger,similartothe ROWNUM()inotherDBMSs. Weneedauservariabletostorethevalueaftereachinvocationofrno().We'llcallthe variable@rno. CREATE FUNCTION rno () RETURNS INT BEGIN SET @rno = @rno + 1; RETURN @rno; END; Wegettherownumberbyjustusingrno()inaSELECT.Here'swhathappensifwecall theprocedurelater: mysql> SET @rno = 0;// Query OK, 0 rows affected (0.00 sec) mysql> SELECT rno(),s1,s2 FROM t;// +-------+------+------+ | rno() | s1 | s2 | +-------+------+------+ | 1 | 1 | a | | 2 | 2 | b | | 3 | 3 | c | | 4 | 4 | d | | 5 | 5 | e | +-------+------+------+ 5 rows in set (0.00 sec) Here'satrickthatresets@rnotozerowithintheSELECT.Itdependsonthefactthat WHEREisevaluatedfirst.ItisslowanddependsonbehaviourthatMySQLwon't guaranteewillworkinfutureversions. CREATE FUNCTION rno_reset () RETURNS INTEGER BEGIN SET @rno = 0; RETURN 1; END; SELECT rno(),s1,s2 FROM t WHERE rno_reset()=1;//

FunctionExample:running_total()84
Thisaccumulatorfunctionbuildsonwhatrno()does.Theonlynoveltyisthatwehaveto passavalueintheparameterinordertoaddtotherunningtotalwitheachinvocation. CREATE FUNCTION running_total (IN adder INT) RETURNS INT BEGIN SET @running_total = @running_total + adder; RETURN @running_total; END; Here'swhathappensifweusetherunning_total()functionlater: mysql> SET @running_total = 0;// Query OK, 0 rows affected (0.01 sec) mysql> SELECT s1,running_total(s1),s2 FROM t ORDER BY s1;// +------+-------------------+------+ | s1 | running_total(s1) | s2 | +------+-------------------+------+ | 1 | 1 | a | | 2 | 3 | b | | 3 | 6 | c | | 4 | 10 | d | | 5 | 15 | e | +------+-------------------+------+ 5 rows in set (0.01 sec) Therunning_total()functionworksbecauseit'scalledafterORDERBYisdone.Thisis notstandardorportable.

ProcedureExample:MyISAMForeignKeyinsertion85
TheMyISAMstorageenginedoesn'tsupportforeignkeys,butyoucanputthelogicfor foreignkeycheckinginastoredprocedure. CREATE PROCEDURE fk_insert (p_fk INT, p_animal VARCHAR(10)) BEGIN DECLARE v INT; BEGIN DECLARE EXIT HANDLER FOR SQLEXCEPTION, NOT FOUND SET v = 0; IF p_fk IS NOT NULL THEN SELECT 1 INTO v FROM tpk WHERE cpk = p_fk LIMIT 1; INSERT INTO tfk VALUES (p_fk, p_animal); ELSE SET v = 1; END IF; END; IF v <> 1 THEN DROP TABLE `The insertion failed`; END IF; END; Notes:EitheranSQLEXCEPTIONoraNOTFOUNDconditionwillcausevtobezero, butotherwisevwillbeone,becausetheSELECTwillputoneintoitandtheEXIT HANDLERwon'tsetitbacktozero.Andhere'swhathappenswhenwecall... mysql> CREATE TABLE tpk (cpk INT PRIMARY KEY);// Query OK, 0 rows affected (0.01 sec) mysql> CREATE TABLE tfk (cfk INT, canimal VARCHAR(10));// Query OK, 0 rows affected (0.00 sec) mysql> INSERT INTO tpk VALUES (1),(7),(10);// Query OK, 3 rows affected (0.01 sec) Records: 3 Duplicates: 0 Warnings: 0 mysql> CALL fk_insert(1,'wombat');// Query OK, 1 row affected (0.02 sec) mysql> CALL fk_insert(NULL,'wallaby');// Query OK, 0 rows affected (0.00 sec) mysql> CALL fk_insert(17,'wendigo');// ERROR 1051 (42S02): Unknown table 'The insertion failed'

ProcedureExample:ErrorPropagation86
Supposethatprocedure1callsprocedure2callsprocedure3.AnSQLerrorinprocedure3 propagates,thatis,unlesssomehandlercatchesit,itkeepsgoinguptheline,causing procedure2tofail,whichcausesprocedure1tofail,andultimatelythecaller(themysql clientinthiscase)seestheerror.SometimesthispropertyisdesirablesostandardSQL hasaSIGNALstatementtoforceerrorstohappen,andotherDBMSshavesomething similar(RAISERROR).MySQLdoesn'tsupportSIGNALyet.Untilitdoes,forceerrors bydoingsomethingharmlessbuterroneous. CREATE PROCEDURE procedure1 () BEGIN CALL procedure2(); SET @x = 1; END; CREATE PROCEDURE procedure2 () BEGIN CALL procedure3(); SET @x = 2; END; CREATE PROCEDURE procedure3 () BEGIN DROP TABLE error.`error #7815`; SET @x = 3; END; Here'swhathappensifwecallprocedure1later: mysql> CALL procedure1()// ERROR 1051 (42S02): Unknown table 'error #7815' And@xisunchanged,becausenoneoftheSET@x=...statementsgotexecuted. UsingDROPallowsustogetabitofusergenerateddiagnosticinformationintotheerror message.However,wehavetodependontheassumptionthatthereisnodatabasenamed `error`.

ProcedureExample:Library87
Thesearethespecificationsforalibraryapplication. Wewantanybodytobeabletocalltheproceduresiftheyhavetherightprivileges,which we'llsetlaterwith GRANTALLONdatabasename.*TOusername; Buttheotherusersshouldonlybeabletoaccessusingtheprocedures.That'snoproblem. ItmeanswewantSQLSECURITYDEFINERcharacteristic.That'sthedefaultbutwe'll specifyitanyway,asdocumentation. Wewantaproceduretoaddbooks.Thereshouldbeatestthatthebook'sidisn'tpositive andthebook'stitleisn'tblank.ThisisasubstituteforaCHECKconstraint,which MySQLdoesn'tsupport. CREATE PROCEDURE add_book (p_book_id INT, p_book_title VARCHAR(100)) SQL SECURITY DEFINER BEGIN IF p_book_id < 0 OR p_book_title='' THEN SELECT 'Warning: Bad parameters'; END IF; INSERT INTO books VALUES (p_book_id, p_book_title); END; Wewantaproceduretoaddpatrons.Thereshouldbeatesttowarnthattherearealready morethan2patrons.It'spossibletotestsuchathingwithasubquery,e.g. IF(SELECTCOUNT(*)FROMtablename)>2)THEN...ENDIF; ButatthetimeIoriginallywrote,therewerebugswithsubqueries.Soinsteadwe'lluse "SELECTCOUNT(*)INTOvariablename". CREATE PROCEDURE add_patron (p_patron_id INT, p_patron_name VARCHAR(100)) SQL SECURITY DEFINER BEGIN DECLARE v INT DEFAULT 0; SELECT COUNT(*) FROM patrons INTO v; IF v > 2 THEN SELECT 'warning: already there are ',v,'patrons!'; END IF; INSERT INTO patrons VALUES (p_patron_id,p_patron_name); END;

ProcedureExample:Library(continued)88
Wewantaproceduretocheckoutbooks.Duringthetransactionwe'dliketodisplaythe patronswhoalreadyhavethisbook,andthebooksthatthispatronalreadyhas.We'llfind thatinformationwithcursorfetching,andwe'llusetwodifferentwaystotestwhether there'snomoredatatofetch:firstbycheckingwhetherthevariableretainsitsoriginal NULLvalueafterafetch,secondbycatchingfetchfailurewithaNOTFOUNDhandler. It'scleanerlookingifthetwocursorsareinseparateBEGIN/ENDblocks.Butwe'll declarevariablesinthemainBEGIN/ENDblock,sothattheirscopeisthewhole procedure. CREATE PROCEDURE checkout (p_patron_id INT, p_book_id INT) SQL SECURITY DEFINER BEGIN DECLARE v_patron_id, v_book_id INT; DECLARE no_more BOOLEAN default FALSE; DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_more=TRUE; BEGIN DECLARE c1 CURSOR FOR SELECT patron_id FROM transactions WHERE book_id = p_book_id; OPEN c1; SET v_patron_id=NULL; FETCH c1 INTO v_patron_id; IF v_patron_id IS NOT NULL THEN SELECT 'Book is already out to this patron:', v_patron_id; END IF; CLOSE c1; END; BEGIN DECLARE c2 CURSOR FOR SELECT book_id FROM transactions WHERE patron_id = p_patron_id; OPEN c2; book_loop: LOOP FETCH c2 INTO v_book_id; IF no_more THEN LEAVE book_loop; END IF; SELECT 'Patron already has this book:', v_book_id; END LOOP; END; INSERT INTO transactions VALUES (p_patron_id, p_book_id); END;

ProcedureExample:Hierarchy(I)89
Thehierarchy()proceduredoespartofwhatCONNECTBYwoulddowithanother DBMS.WehaveaPersonstablewheredescendantsarelinkedtoancestorsviaacolumn person_id.Wepassparameterstart_with=persontobeginthelistwith.Wedisplaythe ancestorsanddescendantsinorder. Theprocedure(wellactuallytwoprocedures)listingisonthenexttwopages.Iexpect thatsinceyou'vegottenthisfar,you'llbeabletofollowtheperambulationsifyouread carefully.Still,I'dbetterprepareyouwithafewexplanatorynotesfirst. Thehierarchy()procedurereceivesaperson'snameasaninputparameter.Itperforms someinitializationsmostimportantly,itmakesatemporarytablewhereeachresultrow willbestoredasit'sfound.Thenitcallsanotherprocedure,hierarchy2().Now, hierarchy2()isrecursive,whichmeansthatitcallsitself.Thiswouldn'tbenecessaryif everyfatherhadonlyzerooronesons,butthat'snothowthingsare,soaswereacheach branch,wehavetokeepgoingdownthetreewithrecursion,thenreturntothebranching pointandgodownthenextbranch. Idecidedtomaketheexceptionhandlersetaflag(error)whichgetscheckedafterany SQLstatement.Ifastatementfails,IuseSELECT'string'tooutputadiagnostic,and thenLEAVEproc.Sinceprocisalabelatthestartoftheprocedure,thateffectively meansleavetheprocedure. Imadesomeofthecompoundstatementsnested(BEGINENDwithinBEGINEND)so thatI'dbeabletodosomeDECLAREsofvariablesandhandlersjustforthespecific statementsthattheyarerelevantto.RememberthatanyDECLAREsintheouter compoundstatementarestillineffectfortheinnercompoundstatementunlessthey're overridden,andrememberthatwhenaninnercompoundstatementends,itsdeclarations gooutofscope(ceasetobenoticeable). TheCREATEPROCEDUREforhierarchy()isonthenextpage.Onthepageafterthatis theCREATEPROCEDUREforhierarchy2().Onthepageafterthatisalistingthatshows whathappenswhenIcallhierarchy()...ifallgoeswell!

ProcedureExample:Hierarchy(continued)90
CREATE PROCEDURE hierarchy (start_with CHAR(10)) proc: BEGIN DECLARE temporary_table_exists BOOLEAN; BEGIN DECLARE CONTINUE HANDLER FOR SQLEXCEPTION BEGIN END; DROP TABLE IF EXISTS Temporary_Table; END; BEGIN DECLARE v_person_id, v_father_id INT; DECLARE v_person_name CHAR(20); DECLARE done, error BOOLEAN DEFAULT FALSE; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET error = TRUE; CREATE TEMPORARY TABLE Temporary_Table (person_id INT, person_name CHAR(20), father_id INT); IF error THEN SELECT 'CREATE TEMPORARY failed'; LEAVE proc; END IF; SET temporary_table_exists=TRUE; SELECT person_id, person_name INTO v_person_id, v_person_name FROM Persons WHERE person_name = start_with limit 1; IF error THEN SELECT 'First SELECT failed'; LEAVE proc; END IF; IF v_person_id IS NOT NULL THEN INSERT INTO Temporary_Table VALUES (v_person_id, v_person_name, v_father_id); IF error THEN SELECT 'First INSERT failed'; LEAVE proc; END IF; CALL hierarchy2(v_person_id); IF error THEN SELECT 'First CALL hierarchy2() failed'; END IF; END IF; SELECT person_id, person_name, father_id FROM Temporary_Table; IF error THEN SELECT 'Temporary SELECT failed'; LEAVE proc; END IF; END; IF temporary_table_exists THEN DROP TEMPORARY TABLE Temporary_Table; END IF; END;//

ProcedureExample:Hierarchy(continued)91
CREATE PROCEDURE hierarchy2 (start_with INT) proc: BEGIN DECLARE v_person_id INT; DECLARE v_father_id INT; DECLARE v_person_name CHAR(20); DECLARE done, error BOOLEAN DEFAULT FALSE; DECLARE c CURSOR FOR SELECT person_id, person_name, father_id FROM Persons WHERE father_id = start_with; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET error = TRUE; OPEN c; IF error THEN SELECT 'OPEN failed'; LEAVE proc; END IF; REPEAT SET v_person_id=NULL; FETCH c INTO v_person_id, v_person_name, v_father_id; IF error THEN SELECT 'FETCH failed'; LEAVE proc; END IF; IF done=FALSE THEN INSERT INTO Temporary_Table VALUES (v_person_id, v_person_name, v_father_id); IF error THEN SELECT 'INSERT in hierarchy2() failed'; END IF; CALL hierarchy2(v_person_id); IF error THEN SELECT 'Recursive CALL hierarchy2() failed'; END IF; END IF; UNTIL done = TRUE END REPEAT; CLOSE c; IF error THEN SELECT 'CLOSE failed'; END IF; END; //

ProcedureExample:Hierarchy(continued)92
WhathappenswhenImakesomesampledataandcallhierarchy()... mysql> CREATE TABLE Persons (person_id INT, -> person_name CHAR(20), father_id INT);// Query OK, 0 rows affected (0.00 sec) mysql> INSERT INTO Persons VALUES -> (1,'Grandpa',NULL);// Query OK, 1 row affected (0.00 sec) mysql> INSERT INTO Persons VALUES -> (2,'Pa-1',1), (3,'Pa-2',1);// Query OK, 2 rows affected (0.00 sec) mysql> INSERT INTO Persons VALUES -> (4,'Grandson-1',2),(5,'Grandson-2',2);// Query OK, 2 rows affected (0.00 sec) mysql> SET @@max_sp_recursion_depth = 100// Query OK, 0 rows affected (0.00 sec) mysql> CALL hierarchy('Grandpa')// +-----------+-------------+-----------+ | person_id | person_name | father_id | +-----------+-------------+-----------+ | 1 | Grandpa | NULL | | 2 | Pa-1 | 1 | | 4 | Grandson-1 | 2 | | 5 | Grandson-2 | 2 | | 3 | Pa-2 | 1 | +-----------+-------------+-----------+ 5 rows in set (0.01 sec) Query OK, 0 rows affected, 1 warning (0.06 sec) Thesearchgoesallthewaydowntograndsons,thenstartsagainatsons.Thisisa"depth first"display.A"widthfirst"displaywouldhavebeenorderedbylevel,thatis,firstthe grandpa,thenallsons,thenallgrandsons. Thehierarchy()procedureisprettycomplex,eh?ItmighthavebugsthatIhaven'tthought of.Itwillprobablyfailifrecursionistoodeep,e.g.ifthereare100generations.Itwill certainlyloopifthereisacycle,e.g.ifsomebodyishisowngrandpa.Butdespiteall thosecaveats,IthinkI'veproventhatCONNECTBYispossibleinMySQLviaa recursivestoredprocedure.

Tipswhenwritinglongroutines93
MySQLhasnostoredproceduredebugger,andMySQLerrormessagescanbeunhelpful. Here'swhatIdowhenI'mcreatinga20lineprocedureandIseeanerrormessage. Startaneditor,andcopythestatementtotheeditor.Ifindit'seasiertochangethe statementwiththeeditorandthencutandpasteonthemysqlclient. Ifit'sasyntaxerror,thenremoveonestatementatatimeuntilthesyntaxerrorgoesaway. ThisisusuallyfasterthaneyeballingthewholeCREATEPROCEDUREstatement,which ofcoursewilloftenappeartobeperfectlyallright. Ifit'saruntimeerror,thenaddSELECTn;aftereveryexecutablestatementinsidethe body,wherenis0,1,2,etc.Thencalltheprocedureagain.Thisisadiagnosticfortracing theflowofcontrol. Ifit'sadataerror,thenskipoveritbyaddingDECLARECONTINUEHANDLERFOR ...BEGINEND;.Oftenwithatestdatabasethedatajustisn'tthere,sowhiledebugging Ihavethesestatementsthatletmecarryon.

Bugs94
Therearestillsomebugsinourstoredprocedurecode.They'regettingrarer.They're gettingobscurer.IcountedtherelevantbugsonDecember142004andagainonMay31 2006. Bug Count On December 14 2004: * Category Causes Operating System To Reboot Crashes or Hangs Returns 'Packets out of order' Fails

Count 1 23 6 31 -61

Bug Count On May 31 2006: Category Causes Operating System To Reboot Crashes or Hangs Returns 'Packets out of order' Fails

Count 0 2 0 35 -37

* Searched differently due to changes in bugs.mysql.com YoucanmeasureourprogressbylookingattheMySQLbugswebpageandsearchingfor storedprocedureortrigger.Herearethestepstotake: Gotothewebpageofourbugsdatabase,http://bugs.mysql.com ClickAdvancedSearch Inthedropdownboxthatbydefaultsays10bugs,selectAll. Inthe'CategorylistboxselectStoredProcedures. ClickSearch. So,ifyoudoasearchnowandfindmorebugsthanIfoundonMay312006,willthatbe abadsign?Well,yes,butperhapsitwillhappenbecausewehaveabiggerpoolof potentialbugreporters,perhapsincludingyou.

FeatureRequests95
Summary: SIGNAL PATH AccessingMoreTablesinFunctionsorTriggers LOADDATA BEGINATOMIC...END Encryptedstorage Timeout Debugger ExternalLanguages DROP...CASCADE|RESTRICT FORstatementswhichendwhenacursorends Here'saquicklookatsomeadditionswe'relookingtoadd. Themostimportantfeaturerequestsareforfunctionsthathelpustocatchupwiththe poweroftheANSI/ISOstandardandofDB2.We'vealsotalkedabouttheneedforaway totimeoutifafunctiongoesintoaloopandrunstoolong.

Resources96
(I)WebPagesOfMySQL (II)DownloadsFromMySQL (III)Books FormylastactI'llshowyouwhereyoucangoforfurtherinformation.

WebPagesOfMySQL
MySQLreferencemanualchapter"StoredProcedures": http://www.mysql.com/doc/en/Stored_Procedures.html Newsletterarticle"StoredProceduresinMySQL": http://www.mysql.com/newsletter/200401/a0000000297.html Herearesomeresourcesyou'llbeabletoaccessfromtheWeb.

MySQLDownloads
> cd ~/mysql-5.1-new/mysql-test/t > dir sp* 39409 2006-04-13 16732 2006-05-11 3670 2006-04-13 125960 2006-05-19

05:19 07:26 08:51 07:18

sp-error.test sp-security.test sp-threads.test sp.test

> cd ~/mysql-5.1-new/Docs > dir sp* 41909 2006-04-13 11:26 sp-imp-spec.txt Formoredetails,downloadtheMySQL5.0sourcecode.Idon'tjustmeanthecodeitself. Therearetestscriptsinthemysqltestdirectory,anddocumentationornewsfilesinthe Docsdirectory. ButIalsomeanthecodeitself.Ifyou'relookingatitforthefirsttime,Irecommend startingwithalookattheonlineInternalsdocumentation,here: http://dev.mysql.com/doc/internals/en/index.html

Books97
UnderstandingSQL'sStoredProcedures:ACompleteGuidetoSQL/PSM Author:JimMelton Publisher:MorganKaufmann Onlyavailablesecondhand. OneexcellentreferenceisabookthatwaswrittenaboutstandardSQLstoredprocedures JimMelton's"UnderstandingSQL'sstoredprocedures,acompleteguidetoSQL/PSM". ThisisabookabouttheSQLstandard,andMySQLtriestofollowthestandard,soit's usefulinplaces. That'sanicething,tohaveabookavailablebeforetheproductisready. MySQLStoredProcedureProgramming Author:GuyHarrison,withStevenFeuerstein Publisher:O'Reilly I'lljustquotewhatIsaidonthebook'sfrontpage: ThisisthefirstbookI'veseenthatreallyconcentratesonMySQL'sstoredprocedures.I foundtipsherethatI'dneverseenbefore.

TheEnd98
Thisistheendofthebook.Idon'tbotherwitharevieworanindex,sinceI'msureyou've hadnotroublememorizingitall. Ifyouenjoyedthisbook,youshouldlookformoreintheMySQL5.0NewFeatures series.Youcanfindalistonourdev.mysql.compage,undertheheadingMySQLKey Features. Thankyouverymuchforyourattention.Ifyouhaveanycommentsaboutthisbook, pleasesendthemtooneoftheMySQLforums: http://forums.mysql.com/

You might also like