您的位置:首页 > 数据库 > MySQL

Trees and Other Hierarchies in MySQL

2011-10-18 14:35 302 查看
原文网址

TreesandOtherHierarchiesinMySQL

GraphsandSQL

Edgelist
Edge-adjacencylistmodelofatree
Automatetreedrawing

NestedsetsmodelofatreeEdge-listmodelofanetworkParts
explosions

Mostnon-trivialdataishierarchical.Customershaveorders,whichhavelineitems,whichrefertoproducts,whichhaveprices.Populationsampleshavesubjects,whotaketests,whichgiveresults,whichhavesub-resultsandnorms.Websiteshavepages,
whichhavelinks,whichcollecthits,whichdistributeacrossdatesandtimes.Withsuchdata,weknowthedepthofthehierarchybeforewesitdowntowriteaquery.ThedepthofthehierarchyoftablesfixesthenumberofJOINsweneedtowrite.

Butifourdatadescribesafamilytree,orabrowsinghistory,orabillofmaterials,hierarchicaldepthdependsonthedata.WenolongerknowhowmanyJOINsitwilltaketowalkthetree.Weneedadifferentdatamodel.

Thatmodelisthegraph(Fig1),whichisasetofnodes(vertices)andtheedges(linesorarcs)thatconnectthem.ThischapterisabouthowtomodelandquerygraphsinaMySQLdatabase.

Graphtheoryisabranchoftopology.Itisthestudyofgeometricrelationswhicharen'tchangedbystretchingandcompression—rubbersheetgeometry,somecallit.Graphtheoryisidealformodellinghierarchies—likefamilytrees,browsinghistories,
searchtreesandbillsofmaterials—whoseshapeandsizewecan'tknowinadvance.



LetthesetofnodesinFig1be
N
,thesetofedgesbe
L
,andthegraphbe
G
.Then
G
isthetupleororderedpair
{N,L}
:

N={A,B,C,D,E,F}L={AC,CD,CF,BE}G={N,L}

Iftheedgesaredirected,thegraphisadigraphordirectedgraph.A
mixedgraphhasbothdirectedandundirectededges.

Examplesofgraphsareorganisationalcharts;itineraries;routemaps;partsexplosions;massivelymultiplayergames;languagerules;chathistories;networkandlinkanalysisinawidevarietyoffields,forexamplesearchengines,forensics,epidemiology
andtelecommunications;datamining;modelsofchemicalstructurehierarchies;and
biochemicalprocesses.

Graphcharacteristicsandmodels

Nodesandedges:Twonodesareadjacentifthereisanedgebetweenthem.Twoedgesareadjacentiftheyconnecttoacommonnode.Ina
completegraph,allnodesareadjacenttoallothernodes.

Inadigraph,thenumberofedgesenteringanodeisitsindegree;thenumberleavingisitsoutdegree.Anodeofindegreezeroisarootnode,anodeofoutdegreezeroisaleafnode.

Inaweightedgraph,usedforexampletosolvethetravellingsalesmanproblem,edgeshaveaweightattribute.Adigraphwithweightededgesisa
network.

Pathsandcycles:Aconnectedsequenceofedgesisapath,itslengththenumberofedgestraversed.Twonodesareconnectedifthereisapathbetweenthem.Ifthereisapathconnectingeverypairofnodes,thegraphisaconnectedgraph.

Apathinwhichnonoderepeatsisasimplepath.Apathwhichreturnstoitsownoriginwithoutcrossingitselfisacycleorcircuit.Agraphwithmultiplepathsbetweenatleastonepairofnodesisreconvergent.Areconvergentgraphmaybecyclic
oracyclic.Aunitlengthcycleisaloop.

Ifagraph'sedgesintersectonlyatnodes,itisplanar.Twopathshavingnonodeincommonare
independent.

Traversinggraphs:Therearetwomainapproaches,breadth-firstand
depth-first.Breadth-firsttraversalvisitsallanode'ssiblingsbeforemovingontothenextlevel,andtypicallyusesa
queue.Depth-firsttraversalfollowsedgesdowntoleavesandbackbeforeproceedingtosiblings,andtypicallyusesa
stack.

Sparsity:AgraphwherethesizeofEapproachesthemaximumN2is
dense.WhenthemultipleismuchsmallerthanN,thegraphisconsidered
sparse.

Trees:Atreeisaconnectedgraphwithnocycles.Itisalsoagraphwheretheindegreeoftherootnodeis0,andtheindegreeofeveryothernodeis1.Atreewhereeverynodeisofoutdegree<=2isabinarytree.A
forestisagraphinwhicheveryconnectedcomponentisatree.

Eulerpaths:ApathwhichtraverseseveryedgeinagraphexactlyonceisanEulerpath.AnEulerpathwhichisacircuitisanEulercircuit.

Ifandonlyifeverynodeofaconnectedgraphhasevendegree,ithasanEulercircuit(whichis

whythegoodpeopleofKönigsbergcannotgoforawalkcrossingeachoftheirsevenbridgesexactlyonce).Ifandonlyifaconnectedgraphhasexactly2nodeswithodddegree,ithasanon-circuitEulerpath.Thedegreeofanendpointofanon-cycleEuler
pathis1+twicethenumberoftimesthepathpassesthroughthatnode,soitisalwaysodd.

Modelsforcomputinggraphs

Traditionally,computersciencetextbookshaveofferededgelists,
adjacencylistsandadjacencymatricesasdatastructuresforgraphs,withalgorithmsimplementedinlanguageslikeC,C++andJava.Morerecentlyothermodelsandtoolshavebeensuggested,includingquerylanguagescustomisedforgraphs.

Edgelist:Thesimplestwaytorepresentagraphistolistitsedges:forFig1,theedgelistis
{AC,CD,CF,BE}
.Itiseasytoaddanedgetothelist;deletionisalittleharder.

Table1
NodesAdjacentnodes
AC
BE
CF,D,A
DC
EB
FC
Adjacencylist:Anadjacencylistisaraggedarray:foreachnodeitlistsalladjacentnodes.Thusitrepresentsadirectedgraphof
nnodesasalistofnlistswherelisticontainsnode
jifthegraphhasanedgefromnodeitonodej.

Anundirectedgraphmayberepresentedbyhavingnodejinthelistfornode
i,andnodeiinthelistfornodej.Table1showstheadjacencylistofthegraphinFig1interpretedasundirected.

Adjacencymatrix:Anadjacencymatrixrepresentsagraphwith
nnodesasannxnmatrix,wheretheentryat(i,j)is1ifthereisanedgefromnode
itonodej,orzeroifthereisnot.
Anadjacencymatrixcanrepresentaweightedgraphusingtheweightastheentry,andcanrepresentanundirectedgraphbyusingthesameentryinboth(i,j)and(j,i),orbyusinganuppertriangularmatrix.

Thereareusefulglossaries
hereand
here.

GraphsandSQL

OftenstandardSQLhasbeenthoughtcumbersomeforgraphproblems.CraigMullinsonce

wrotethat"theset-basednatureofSQLisnotsimpletomasterandisanathematotheOOtechniquespracticedbyJavadevelopers."

AfewyearsafterMullinswrotethat,SQLiseverywhere,anditisincreasinglyappliedtographproblems.DB2,OracleandSQLServerhaverecursiveoperatorsforprocessingrecursivesets,thoughtheyallworkalittledifferently.MySQLhasnosuchspecial
tools,thoughtheOpenQuerygrouphasa
graphengineunderdevelopment.Meanwhile
JoeCelkoand
ScottStephens,amongothers,havepublishedgeneralSQLgraphproblemsolutionsthataresimplerandsmallerthanequivalentC++,

C#orJavacode.HereweimplementsomeoftheseideasinMySQL.

BewarethatinportsofedgelistandadjacencylistmethodstoSQL,therehasbeennameslippage.
What'softencalledtheadjacencylistmodelintheSQLworldisactuallyanedgelistmodel.Ifyoufollowthenow-commonpracticeintheSQLworldofreferringtoedgelistsasadjacencylists,don'tbesurprisedtofindthatthemodelisn'tquite
liketheadjacencylistinTable1.Herewewaffle.Wecallthemedge-adjacencylists.

Therearealsotwonewerkindsofmodels:whatJoeCelkocalledthenestedsetsmodel—alsoknownasthe
intervalmodel—whichusesgreater-than/less-thanarithmetictoencodetreerelationshipsandmodifiedpreordertreetraversal(MPTT)toquerythem,andTropashko's
materialisedpathmodel,whereeachnodeisstoredwithits(denormalised)pathtotheroot.SowehavefourmainpossibilitiesformodellinggraphsinMySQL:

edge-adjacencylists:basedonanadaptationbyEFCoddofthelogicoflinkedliststotablestructuresandqueries,
adjacencymatrices,
nestedsetsfortreessimplifysomequeries,butinsertionanddeletionarecumbersome,and

materialisedpaths.

Hereweworkouthowtoimplementedge-adjacency,nestedsetsandmaterialisedpathmodels—orpartsofthem—inMySQL5&6.

Theedgelist

Theedgelististhesimplestpossiblerepresentationofagraph:minimally,asingleedgestablewhereeachrowspecifiesonenodeanditsparent(whichisNULLfortherootnode),ormoreelaboratelytwotables,oneforthenodes,theotherabridging
tablefortheir
edges.

Intherealworld,thenodestablemightbeatableofpersonnel,orassemblyparts,orlocationsonamap.Itmighthavemanyothercolummsofdata.Theedgestablemightalsohaveadditionalcolumnsforedgeproperties.Thekeyintegersofbothtablesmight
beBIGINTs.

Tomodel
Fig1,though,wekeepthingsassimpleaspossible:

Listing1CREATETABLEnodes(nodeIDCHAR(1)PRIMARYKEY);CREATETABLEedges(childIDCHAR(1)NOTNULL,parentIDCHAR(1)NOTNULL,PRIMARYKEY(childID,parentID));INSERTINTOnodesVALUES('A'),('B'),('C'),('D'),('E'),('F');INSERTINTOedgesVALUES('A','C'),('C','D'),('C','F'),('B','E');SELECT*FROMedges;+---------+----------+|childID|parentID|+---------+----------+|A|C||B|E||C|D||C|F|+---------+----------+

Now,withoutanyassumptionswhateveraboutwhetherthegraphisconnected,whetheritisdirected,whetheritisatree,orwhatever,howhardisittowritea
reachabilityprocedure,aprocedurewhichtellsuswherewecangettofromhere,wherever'here'is?

Asimpleapproachisabreadth-firstsearch:

Seedthelistwiththestartingnode,
Add,butdonotduplicate,nodeswhicharechildrenofnodesinthelist,
Add,butdonotduplicate,nodeswhichareparentsofnodesinthelist,
Repeatsteps2and3untiltherearenomorenodestoadd.

HereitisasaMySQLstoredprocedure.Itavoidsduplicatenodesbydefining
reached.nodeID
asaprimarykeyandaddingreachablenodeswith
INSERTIGNORE
:

Listing2DROPPROCEDUREIFEXISTSListReached;DELIMITERgoCREATEPROCEDUREListReached(INrootCHAR(1))BEGINDECLARErowsSMALLINTDEFAULT0;DROPTABLEIFEXISTSreached;CREATETABLEreached(nodeIDCHAR(1)PRIMARYKEY)ENGINE=HEAP;INSERTINTOreachedVALUES(root);SETrows=ROW_COUNT();WHILErows>0DOINSERTIGNOREINTOreachedSELECTDISTINCTchildIDFROMedgesASeINNERJOINreachedASpONe.parentID=p.nodeID;SETrows=ROW_COUNT();INSERTIGNOREINTOreachedSELECTDISTINCTparentIDFROMedgesASeINNERJOINreachedASpONe.childID=p.nodeID;SETrows=rows+ROW_COUNT();ENDWHILE;SELECT*FROMreached;DROPTABLEreached;END;goDELIMITER;CALLListReached('A');+--------+|nodeID|+--------+|A||C||D||F|+--------+

Tomaketheproceduremoreversatile,giveitinputparameterswhichtellitwhethertolistchild,parentorallconnections,andwhethertorecogniseloops(forexampleCtoC).

Togivethemodelreferentialintegrity,useInnoDBandmake
edges.childID
and
edges.parentID
foreignkeys.Toaddordeleteanode,addordeletedesiredsinglerowsin
nodes
and
edges
.Tochangeanedge,editit.Themodeldoesnotrequirethegraphtobeconnectedortreelike,anddoesnotpresumedirection.

TheedgelistisbasictowhatSQLersoftencalltheadjacencylistmodel.

Edge-adjacencylistmodelofatree



WritersintheSQLgraphliteratureoftengivesolutionsusingsingledenormalisedtables.Denormalisationcancost,bigtime.Thebiggerthetable,thebiggerthecost.Youcannoteditnodesandedgesseparately.Carryingextranodeinformationduringedge
computationslowsperformance.Withnodesandedgesdenormalisedtoonetable,youhavetorepresenttherootnodewitha
NULL.

Toavoidthesedifficulties,normalisetreeslikeWilliamShakespeare'sfamilytree(Fig2)withtwotables,a
nodestable(family)containinginformationaboutindividuals,andan
edgestable(familytree)witharowforeachparent-childlinkoredge.Later,whenweuseadifferenttreemodel,wewon'thavetomesswiththedatabeingmodelled.

Listing3--Basedata:CREATETABLEfamily(IDsmallint(6)PRIMARYKEYAUTO_INCREMENT,namechar(20)default'',sibordertinyint(4)defaultNULL,bornsmallint(4)unsigneddefaultNULL,diedsmallint(4)unsigneddefaultNULL);INSERTINTOfamilyVALUES(1,'RichardShakespeare',NULL,NULL,1561);INSERTINTOfamilyVALUES(2,'HenryShakespeare',1,NULL,1569);INSERTINTOfamilyVALUES(3,'JohnShakespeare',2,1530,1601);INSERTINTOfamilyVALUES(4,'JoanShakespeare',1,1558,NULL);INSERTINTOfamilyVALUES(5,'MargaretShakespeare',2,1562,1563);INSERTINTOfamilyVALUES(6,'WilliamShakespeare',3,1564,1616);INSERTINTOfamilyVALUES(7,'GilbertShakespeare',4,1566,1612);INSERTINTOfamilyVALUES(8,'JoanShakespeare',5,1568,1646);INSERTINTOfamilyVALUES(9,'AnneShakespeare',6,1571,1579);INSERTINTOfamilyVALUES(10,'RichardShakespeare',7,1574,1613);INSERTINTOfamilyVALUES(11,'EdmondShakespeare',8,1580,1607);INSERTINTOfamilyVALUES(12,'SusanaShakespeare',1,1583,1649);INSERTINTOfamilyVALUES(13,'HamnetShakespeare',1,1585,1596);INSERTINTOfamilyVALUES(14,'JudithShakespeare',1,1585,1662);INSERTINTOfamilyVALUES(15,'WilliamHart',1,1600,1639);INSERTINTOfamilyVALUES(16,'MaryHart',2,1603,1607);INSERTINTOfamilyVALUES(17,'ThomasHart',3,1605,1670);INSERTINTOfamilyVALUES(18,'MichaelHart',1,1608,1618);INSERTINTOfamilyVALUES(19,'ElizabethHall',1,1608,1670);INSERTINTOfamilyVALUES(20,'ShakespeareQuiney',1,1616,1617);INSERTINTOfamilyVALUES(21,'RichardQuiney',2,1618,1639);INSERTINTOfamilyVALUES(22,'ThomasQuiney',3,1620,1639);INSERTINTOfamilyVALUES(23,'JohnBernard',1,NULL,1674);--Tablewhichmodelsthetree:CREATETABLEfamilytree(childIDsmallintNOTNULL,parentIDsmallintNOTNULL,PRIMARYKEY(childID,parentID););INSERTINTOfamilytreeVALUES(2,1),(3,1),(4,2),(5,2),(6,2),(7,2),(8,2),(9,2),(10,2),(11,2),(12,6),(13,6),(14,6),(15,8),(16,8),(17,8),(18,8),(19,12),(20,14),(21,14),(22,14),(23,19);

(The
family
PKisauto-increment,butthelistingismorereader-friendlywhenthe
ID
valuesareshown.)

Itwillbeusefultohaveafunctionthatreturns
family.name
foraparentorchildIDin
familytree
:

Listing4--5.0.16ORLATER:SETGLOBALlog_bin_trust_function_creators=TRUE;DROPFUNCTIONIFEXISTSPersonName;DELIMITERgoCREATEFUNCTIONPersonName(personIDSMALLINT)RETURNSCHAR(20)BEGINDECLAREpnameCHAR(20)DEFAULT'';SELECTnameINTOpnameFROMfamilyWHEREID=personID;RETURNpname;END;goDELIMITER;SELECTPersonName(parentID)AS'ParentofWilliam'FROMfamilytreeWHEREchildID=6;+-------------------+|ParentofWilliam|+-------------------+|HenryShakespeare|+-------------------+SELECTPersonName(childID)AS'ChildrenofWilliam'FROMfamilytreeWHEREparentID=(SELECTIDFROMfamilyWHEREname='WilliamShakespeare');+---------------------+|ChildrenofWilliam|+---------------------+|SusanaShakespeare||HamnetShakespeare||JudithShakespeare|+---------------------+SELECTPersonName(childID)ASchild,PersonName(parentID)ASparentFROMfamilytree;+----------------------+---------------------+|child|parent|+----------------------+---------------------+|HenryShakespeare|RichardShakespeare||JohnShakespeare|RichardShakespeare||JoanShakespeare|HenryShakespeare||MargaretShakespeare|HenryShakespeare||WilliamShakespeare|HenryShakespeare||GilbertShakespeare|HenryShakespeare||JoanShakespeare|HenryShakespeare||AnneShakespeare|HenryShakespeare||RichardShakespeare|HenryShakespeare||EdmondShakespeare|HenryShakespeare||SusanaShakespeare|WilliamShakespeare||HamnetShakespeare|WilliamShakespeare||JudithShakespeare|WilliamShakespeare||WilliamHart|JoanShakespeare||MaryHart|JoanShakespeare||ThomasHart|JoanShakespeare||MichaelHart|JoanShakespeare||ElizabethHall|SusanaShakespeare||ShakespeareQuiney|JudithShakespeare||RichardQuiney|JudithShakespeare||ThomasQuiney|JudithShakespeare||JohnBernard|ElizabethHall|+----------------------+---------------------+

Asame-tableforeignkeycansimplifytreemaintenance:

Listing4acreatetableedges(IDintPRIMARYKEY,	parentidint,foreignkey(parentID)referencesedges(ID)ONDELETECASCADEONUPDATECASCADE)engine=innodb;insertintoedges(ID,parentID)values(1,null),(2,1),(3,1),(4,2);select*fromedges;+----+----------+|ID|parentid|+----+----------+|1|NULL||2|1||3|1||4|2|+----+----------+deletefromedgeswhereid=2;select*fromedges;+----+----------+|ID|parentid|+----+----------+|1|NULL||3|1|+----+----------+

Simplequeriesretrievebasicfactsaboutthetree,forexampleGROUP_CONCAT()collectsparentnodeswiththeirchildrenincorrectorder:

Listing5SELECTparentIDASParent,GROUP_CONCAT(childIDORDERBYsiborder)ASChildrenFROMfamilytreetJOINfamilyfONt.parentID=f.IDGROUPBYparentID;+--------+-------------------+|Parent|Children|+--------+-------------------+|1|3,2||2|4,5,6,7,8,9,10,11||6|12,13,14||8|18,17,16,15||12|19||14|22,21,20||19|23|+--------+-------------------+

Iterateoverthosecomma-separatedlistswithabitofapplicationcodeandyouhaveahybridtreewalk.The
paterfamiliasistherootnode,individualswithnochildrenaretheleafnodes,andqueriestoretrievesubtreestatisticsarestraightforward:

Listing6SELECTPersonName(ID)ASPaterfamilias,IFNULL(born,'?')ASBorn,IFNULL(died,'?')ASDiedFROMfamilyASfLEFTJOINfamilytreeAStONf.ID=t.childIDWHEREt.childIDISNULL;+---------------------+------+------+|Paterfamilias|Born|Died|+---------------------+------+------+|RichardShakespeare|?|1561|+---------------------+------+------+SELECTPersonName(ID)ASChildless,IFNULL(born,'?')ASBorn,IFNULL(died,'?')ASDiedFROMfamilyASfLEFTJOINfamilytreeAStONf.ID=t.parentIDWHEREt.parentIDISNULL;+----------------------+------+------+|Childless|Born|Died|+----------------------+------+------+|JohnShakespeare|1530|1601||JoanShakespeare|1558|?||MargaretShakespeare|1562|1563||GilbertShakespeare|1566|1612||AnneShakespeare|1571|1579||RichardShakespeare|1574|1613||EdmondShakespeare|1580|1607||HamnetShakespeare|1585|1596||WilliamHart|1600|1639||MaryHart|1603|1607||ThomasHart|1605|1670||MichaelHart|1608|1618||ShakespeareQuiney|1616|1617||RichardQuiney|1618|1639||ThomasQuiney|1620|1639||JohnBernard|?|1674|+----------------------+------+------+SELECTROUND(AVG(died-born),2)AS'Longevityofthechildless'FROMfamilyASfLEFTJOINfamilytreeAStONf.ID=t.parentIDWHEREt.parentIDISNULL;+----------------------------+|Longevityofthechildless|+----------------------------+|25.86|+----------------------------+

InstrikingcontrastwithCelko's
nestedsetsmodel,insertinganewiteminthismodelrequiresnorevisionofexistingrows.Wejustaddanew
family
row,thenanew
familytree
rowwithIDsspecifyingwhoisparenttowhom.Deletionisalsoatwo-step:deletethe
familytree
rowforthatchild-parentlink,thendeletethe
family
rowforthatchild.

Walkinganedgelisttree
Traversingsubtreesiswhatgivestheedge-adjacencylistmodelitsreputationfordifficulty.Wecan'tknowinadvance,exceptinthesimplestoftrees,howmanylevelsofparentandchildhavetobequeried,soweneedrecursionoralogicallyequivalent
loop.

It'sanaturalproblemforastoredprocedure.Earliereditionsshowedabruteforce

breadth-firstalgorithmthatneededthreeintermediarytables(itcanbefoundin
AppendixE).Hereisasimpleralgorithmthatjustseedsaresulttablewithfirst-foundparent-childpairs,thenusesMySQL’sINSERTIGNOREtoaddremainingpairs:

Listing7DROPPROCEDUREIFEXISTSfamsubtree;DELIMITERgoCREATEPROCEDUREfamsubtree(rootINT)BEGINDROPTABLEIFEXISTSfamsubtree;CREATETABLEfamsubtreeSELECTchildID,parentID,0ASlevelFROMfamilytreeWHEREparentID=root;ALTERTABLEfamsubtreeADDPRIMARYKEY(childID,parentID);REPEATINSERTIGNOREINTOfamsubtreeSELECTf.childID,f.parentID,s.level+1FROMfamilytreeASfJOINfamsubtreeASsONf.parentID=s.childID;UNTILRow_Count()=0ENDREPEAT;END;goDELIMITER;callfamsubtree(1);--fromtherootyoucanseeforeverSELECTConcat(Space(level),parentID)ASParent,Group_Concat(childIDORDERBYchildID)ASChildFROMfamsubtreeGROUPBYparentID;+--------+-------------------+|Parent|Child|+--------+-------------------+|1|2,3||2|4,5,6,7,8,9,10,11||6|12,13,14||8|15,16,17,18||12|19||14|20,21,22||19|23|+--------+-------------------+

Simpleandquick.Thelogicportstoanyedgelist.Wecanprovethatrightnowbywritingagenericversion.GenericTree()justneedsparametersforthenameofthetargettable,thenamesofitschildandparentIDcolumns,andtheparentIDwhose
descendantsaresought:

Listing7a:General-purposeedgelisttreewalkerDROPPROCEDUREIFEXISTSGenericTree;DELIMITERgoCREATEPROCEDUREGenericTree(edgeTableCHAR(64),edgeIDcolCHAR(64),edgeParentIDcolCHAR(64),ancestorIDINT)BEGINDECLARErINTDEFAULT0;DROPTABLEIFEXISTSsubtree;SET@sql=Concat('CREATETABLEsubtreeENGINE=MyISAMSELECT',edgeIDcol,'ASchildID,',edgeParentIDcol,'ASparentID,','0ASlevelFROM',edgeTable,'WHERE',edgeParentIDcol,'=',ancestorID);PREPAREstmtFROM@sql;EXECUTEstmt;DROPPREPAREstmt;ALTERTABLEsubtreeADDPRIMARYKEY(childID,parentID);REPEATSET@sql=Concat('INSERTIGNOREINTOsubtreeSELECTa.',edgeIDcol,',a.',edgeparentIDcol,',b.level+1FROM',edgeTable,'ASaJOINsubtreeASbONa.',edgeParentIDcol,'=b.childID');PREPAREstmtFROM@sql;EXECUTEstmt;SETr=Row_Count();--saverow_count()resultbeforeDROPPREPARElosesthevalueDROPPREPAREstmt;UNTILr<1ENDREPEAT;END;goDELIMITER;

ToretrievedetailslikenamesandotherdataassociatedwithnodeIDs,writeafrontendquerytojointhesubtreeresulttablewiththerequireddetailtable(s),forexample:

CALLGenericTree('familytree','childID','parentID',1);SELECTConcat(Repeat('',s.level),a.name)ASParent,b.nameASChildFROMsubtreesJOINfamilyaONs.parentID=a.IDJOINfamilybONs.childID=b.ID;+-----------------------+----------------------+|Parent|Child|+-----------------------+----------------------+|RichardShakespeare|HenryShakespeare||RichardShakespeare|JohnShakespeare||HenryShakespeare|JoanShakespeare||HenryShakespeare|MargaretShakespeare||HenryShakespeare|WilliamShakespeare||HenryShakespeare|GilbertShakespeare||HenryShakespeare|JoanShakespeare||HenryShakespeare|AnneShakespeare||HenryShakespeare|RichardShakespeare||HenryShakespeare|EdmondShakespeare||WilliamShakespeare|SusanaShakespeare||WilliamShakespeare|HamnetShakespeare||WilliamShakespeare|JudithShakespeare||JoanShakespeare|WilliamHart||JoanShakespeare|MaryHart||JoanShakespeare|ThomasHart||JoanShakespeare|MichaelHart||SusanaShakespeare|ElizabethHall||JudithShakespeare|ShakespeareQuiney||JudithShakespeare|RichardQuiney||JudithShakespeare|ThomasQuiney||ElizabethHall|JohnBernard|+-----------------------+----------------------+

IsGenericTree()fast?Youbet.Onstandardhardwareitprocessesa5,000-nodetreeinlessthan0.5secs—muchfasterthanacomparable

nestedsetsqueryonthesametree!Ithasnoseriousscalingissues.Anditslogiccanbeusedtoprune:callGenericTree()thendeletethelistedrows.Betterstill,writeagenerictreeprunerfromListing7aandaDELETEcommand.Toinsertasubtree,
prepareatableofnewrows,pointitstopedgeatanexistingnodeasparent,andINSERTit.

Theedgelisttreewalkislogicallyrecursive,sohowaboutcodingitrecursively?Hereisarecursive

depth-firstPHPtreewalkforthe
familytreeandfamilytables:

Listing7b:RecursiveedgelistsubtreeinPHP$info=recursivesubtree(1,$a=array(),0);foreach($infoas$row)echostr_repeat(" ",2*$row[4]),($row[3]>0)?"<b>{$row[1]}</b>":$row[1],"<br/>";functionrecursivesubtree($rootID,$a,$level){$childcountqry="(SELECTCOUNT(*)FROMfamilytreeWHEREparentID=t.childID)ASchildcount";$qry="SELECTt.childid,f.name,t.parentid,$childcountqry,$level"."FROMfamilytreetJOINfamilyfONt.childID=f.ID"."WHEREparentid=$rootIDORDERBYchildcount<>0,t.childID";$res=mysql_qry($qry);while($row=mysql_fetch_row($res)){$a[]=$row;if($row[3]>0)$a=recursivesubtree($row[0],$a,$level+1);//downbeforeright}return$a;}

Aquerywithasubquery,afetchloop,andarecursivecall--that'sallthereistoit.Anicefeatureofthisalgorithmisthatitwritesresultrowsindisplay-readyorder.ToportthistoMySQL,youmusthavesetmaximumrecursiondepthin
my.cnf/iniorinyourclient:

Listing7c:RecursiveedgelistsubtreeinMySQLSET@@SESSION.max_sp_recursion_depth=25;DROPPROCEDUREIFEXISTSrecursivesubtree;DELIMITERgoCREATEPROCEDURErecursivesubtree(irootINT,ilevelINT)BEGINDECLAREirows,ichildid,iparentid,ichildcount,doneINTDEFAULT0;DECLAREcnameVARCHAR(64);SETirows=(SELECTCOUNT(*)FROMfamilytreeWHEREparentID=iroot);IFilevel=0THENDROPTEMPORARYTABLEIFEXISTS_descendants;CREATETEMPORARYTABLE_descendants(childIDINT,parentIDINT,nameVARCHAR(64),childcountINT,levelINT);ENDIF;IFirows>0THENBEGINDECLAREcurCURSORFORSELECTchildid,parentid,f.name,(SELECTCOUNT(*)FROMfamilytreeWHEREparentID=t.childID)ASchildcountFROMfamilytreetJOINfamilyfONt.childID=f.IDWHEREparentid=irootORDERBYchildcount<>0,t.childID;DECLARECONTINUEHANDLERFORSQLSTATE'02000'SETdone=1;OPENcur;WHILENOTdoneDOFETCHcurINTOichildid,iparentid,cname,ichildcount;IFNOTdoneTHENINSERTINTO_descendantsVALUES(ichildid,iparentid,cname,ichildcount,ilevel);IFichildcount>0THENCALLrecursivesubtree(ichildid,ilevel+1);ENDIF;ENDIF;ENDWHILE;CLOSEcur;END;ENDIF;IFilevel=0THEN--Showresulttableheadedbynamethatcorrespondstoiroot:SETcname=(SELECTnameFROMfamilyWHEREID=iroot);SET@sql=CONCAT('SELECTCONCAT(REPEAT(CHAR(32),2*level),IF(childcount,UPPER(name),name))','AS',CHAR(39),'Descendantsof',cname,CHAR(39),'FROM_descendants');PREPAREstmtFROM@sql;EXECUTEstmt;DROPPREPAREstmt;ENDIF;END;goDELIMITER;CALLrecursivesubtree(1,0);+------------------------------------+|DescendantsofRichardShakespeare|+------------------------------------+|HENRYSHAKESPEARE||JoanShakespeare||MargaretShakespeare||WILLIAMSHAKESPEARE||SUSANASHAKESPEARE||ELIZABETHHALL||JohnBernard||HamnetShakespeare||JUDITHSHAKESPEARE||ShakespeareQuiney||RichardQuiney||ThomasQuiney||GilbertShakespeare||JOANSHAKESPEARE||WilliamHart||MaryHart||ThomasHart||MichaelHart||AnneShakespeare||RichardShakespeare||EdmondShakespeare||JohnShakespeare|+------------------------------------+

InMySQLthisrecursivetreewalkcanbeupto100timesslowerthanGenericTree().ItsslownessiscomparabletothatofaMySQLversionofKendallWillet'sdepth-firstedgelistsubtreealgorithm:

Listing7d:Depth-firstedgelistsubtreeCREATEPROCEDUREdepthfirstsubtree(irootINT)BEGINDECLAREilastvisited,inxt,ilastordINT;SETilastvisited=iroot;SETilastord=1;DROPTABLEIFEXISTSdescendants;CREATETABLEdescendantsSELECTchildID,parentID,-1ASordFROMfamilytree;UPDATEdescendantsSETord=1WHEREchildID=iroot;this:LOOPSETinxt=NULL;SELECTMIN(childID)INTOinxtFROMdescendants--godownWHEREparentID=ilastvisitedANDord=-1;IFinxtISNULLTHEN--nothingdown,sogorightSELECTMIN(d2.childID)INTOinxtFROMdescendantsd1JOINdescendantsd2ONd1.parentID=d2.parentIDANDd1.childID<d2.childIDWHEREd1.childID=ilastvisited;ENDIF;IFinxtISNULLTHEN--nothingright.sogoupSELECTparentIDINTOinxtFROMdescendantsWHEREchildID=ilastvisitedANDparentIDISNOTNULL;ENDIF;UPDATEdescendantsSETord=ilastord+1WHEREchildID=inxtANDord=-1;IFROW_COUNT()>0THENSETilastord=ilastord+1;ENDIF;IFinxtISNULLTHENLEAVEthis;ENDIF;SETilastvisited=inxt;ENDLOOP;END;

OnereasonWillet'sissloweristhatMySQLdoesnotpermitmultiplereferencestoatemporarytableinaquery.Whenallalgorithmsaredeniedtemptables,though,thisalgorithmisstillslowerthanrecursion,andbotharemuchslowerthan

GenericTree().

Edgelistsubtreequeriesperformfasterandareeasiertowritethantheirreputationsuggests.Andedgetablesareflexible.Foratreedescribingapartsexplosionratherthanafamily,justaddcolumnsforweight,quantity,assemblytime,cost,price
andsoon.Reportsneedonlyaggregatecolumnvaluesandsums.We'llrevisitthisneartheendofthechapter.

Enumeratingpathsinanedge-adjacencylist
Pathenumerationinanedgelisttreeisalmostaseasyasdepth-firstsubtreetraversal:

createatableforpaths,
seeditwithpathsofunitlengthfromthetreetable,
iterativelyaddpathstilltherearenomoretoadd.

MySQL'sINSERTIGNOREcommandsimplifiesthecodebyremovingtheneedforaNOTEXISTS(...)clauseintheINSERT...SELECTstatement.Sinceadjacenciesarelogicallysymmetrical,wemakepathdirectionthecaller'schoice,
UP
or
DOWN
:

Listing8DROPPROCEDUREIFEXISTSListAdjacencyPaths;DELIMITERgoCREATEPROCEDUREListAdjacencyPaths(INdirectionCHAR(5))BEGINDROPTABLEIFEXISTSpaths;CREATETABLEpaths(startSMALLINT,stopSMALLINT,lenSMALLINT,PRIMARYKEY(start,stop))ENGINE=HEAP;IFdirection='UP'THENINSERTINTOpathsSELECTchildID,parentID,1FROMfamilytree;ELSEINSERTINTOpathsSELECTparentID,childID,1FROMfamilytree;ENDIF;WHILEROW_COUNT()>0DOINSERTIGNOREINTOpathsSELECTDISTINCTp1.start,p2.stop,p1.len+p2.lenFROMpathsASp1INNERJOINpathsASp2ONp1.stop=p2.start;ENDWHILE;SELECTstart,stop,lenFROMpathsORDERBYstart,stop;DROPTABLEpaths;END;goDELIMITER;

Tofindthepathsfromjustonenode,seedthe
paths
tablewithpathsfromthestartingnode,theniterativelysearchaJOINof
familytree
and
paths
foredgeswhichwillextendexistingpathsintheuser-specifieddirection:

Listing8aDROPPROCEDUREIFEXISTSListAdjacencyPathsOfNode;DELIMITERgoCREATEPROCEDUREListAdjacencyPathsOfNode(INnodeSMALLINT,INdirectionCHAR(5))BEGINTRUNCATEpaths;IFdirection='UP'THENINSERTINTOpathsSELECTchildID,parentID,1FROMfamilytreeWHEREchildID=node;ELSEINSERTINTOpathsSELECTparentID,childID,1FROMfamilytreeWHEREparentID=node;ENDIF;WHILEROW_COUNT()>0DOIFdirection='UP'THENINSERTIGNOREINTOpathsSELECTDISTINCTpaths.start,familytree.parentID,paths.len+1FROMpathsINNERJOINfamilytreeONpaths.stop=familytree.childID;ELSEINSERTIGNOREINTOpathsSELECTDISTINCTpaths.start,familytree.childID,paths.len+1FROMpathsINNERJOINfamilytreeONpaths.stop=familytree.parentID;ENDIF;ENDWHILE;SELECTstart,stop,lenFROMpathsORDERBYstart,stop;END;goDELIMITER;CALLListAdjacencyPathsOfNode(1,'DOWN');+-------+------+------+|start|stop|len|+-------+------+------+|1|2|1||1|3|1||1|4|2||1|5|2||1|6|2||1|7|2||1|8|2||1|9|2||1|10|2||1|11|2||1|12|3||1|13|3||1|14|3||1|15|3||1|16|3||1|17|3||1|18|3||1|19|4||1|20|4||1|21|4||1|22|4||1|23|5|+-------+------+------+

Thesealgorithmsdon'tbendthebrain.Theyperformacceptablywithlargetrees.Queryingedge-adjacencylistsforsubtreesandpathsislessdauntingthantheirreputationsuggests.

Automatetreedrawing!
Tablesofnumbersmaybethemostboringobjectsonearth.Howtobringthemalive?TheGoogleVisualizationAPIlibraryhasan

‘OrgChart’modulethatcanmakeedgelisttreeslooklike
Fig2,buteachinstanceneedsfiftyorsolinesofspecificJavaScriptcode,plusanadditionallineofcodeforeachrowofdatainthetree.Couldweautogeneratethatcode?
Maisoui!Themoduleneedschildnodeandparentnodecolumnsofdata,andacceptsanoptionalthirdcolumnforinfothatpopsupwhenthemousehovers.HereissuchaqueryfortheShakespearefamilytree...

Listing9selectconcat(node.ID,'',node.name)asnode,if(edges.parentIDisnull,'',concat(parent.ID,'',parent.name))asparent,if(node.bornisnull,'Birthdateunknown',concat('Born',node.born))astooltipfromfamilyasnodeleftjoinfamilytreeasedgesonnode.ID=edges.childIDleftjoinfamilyasparentonedges.parentID=parent.ID;

andhereisaPHPfunctionwhichgeneratestheHTMLandJavaScriptneededtopaintanOrgChartfor
anytreequerythatreturnsstringcolumnsfornode,parentandoptionallytooltips:

Listing9afunctionorgchart($qry){$cols=array();$rows=array();$res=mysql_query($qry)orexit(mysql_error());$colcount=mysql_num_fields($res);if($colcount<2)exit("Orgchartneedstwoorthreecolumns");$rowcount=mysql_num_rows($res);for($i=0;$i<$colcount;$i++)$cols[]=mysql_fetch_field($res,$i);while($row=mysql_fetch_row($res))$rows[]=$row;echo"<html>\n<head>\n","<scripttype='text/javascript'src='https://www.google.com/jsapi'></script>\n","<scripttype='text/javascript'>\n","google.load('visualization','1',{'packages':['orgchart']});\n","google.setOnLoadCallback(drawChart);\n","functiondrawChart(){\n","vardata=newgoogle.visualization.DataTable();\n";for($i=0;$i<$colcount;$i++)echo"data.addColumn('string','{$cols[$i]->name}')\n";echo"data.addRows([\n";for($j=0;$j<$rowcount;$j++){$row=$rows[$j];$c=(($j<$rowcount-1)?",":"");echo"['{$row[0]}','{$row[1]}','{$row[2]}']$c\n";}echo"]);\n","varchart=newgoogle.visualization.OrgChart(document.getElementById('chart_div'));\n","varoptions={'size':'small','allowHtml':'true','allowCollapse':'true'};\n","chart.draw(data,options);\n","}\n","</script>\n/head>\n<body>\n","<divid='chart_div'></div>\n","</body>\n</html>";}

Nestedsetsmodelofatree

ImagineanovaldrawnroundeveryleafandeverysubtreeinFig2,andafinalovalroundtheentiretree.Thetreeisaset.Eachsubtreeisasubset.That'sthebasicideaofthenestedsetsmodel.

Theadvantageofthenestedsetsmodelisthatroot,leaves,subtrees,levels,treeheight,ancestors,descendantsandpathscanberetrievedwithoutrecursionorapplicationlanguagecode.Thedisadvantagesare:

initialsetupofthetreetablecanbedifficult,
queriesforparents(immediatesuperiors)andchildren(immediatesubordinates)aremorecomplicatedthanwithanedgelistmodel,

insertion,updatesanddeletionareextremelycumbersomesincetheymayrequireupdatestomuchofthetree.

Thenestedsetsmodeldependsonusinga
modifiedpreordertreetraversal(MPTT)
depth-firstalgorithmtoassigneachnodeleftandrightintegerswhichdefinethenode'streeposition.Allnodesofasubtreehave

leftvaluesgreaterthanthesubtreeparent'sleftvalue,and

rightvaluessmallerthanthatofthesubtreeparent'srightvalue.

soqueriesforsubtreesaredeadsimple.Ifthenumberingschemeisinteger-sequentialasinFig3,therootnodereceivesa
leftvalueof1andarightvalueequaltotwicetheitemcount.



ToseehowtocodenestedsetsusingMPTT,tracetheascendingintegersinFig3,startingwith1onthe
leftsideoftherootnode(
RichardShakespeare
).Followingedgesdownwardandleftward,the
leftsideofeachboxgetsthenextinteger.Whenyoureachaleaf(
Joan
,
left=3),therightsideofthatboxgetsthenextinteger(4).Ifthereisanothernodetotherightonthesamelevel,continueinthatdirection;otherwisecontinueupthe
rightsideofthesubtreeyoujustdescended.Whenyouarrivebackattherootonthe
rightside,you'redone.Down,rightandup.

Aseriousproblemwiththisschemejumpsoutrightaway:afteryou'vewrittentheFig3treetoatable,whatifhistoriansdiscoveranolderbrotherorsisterofHenryandJohn?Everyrowinthetreetablemustbeupdated!

Celkoandothershaveproposedalternativenumberingschemestogetroundthisproblem,butthelogicaldifficultyremains:insertsandupdatescaninvalidatemanyorallrows,andnoSQLCHECKorCONSTRAINTcanpreventit.Thenestedsetsmodelisnotgood
fortreeswhichrequirefrequentupdates,andisprettymuchunsupportableforlargeupdatabletreesthatwillbeaccessedbymanyconcurrentusers.Butaswe'llseeinamoment,itcanbeveryusefulindeedforreportingatree.

Howtobuildanestedsetsrepresentationfromanedgelist
Obviously,numberingatreebyhandwouldbeerror-prone,seriouslyimpracticalforlargetrees,soit'susuallybesttocodethetreeinitiallyasanedgelist,thenuseastoredproceduretotranslatetheedgelistrepresentationtonestedsets.

Celko'sdepth-firstpushdownstackmethodwilltranslateanyedgelisttreeintoanestedsetstree,thoughslowly:

Createatablenestedsettreeforthetree:
node
,
leftedge
,
rightedge
,andastackpointer(
top
),
Seedthattablewiththerootnodeoftheedgelisttree,
Setanextedgecounterto1plustheleftvalueoftherootnode,i.e.2,
Whilethatcounterislessthantherightedgevalueoftherootnode...

insertarowforthisparent'ssmallestunwrittenchild,anddropdownalevel,or

ifwe'reoutofchildren,incrementrightedge,writeittothecurrentrow,andbackupalevel.

ThisversionhasbeenimprovedtohandleedgelisttreeswithorwithoutarowcontainingtherootnodeanditsNULLparent:

Listing10DROPPROCEDUREIFEXISTSEdgeListToNestedSet;DELIMITERgoCREATEPROCEDUREEdgeListToNestedSet(edgeTableCHAR(64),idColCHAR(64),parentColCHAR(64))BEGINDECLAREmaxrightedge,rowsSMALLINTDEFAULT0;DECLAREtrees,currentSMALLINTDEFAULT1;DECLAREnextedgeSMALLINTDEFAULT2;DECLAREmsgCHAR(128);--createworkingtreetableasacopyofedgeTableDROPTEMPORARYTABLEIFEXISTStree;CREATETEMPORARYTABLEtree(childIDINT,parentIDINT);SET@sql=CONCAT('INSERTINTOtreeSELECT',idCol,',',parentCol,'FROM',edgeTable);PREPAREstmtFROM@sql;EXECUTEstmt;DROPPREPAREstmt;--initialiseresulttableDROPTABLEIFEXISTSnestedsettree;CREATETABLEnestedsettree(topSMALLINT,nodeIDSMALLINT,leftedgeSMALLINT,rightedgeSMALLINT,KEY(nodeID,leftedge,rightedge))ENGINE=HEAP;--rootischildwithNULLparentorparentwhichisnotachildSET@nulls=(SELECTCount(*)FROMtreeWHEREparentIDISNULL);IF@nulls>1THENSETtrees=2;ELSEIF@nulls=1THENSET@root=(SELECTchildIDFROMtreeWHEREparentIDISNULL);DELETEFROMtreeWHEREchildID=@root;ELSESET@sql=CONCAT('SELECTCount(DISTINCTf.',parentcol,')INTO@rootsFROM',edgeTable,'fLEFTJOIN',edgeTable,'tONf.',parentCol,'=','t.',idCol,'WHEREt.',idCol,'ISNULL');PREPAREstmtFROM@sql;EXECUTEstmt;DROPPREPAREstmt;IF@roots<>1THENSETtrees=@roots;ELSESET@sql=CONCAT('SELECTDISTINCTf.',parentCol,'INTO@rootFROM',edgeTable,'fLEFTJOIN',edgeTable,'tONf.',parentCol,'=','t.',idCol,'WHEREt.',idCol,'ISNULL');PREPAREstmtFROM@sql;EXECUTEstmt;DROPPREPAREstmt;ENDIF;ENDIF;IFtrees<>1THENSETmsg=IF(trees=0,"Notreefound","Tablehasmultipletrees");SELECTmsgAS'Cannotcontinue';ELSE--buildnestedsetstreeSETmaxrightedge=2*(1+(SELECT+COUNT(*)FROMtree));INSERTINTOnestedsettreeVALUES(1,@root,1,maxrightedge);WHILEnextedge<maxrightedgeDOSETrows=(SELECTCount(*)FROMnestedsettreesJOINtreetONs.nodeID=t.parentIDANDs.top=current);IFrows>0THENBEGININSERTINTOnestedsettreeSELECTcurrent+1,MIN(t.childID),nextedge,NULLFROMnestedsettreeASsJOINtreeAStONs.nodeID=t.parentIDANDs.top=current;DELETEFROMtreeWHEREchildID=(SELECTnodeIDFROMnestedsettreeWHEREtop=(current+1));SETnextedge=nextedge+1,current=current+1;END;ELSEUPDATEnestedsettreeSETrightedge=nextedge,top=-topWHEREtop=current;SETnextedge=nextedge+1,current=current-1;ENDIF;ENDWHILE;--showresultIF(SELECTCOUNT(*)FROMtree)>0THENSELECT'Orphanedrowsremain'AS'Error';ENDIF;DROPTEMPORARYTABLEtree;ENDIF;END;goDELIMITER;CALLEdgeListToNestedSet('familytree','childID','parentID');SELECTnodeID,PersonName(nodeID)ASName,ABS(top)AS'TreeLevel',leftedgeAS'Left',rightedgeAS'Right'FROMnestedsettreeORDERBYnodeID;+--------+----------------------+------------+------+-------+|nodeID|Name|TreeLevel|Left|Right|+--------+----------------------+------------+------+-------+|1|RichardShakespeare|1|1|46||2|HenryShakespeare|2|2|43||3|JohnShakespeare|2|44|45||4|JoanShakespeare|3|3|4||5|MargaretShakespeare|3|5|6||6|WilliamShakespeare|3|7|24||7|GilbertShakespeare|3|25|26||8|JoanShakespeare|3|27|36||9|AnneShakespeare|3|37|38||10|RichardShakespeare|3|39|40||11|EdmondShakespeare|3|41|42||12|SusanaShakespeare|4|8|13||13|HamnetShakespeare|4|14|15||14|JudithShakespeare|4|16|23||15|WilliamHart|4|28|29||16|MaryHart|4|30|31||17|ThomasHart|4|32|33||18|MichaelHart|4|34|35||19|ElizabethHall|5|9|12||20|ShakespeareQuiney|5|17|18||21|RichardQuiney|5|19|20||22|ThomasQuiney|5|21|22||23|JohnBernard|6|10|11|+--------+----------------------+------------+------+-------+

Verifythefunctionwithaquerythatgeneratesanedgelisttreefromanestedsetstree:

Listing10a:SELECTa.nodeID,b.nodeIDASparentFROMnestedsettreeASaLEFTJOINnestedsettreeASbONb.leftedge=(SELECTMAX(leftedge)FROMnestedsettreeAStWHEREa.leftedge>t.leftedgeANDa.leftedge<t.rightedge)ORDERBYa.nodeID;

Foramulti-treeversionofListing10,implementingedgelistandnestedsetstreemodelsinasingletable,see“Multiplenestedsetstreesinonetable”onour
Queriespage.

Findinganode'sparentandchildren
Inanedgelist,theparentofanodeistherow'sparentID,anditschildrenaretherowswherethatnodeIDisparentID.Whatcouldbesimpler?Incomparison,nestedsetsqueriesforparentsandtheirchildrenaretortuousandslow.Onewaytofetchthe
childnodesofagivennodeistoINNERJOINthenestedsetstreetableASparenttoitselfASchildONchild.leftedgeBETWEENparent.leftedgeANDparent.rightedge,thenscopeonthetargetrow'sleftedgeandrightedgevalues.Intheresultinglist,child.nodeID
valuesoneleveldownoccuronceandarechildren,grandkidsaretwolevelsdownandoccurtwice,andsoon:

Listing11SELECTPersonName(child.nodeID)AS'DescendantsofWilliam',COUNT(*)ASGenerationFROMnestedsettreeASparentJOINnestedsettreeASchildONchild.leftedgeBETWEENparent.leftedgeANDparent.rightedgeWHEREparent.leftedge>7ANDparent.rightedge<24--William’sleftedge,rightedgeGROUPBYchild.nodeID;+------------------------+------------+|DescendantsofWilliam|Generation|+------------------------+------------+|SusanaShakespeare|1||HamnetShakespeare|1||JudithShakespeare|1||ElizabethHall|2||ShakespeareQuiney|2||RichardQuiney|2||ThomasQuiney|2||JohnBernard|3|+------------------------+------------+

Therefore
HAVINGCOUNT(t2.nodeID)=1
scopeslisteddescendantstothechildren:

Listing11aSELECTPersonName(child.nodeID)AS'ChildrenofWilliam'FROMnestedsettreeASparentJOINnestedsettreeASchildONchild.leftedgeBETWEENparent.leftedgeANDparent.rightedgeWHEREparent.leftedge>7ANDparent.rightedge<24GROUPBYchild.nodeIDHAVINGCOUNT(child.nodeID)=1+---------------------+|ChildrenofWilliam|+---------------------+|SusanaShakespeare||HamnetShakespeare||JudithShakespeare|+---------------------+

Retrievingasubtreeorasubsetofparentsrequiresyetanotherjoin:

Listing11bSELECTParent,Group_Concat(ChildORDERBYChild)ASChildrenFROM(SELECTmaster.nodeIDASParent,child.nodeIDASChildFROMnestedsettreeASmasterJOINnestedsettreeASparentJOINnestedsettreeASchildONchild.leftedgeBETWEENparent.leftedgeANDparent.rightedgeWHEREparent.leftedge>master.leftedgeANDparent.rightedge<master.rightedgeGROUPBYmaster.nodeID,child.nodeIDHAVINGCOUNT(*)=1)AStmpWHEREparentin(6,8,12,14)GROUPBYParent;+--------+-------------------+|Parent|Children|+--------+-------------------+|6|12,13,14||8|15,16,17,18||12|19||14|20,21,22|+--------+-------------------+

Thistakeshundredsoftimeslongerthanaqueryforthesameinfofromanedgelist!Anaggregatingversionof

Listing19iseasiertowrite,butisanevenworseperformer:

Listing11cSELECTp.nodeIDASParent,Group_Concat(c.nodeID)ASChildrenFROMnestedsettreeASpJOINnestedsettreeAScONp.leftedge=(SELECTMAX(s.leftedge)FROMnestedsettreeASsWHEREc.leftedge>s.leftedgeANDc.leftedge<s.rightedge)GROUPBYParent;+--------+-------------------+|Parent|Children|+--------+-------------------+|1|2,3||2|5,6,7,8,9,10,11,4||6|12,13,14||8|15,16,17,18||12|19||14|20,21,22||19|23|+--------+-------------------+

LogicthatisreciprocaltothatofListing11agetsustheparentofanode:

retrieveits
leftedge
and
rightedge
values,
computeitslevel,
findthenodewhichisonelevelupandhasedgevaluesoutsidethenode's
leftedge
and
rightedge
values.

Listing12DROPPROCEDUREIFEXISTSShowNestedSetParent;DELIMITERgoCREATEPROCEDUREShowNestedSetParent(nodeSMALLINT)BEGINDECLARElevel,thisleft,thisrightSMALLINTDEFAULT0;--findnodeedgesSELECTleftedge,rightedgeINTOthisleft,thisrightFROMnestedsettreeWHEREnodeID=node;--findnodelevelSELECTCOUNT(n1.nodeid)INTOlevelFROMnestedsettreeASn1INNERJOINnestedsettreeASn2ONn2.leftedgeBETWEENn1.leftedgeANDn1.rightedgeWHEREn2.nodeid=nodeGROUPBYn2.nodeid;--findparentSELECTPersonName(n2.nodeid)ASParentFROMnestedsettreeASn1INNERJOINnestedsettreeASn2ONn2.leftedgeBETWEENn1.leftedgeANDn1.rightedgeWHEREn2.leftedge<thisleftANDn2.rightedge>thisrightGROUPBYn2.nodeidHAVINGCOUNT(n1.nodeid)=level-1;END;goDELIMITER;CALLShowNestedSetParent(6);+-------------------+|Parent|+-------------------+|HenryShakespeare|+-------------------+

Otherqueries
Forsomequeryproblems,edgelistandnestedsetsqueriesareequivalentlysimple.Forexampletofindthetreerootandleaves,compare

Listing6with:

Listing13SELECTnameASPaterfamilias,IFNULL(born,'?')ASBorn,IFNULL(died,'?')ASDiedFROMnestedsettreeAStINNERJOINfamilyASfONt.nodeID=f.IDWHEREleftedge=1;+---------------------+------+------+|Paterfamilias|Born|Died|+---------------------+------+------+|RichardShakespeare|?|1561|+---------------------+------+------+SELECTnameAS'ChildlessShakespeares',IFNULL(born,'?')ASBorn,IFNULL(died,'?')ASDiedFROMnestedsettreeAStINNERJOINfamilyASfONt.nodeID=f.IDWHERErightedge=leftedge+1;+------------------------+------+------+|ChildlessShakespeares|Born|Died|+------------------------+------+------+|JoanShakespeare|1558|?||MargaretShakespeare|1562|1563||JohnBernard|?|1674||HamnetShakespeare|1585|1596||ShakespeareQuiney|1616|1617||RichardQuiney|1618|1639||ThomasQuiney|1620|1639||GilbertShakespeare|1566|1612||WilliamHart|1600|1639||MaryHart|1603|1607||ThomasHart|1605|1670||MichaelHart|1608|1618||AnneShakespeare|1571|1579||RichardShakespeare|1574|1613||EdmondShakespeare|1580|1607||JohnShakespeare|1530|1601|+------------------------+------+------+

Findingsubtreesinanestedsetsmodelrequiresnotwistedcode,nostoredprocedure.Toretrievethe
nestedsettree
nodesinWilliam'ssubtree,justaskfornodeswhose
leftedge
valuesaregreater,andwhose
rightedge
valuesaresmallerthanWilliam's:

Listing14SELECTPersonName(t.nodeID)ASDescendantFROMnestedsettreeASsJOINnestedsettreeAStONs.leftedge<t.leftedgeANDs.rightedge>t.rightedgeJOINfamilyfONs.nodeID=f.IDWHEREf.name='WilliamShakespeare';

Findingasinglepathinthenestedsetsmodelisaboutascomplicatedasedgelistpathenumeration(Listings

8,
9):

Listing15SELECTt2.nodeIDASNode,PersonName(t2.nodeID)ASPerson,(SELECTCOUNT(*)FROMnestedsettreeASt4WHEREt4.leftedgeBETWEENt1.leftedgeANDt1.rightedgeANDt2.leftedgeBETWEENt4.leftedgeANDt4.rightedge)ASPathFROMnestedsettreeASt1INNERJOINnestedsettreeASt2ONt2.leftedgeBETWEENt1.leftedgeANDt1.rightedgeINNERJOINnestedsettreeASt3ONt3.leftedgeBETWEENt2.leftedgeANDt2.rightedgeWHEREt1.nodeID=(SELECTIDFROMfamilyWHEREname='WilliamShakespeare')ANDt3.nodeID=(SELECTIDFROMfamilyWHEREname='JohnBernard');+------+---------------------+------+|Node|Person|Path|+------+---------------------+------+|6|WilliamShakespeare|1||12|SusanaShakespeare|2||19|ElizabethHall|3||23|JohnBernard|4|+------+---------------------+------+

Displayingthetree
Herethenestedsetsmodelshines.Thearithmeticthatwasusedtobuildthetreemakesshortworkofsummaryqueries.Forexampletoretrieveanodelistwhichpreservesallparent-childrelations,weneedjusttwofacts:

listingorderistheordertakeninthenodewalkthatcreatedthetree,i.e.
leftedge
,
anode'sindentationdepthissimplytheJOIN(edge)countfromroottonode:

Listing16SELECTCONCAT(SPACE(2*COUNT(parent.nodeid)-2),PersonName(child.nodeid))AS'TheShakespeareFamilyTree'FROMnestedsettreeASparentINNERJOINnestedsettreeASchildONchild.leftedgeBETWEENparent.leftedgeANDparent.rightedgeGROUPBYchild.nodeidORDERBYchild.leftedge;+-----------------------------+|TheShakespeareFamilyTree|+-----------------------------+|RichardShakespeare||HenryShakespeare||JoanShakespeare||MargaretShakespeare||WilliamShakespeare||SusanaShakespeare||ElizabethHall||JohnBernard||HamnetShakespeare||JudithShakespeare||ShakespeareQuiney||RichardQuiney||ThomasQuiney||GilbertShakespeare||JoanShakespeare||WilliamHart||MaryHart||ThomasHart||MichaelHart||AnneShakespeare||RichardShakespeare||EdmondShakespeare||JohnShakespeare|+-----------------------------+

Toretrieveonlyasubtree,addaqueryclausewhichrestrictsnodestothosewhoseedgesarewithintherangeoftheparentnode'sleftandrightedgevalues,forexampleforWilliamandhisdescendants...

WHEREparent.leftedge>=7ANDparent.rightedge<=24


Nodeinsertions,updatesanddeletions
Nestedsetsarithmeticalsohelpswithinsertions,updatesanddeletions,buttheproblemremainsthatchangingjustonenodecanrequirechangingmuchofthetree.

Insertinganodeinthetreerequiresatleasttwopiecesofinformation:the
nodeID
toinsert,andthe
nodeID
ofitsparent.Themodelisnormalisedsothe
nodeID
firstmusthavebeenaddedtotheparent(
family
)table.Thealgorithmforaddingthenodetothetreeis:

increment
leftedge
by2innodeswherethe
rightedge
valueisgreaterthantheparent's
rightedge
,
increment
rightedge
by2innodeswherethe
leftedge
valueisgreaterthantheparent's
leftedge
,
insertarowwiththegiven
nodeID
,
leftedge
=1+parent's
leftedge
,
rightedge
=2+parent's
leftedge
.

That'snotdifficult,butallrowswillhavetobeupdated!

Listing17DROPPROCEDUREIFEXISTSInsertNestedSetNode;DELIMITERgoCREATEPROCEDUREInsertNestedSetNode(INnodeSMALLINT,INparentSMALLINT)BEGINDECLAREparentleft,parentrightSMALLINTDEFAULT0;SELECTleftedge,rightedgeINTOparentleft,parentrightFROMnestedsettreeWHEREnodeID=parent;IFFOUND_ROWS()=1THENBEGINUPDATEnestedsettreeSETrightedge=rightedge+2WHERErightedge>parentleft;UPDATEnestedsettreeSETleftedge=leftedge+2WHEREleftedge>parentleft;INSERTINTOnestedsettreeVALUES(0,node,parentleft+1,parentleft+2);END;ENDIF;END;goDELIMITER;

"Sibline"orhorizontalorderisobviouslysignificantinfamilytrees,butmaynotbesignificantinothertrees.Listing17addsthenewnodeattheleftedgeofthesibline.Tospecifyanotherposition,modifytheproceduretoacceptathirdparameter
forthe
nodeID
whichistobetotheleftorrightoftheinsertionpoint.

Updatinganodeinplacerequiresnothingmorethanediting
nodeID
topointatadifferentparentrow.

Deletinganoderaisestheproblemofhowtorepairlinksseveredbythedeletion.Intreemodelsofpartsexplosions,theitemtobedeletedisoftenreplacedbyanewitem,soitcanbetreatedlikeasimple
nodeID
update.Inorganisationalboss-employeecharts,though,doesacolleaguemoveover,doesasubordinategetpromoted,doeseverybodyinthesubtreemoveupalevel,ordoessomethingelsehappen?Noformulacancatchallthepossibilities.
Listing18illustrateshowtohandletwocommonscenarios,moveeveryoneup,andmovesomeoneover.Allpossibilitiesexceptsimplereplacementofthe
nodeID
involvechangeselsewhereinthetree.

Listing18DROPPROCEDUREIFEXISTSDeleteNestedSetNode;DELIMITERgoCREATEPROCEDUREDeleteNestedSetNode(INmodeCHAR(7),INnodeSMALLINT)BEGINDECLAREthisleft,thisrightSMALLINTDEFAULT0;SELECTleftedge,rightedgeINTOthisleft,thisrightFROMnestedsettreeWHEREnodeID=node;IFmode='PROMOTE'THENBEGIN--IanHolsmanfoundthesetwobugsDELETEFROMnestedsettreeWHEREnodeID=node;UPDATEnestedsettreeSETleftedge=leftedge-1,rightedge=rightedge-1--ratherthan=thisleftWHEREleftedgeBETWEENthisleftANDthisright;UPDATEnestedsettreeSETrightedge=rightedge-2WHERErightedge>thisright;UPDATEnestedsettreeSETleftedge=leftedge-2WHEREleftedge>thisright;--ratherthan>thisleftEND;ELSEIFmode='REPLACE'THENBEGINUPDATEnestedsettreeSETleftedge=thisleft-1,rightedge=thisrightWHEREleftedge=thisleft+1;UPDATEnestedsettreeSETrightedge=rightedge-2WHERErightedge>thisleft;UPDATEnestedsettreeSETleftedge=leftedge-2WHEREleftedge>thisleft;DELETEFROMnestedsettreeWHEREnodeID=node;END;ENDIF;END;goDELIMITER;

Nestedssetmodelsummary
Somenestedsetsqueriesarequickerthantheiredgelistcounterparts,somearen't.Giventheconcurrencynightmarewhichnestedsetsimposeforinsertsanddeletions,itisreasonabletoreservethenestedsetsmodelforfairlystatictreeswhoseusers
aremostlyinterestedinqueryingsubtrees.YoucouldthinkofthenestedsetsmodelasaspecialisedOLAPtool:maintainanOLTPtreeinanedgelistrepresentation,andbuildanestedsetsOLAPtablewhencertainreportsareneeded.

Ifyouwillbeusingthenestedsetsmodel,youmaybeconvertingbackandforthwithedgelistmodels,sohereisasimplequerywhichgeneratesanedgelistfromanestedsetstree:

Listing19SELECTp.nodeIDASparentID,c.nodeIDASchildIDFROMnestedsettreeASpINNERJOINnestedsettreeAScONp.leftedge=(SELECTMAX(s.leftedge)FROMnestedsettreeASsWHEREc.leftedge>s.leftedgeANDc.leftedge<s.rightedge)ORDERBYp.nodeID;

Edgelistmodelofanon-treegraph

Manygraphsarenottrees.Imagineasmallairlinewhichhasjustacquiredlicencesforflightsnolongerthan6,000kmbetweenLosAngeles(LAX),NewYork(JFK),HeathrowinLondon,CharlesdeGaulleinParis,Amsterdam-Schiphol,ArlandainSweden,and
Helsinki-Vantaa.Youhavebeenaskedtocomputetheshortestpossibleone-wayroutesthatdonotdeviatemorethan90°fromthedirectionofthefirsthop—roughly,one-wayroutesandnocircuits.

Airportsarenodes,flightsareedges,routesarepaths.Wewillneedthreetables.

Airports(nodes)
Toidentifyanairportweneeditscode,locationname,latitudeandlongitude.Latitudeandlongitudeareusuallygivenasdegrees,minutesandseconds,northorsouthoftheequator,eastorwestofGreenwich.Tohidedetailsthataren'tdirectlyrelevant
tonodesandedges,codelatitudeandlongitudeassimplerealswherelongitudeswestofGreenwichandlatitudessouthoftheequatorarenegative,whilstlongitudeseastofGreenwichandlatitudesnorthoftheequatorarepositive:

Listing20CREATETABLEairports(codechar(3)NOTNULL,citychar(100)defaultNULL,latitudefloatNOTNULL,longitudefloatNOTNULL,PRIMARYKEY(code))ENGINE=MyISAM;INSERTINTOairportsVALUES('JFK','NewYork,NY',40.75,-73.97);INSERTINTOairportsVALUES('LAX','LosAngeles,CA',34.05,-118.22);INSERTINTOairportsVALUES('LHR','London,England',51.5,-0.45);INSERTINTOairportsVALUES('HEL','Helsinki,Finland',60.17,24.97);INSERTINTOairportsVALUES('CDG','Paris,France',48.86,2.33);INSERTINTOairportsVALUES('STL','StLouis,MO',38.63,-90.2);INSERTINTOairportsVALUES('ARN','Stockholm,Sweden',59.33,18.05);

Flights(edges)
Themodelattachestwoweightstoflights:distanceanddirection.

WeneedamethodofcalculatingtheGreatCircleDistance—thegeographicaldistancebetweenanytwocities-anothernaturaljobforastoredfunction.Thedistancecalculation

convertstoradiansthedegreecoordinatesofanytwopointsontheearth'ssurface,

calculatestheangleofthearcsubtendedbythetwopoints,and
convertstheresult,alsoinradians,tosurface(circumferential)kilometres(1radian=6,378.388km).

Listing21SETGLOBALlog_bin_trust_function_creators=TRUE;--since5.0.16DROPFUNCTIONIFEXISTSGeoDistKM;DELIMITERgoCREATEFUNCTIONGeoDistKM(lat1FLOAT,lon1FLOAT,lat2FLOAT,lon2FLOAT)RETURNSfloatBEGINDECLAREpi,q1,q2,q3FLOAT;SETpi=PI();SETlat1=lat1*pi/180;SETlon1=lon1*pi/180;SETlat2=lat2*pi/180;SETlon2=lon2*pi/180;SETq1=COS(lon1-lon2);SETq2=COS(lat1-lat2);SETq3=COS(lat1+lat2);SETrads=ACOS(0.5*((1.0+q1)*q2-(1.0-q1)*q3));RETURN6378.388*rads;END;goDELIMITER;

Thattakescareofflightdistances.Flightdirectionis,approximately,thearctangent(ATAN)ofthedifferencebetween
flights.depart
and
flights.arrive
latitudesandlongitudes.Nowwecanseedtheairline's
flights
tablewithone-hopflightsupto6,000kmlong:

Listing22CREATETABLEflights(idINTPRIMARYKEYAUTO_INCREMENT,departCHAR(3),arriveCHAR(3),distanceDECIMAL(10,2),directionDECIMAL(10,2))ENGINE=MYISAM;INSERTINTOflightsSELECTNULL,depart.code,arrive.code,ROUND(GeoDistKM(depart.latitude,depart.longitude,arrive.latitude,arrive.longitude),2),ROUND(DEGREES(ATAN(arrive.latitude-depart.latitude,arrive.longitude-depart.longitude)),2)FROMairportsASdepartINNERJOINairportsASarriveONdepart.code<>arrive.codeHAVINGKm<=6000;SELECT*FROMflights;+----+--------+--------+----------+-----------+|id|depart|arrive|distance|direction|+----+--------+--------+----------+-----------+|1|LAX|JFK|3941.18|8.61||2|LHR|JFK|5550.77|-171.68||3|CDG|JFK|5837.46|-173.93||4|STL|JFK|1408.11|7.44||5|JFK|LAX|3941.18|-171.39||6|STL|LAX|2553.37|-170.72||7|JFK|LHR|5550.77|8.32||8|HEL|LHR|1841.91|-161.17||9|CDG|LHR|354.41|136.48||10|ARN|LHR|1450.12|-157.06||11|LHR|HEL|1841.91|18.83||12|CDG|HEL|1912.96|26.54||13|ARN|HEL|398.99|6.92||14|JFK|CDG|5837.46|6.07||15|LHR|CDG|354.41|-43.52||16|HEL|CDG|1912.96|-153.46||17|ARN|CDG|1545.23|-146.34||18|JFK|STL|1408.11|-172.56||19|LAX|STL|2553.37|9.28||20|LHR|ARN|1450.12|22.94||21|HEL|ARN|398.99|-173.08||22|CDG|ARN|1545.23|33.66|+----+--------+--------+----------+-----------+

Thedistancesagreeapproximatelywithpublicinformationsourcesforflightlengths.ForapairofairportsAandBnotverynearthepoles,theerrorincalculatingdirectionusingATAN(),issmall.Toremovethaterror,insteadofATAN()useaformula
fromsphericaltrigonometry(forexampleoneoftheformulasathttp://www.dynagen.co.za/eugene/where/formula.html).
Routes(paths)
Arouteisapathalongoneormoreoftheseedges,so
flights:routes
isa1:manyrelationship.Forsimplicity,though,wedenormaliseourrepresentationofrouteswithavariationofthe

materialisedpathmodeltostoreallthehopsofonerouteasalistofflightsinone
routes
column.Thecolumn
routes.route
isthesequenceofairports,fromfirstdeparturetofinalarrival,thecolumn
routes.hops
isthenumberofhopsinthatroute,andthecolumn
routes.direction
isthedirection:

Listing23CREATETABLEroutes(idINTPRIMARYKEYAUTO_INCREMENT,departCHAR(3),arriveCHAR(3),hopsSMALLINT,routeCHAR(50),distanceDECIMAL(10,2),directionDECIMAL(10,2))ENGINE=MYISAM;

Startingwithanempty
routes
table,howdowepopulateitwiththeshortest
routes
betweenallorderedpairsof
airports
?

Insertall1-hopflightsfromthe
flights
table.
Addinthesetofshortestmulti-hoproutesforallpairsofairportswhichdon'thaverowsintheflightstable.

For1-hopflightswejustwrite

Listing24INSERTINTOroutesSELECTNULL,depart,arrive,1,CONCAT(depart,',',arrive),distance,directionFROMflights;

NULLbeingtheplaceholderfortheauto-incrementing
id
column.

Formulti-hoproutes,weiterativelyaddinsetsofallallowed2-hop,3-hop,...n-hoproutes,replacinglongerroutesbyshorterroutesaswefindthem,untilthereisnothingmoretoaddorreplace.Thatalsobreaksdowntotwologicalsteps:addhopsto
buildthesetofnextallowedroutes,andupdatelongerrouteswithshorterones.

Nextallowedroutes
Thesetofnextallowedroutesisthesetofshortestroutesthatcanbebuiltbyadding,toexistingroutes,flightswhichleavefromthelastarrivalairportofanexistingroute,whicharriveatanairportwhichisnotyetinthegivenroute,andwhich
staywithin±90°oftheroute'sinitialcompassdirection.Thatis,everynewrouteisaJOINbetween
routes
and
flights
inwhich

depart=routes.depart
,
arrive=flights.arrive
,
flights.depart=routes.arrive
,
distance=
MIN(routes.distance+flights.distance)
,
LOCATE(
flights.arrive,routes.route)=0
,

flights.direction+360>routes.direction+270ANDflights.direction+360<routes.direction+450


ThisisanaturallogicalunitofworkforaVIEW:

Listing25CREATEORREPLACEVIEWnextroutesASSELECTroutes.depart,flights.arrive,routes.hops+1AShops,CONCAT(routes.route,',',flights.arrive)ASroute,MIN(routes.distance+flights.distance)ASdistance,routes.directionFROMroutesINNERJOINflightsONroutes.arrive=flights.departAND[code]LOCATE(
flights.arrive,routes.route)=0
WHEREflights.direction+360>routes.direction+270ANDflights.direction+360<routes.direction+450GROUPBYdepart,arrive;[/code]
Howtoaddthesenewhopsto
routes
?InstandardSQL,thisvariantonaquerybyScottStephensshoulddoit...

Listing26INSERTINTOroutesSELECTNULL,depart,arrive,hops,route,distance,directionFROMnextroutesWHERE(nextroutes.depart,nextroutes.arrive)NOTIN(SELECTdepart,arriveFROMroutes);

butMySQLdoesnotyetsupportsubqueriesonthetablebeingupdated.Wehavetouseasubquery-less(andfaster)versionofthatlogic:

Listing27INSERTINTOroutesSELECTNULL,nextroutes.depart,nextroutes.arrive,nextroutes.hops,nextroutes.route,nextroutes.distance,nextroutes.directionFROMnextroutesLEFTJOINroutesONnextroutes.depart=routes.departANDnextroutes.arrive=routes.arriveWHEREroutes.departISNULLANDroutes.arriveISNULL;

Runningthatcoderightaftertheinitialseedingfrom
flights
gives...

SELECT*FROMroutes;+----+--------+--------+------+-------------+----------+-----------+|id|depart|arrive|hops|route|distance|direction|+----+--------+--------+------+-------------+----------+-----------+|1|LAX|JFK|1|LAX,JFK|3941.18|8.61||2|LHR|JFK|1|LHR,JFK|5550.77|-171.68||3|CDG|JFK|1|CDG,JFK|5837.46|-173.93||4|STL|JFK|1|STL,JFK|1408.11|7.44||5|JFK|LAX|1|JFK,LAX|3941.18|-171.39||6|STL|LAX|1|STL,LAX|2553.37|-170.72||7|JFK|LHR|1|JFK,LHR|5550.77|8.32||8|HEL|LHR|1|HEL,LHR|1841.91|-161.17||9|CDG|LHR|1|CDG,LHR|354.41|136.48||10|ARN|LHR|1|ARN,LHR|1450.12|-157.06||11|LHR|HEL|1|LHR,HEL|1841.91|18.83||12|CDG|HEL|1|CDG,HEL|1912.96|26.54||13|ARN|HEL|1|ARN,HEL|398.99|6.92||14|JFK|CDG|1|JFK,CDG|5837.46|6.07||15|LHR|CDG|1|LHR,CDG|354.41|-43.52||16|HEL|CDG|1|HEL,CDG|1912.96|-153.46||17|ARN|CDG|1|ARN,CDG|1545.23|-146.34||18|JFK|STL|1|JFK,STL|1408.11|-172.56||19|LAX|STL|1|LAX,STL|2553.37|9.28||20|LHR|ARN|1|LHR,ARN|1450.12|22.94||21|HEL|ARN|1|HEL,ARN|398.99|-173.08||22|CDG|ARN|1|CDG,ARN|1545.23|33.66||23|ARN|JFK|2|ARN,LHR,JFK|7000.89|-157.06||24|CDG|LAX|2|CDG,JFK,LAX|9778.64|-173.93||25|CDG|STL|2|CDG,JFK,STL|7245.57|-173.93||26|HEL|JFK|2|HEL,LHR,JFK|7392.68|-161.17||27|JFK|ARN|2|JFK,LHR,ARN|7000.89|8.32||28|JFK|HEL|2|JFK,LHR,HEL|7392.68|8.32||29|LAX|CDG|2|LAX,JFK,CDG|9778.64|8.61||30|LAX|LHR|2|LAX,JFK,LHR|9491.95|8.61||31|LHR|LAX|2|LHR,JFK,LAX|9491.95|-171.68||32|LHR|STL|2|LHR,JFK,STL|6958.88|-171.68||33|STL|CDG|2|STL,JFK,CDG|7245.57|7.44||34|STL|LHR|2|STL,JFK,LHR|6958.88|7.44|+----+--------+--------+------+-------------+----------+-----------+

...adding12two-hoprows.

Replacelongerrouteswithshorterones
Aswebuildrouteswithmorehops,itislogicallypossiblethatthe
nextroutes
viewwillfindshorterroutesforanexisting
routes
pairof
depart
and
arrive
.StandardSQLforreplacingexisting
routes
rowswith
nextroutes
rowswhichmatch
(depart,arrive)
andhaveshorter
distance
valueswouldbe:

Listing28UPDATEroutesSET(hops,route,distance,direction)=(SELECThops,route,distance,directionFROMnextroutesWHEREnextroutes.depart=routes.departANDnextroutes.arrive=routes.arrive)WHERE(depart,arrive)IN(SELECTdepart,arriveFROMnextroutesWHEREnextroutes.distance<routes.distance);

butMySQLdoesnotsupport
SET(col1,...)
syntax,andaswith
Listing7,MySQLdoesnotyetacceptsubqueriesreferencingthetablebeingupdated,sowehavetowritemoreliteralSQL:

Listing29UPDATEroutes,nextroutesSETroutes.hops=nextroutes.hops,routes.route=nextroutes.route,routes.distance=nextroutes.distance,routes.direction=nextroutes.directionWHEREroutes.arrive=nextroutes.arriveANDroutes.depart=nextroutes.departANDnextroutes.distance<routes.distance;

RunningthiscoderightafterthefirstrunofListing27updatesnorows.Totestthelogicofiteration,continuerunningListings27and29untilnorowsarebeingaddedorchanged.Thefinalresultis:

SELECT*FROMROUTES;+----+--------+--------+------+-----------------+----------+-----------+|id|depart|arrive|hops|route|distance|direction|+----+--------+--------+------+-----------------+----------+-----------+|1|LAX|JFK|1|LAX,JFK|3941.18|8.61||2|LHR|JFK|1|LHR,JFK|5550.77|-171.68||3|CDG|JFK|1|CDG,JFK|5837.46|-173.93||4|STL|JFK|1|STL,JFK|1408.11|7.44||5|JFK|LAX|1|JFK,LAX|3941.18|-171.39||6|STL|LAX|1|STL,LAX|2553.37|-170.72||7|JFK|LHR|1|JFK,LHR|5550.77|8.32||8|HEL|LHR|1|HEL,LHR|1841.91|-161.17||9|CDG|LHR|1|CDG,LHR|354.41|136.48||10|ARN|LHR|1|ARN,LHR|1450.12|-157.06||11|LHR|HEL|1|LHR,HEL|1841.91|18.83||12|CDG|HEL|1|CDG,HEL|1912.96|26.54||13|ARN|HEL|1|ARN,HEL|398.99|6.92||14|JFK|CDG|1|JFK,CDG|5837.46|6.07||15|LHR|CDG|1|LHR,CDG|354.41|-43.52||16|HEL|CDG|1|HEL,CDG|1912.96|-153.46||17|ARN|CDG|1|ARN,CDG|1545.23|-146.34||18|JFK|STL|1|JFK,STL|1408.11|-172.56||19|LAX|STL|1|LAX,STL|2553.37|9.28||20|LHR|ARN|1|LHR,ARN|1450.12|22.94||21|HEL|ARN|1|HEL,ARN|398.99|-173.08||22|CDG|ARN|1|CDG,ARN|1545.23|33.66||23|ARN|JFK|2|ARN,LHR,JFK|7000.89|-157.06||24|CDG|LAX|2|CDG,JFK,LAX|9778.64|-173.93||25|CDG|STL|2|CDG,JFK,STL|7245.57|-173.93||26|HEL|JFK|2|HEL,LHR,JFK|7392.68|-161.17||27|JFK|ARN|2|JFK,LHR,ARN|7000.89|8.32||28|JFK|HEL|2|JFK,LHR,HEL|7392.68|8.32||29|LAX|CDG|2|LAX,JFK,CDG|9778.64|8.61||30|LAX|LHR|2|LAX,JFK,LHR|9491.95|8.61||31|LHR|LAX|2|LHR,JFK,LAX|9491.95|-171.68||32|LHR|STL|2|LHR,JFK,STL|6958.88|-171.68||33|STL|CDG|2|STL,JFK,CDG|7245.57|7.44||34|STL|LHR|2|STL,JFK,LHR|6958.88|7.44||35|ARN|LAX|3|ARN,LHR,JFK,LAX|10942.07|-157.06||36|ARN|STL|3|ARN,LHR,JFK,STL|8409.00|-157.06||37|HEL|LAX|3|HEL,LHR,JFK,LAX|11333.86|-161.17||38|HEL|STL|3|HEL,LHR,JFK,STL|8800.79|-161.17||39|LAX|ARN|3|LAX,JFK,CDG,ARN|10942.07|8.61||40|LAX|HEL|3|LAX,JFK,CDG,HEL|11333.86|8.61||41|STL|ARN|3|STL,JFK,CDG,ARN|8409.00|7.44||42|STL|HEL|3|STL,JFK,CDG,HEL|8800.79|7.44|+----+--------+--------+------+-----------------+----------+-----------+

Allthat'slefttodoistoassemblethecodeinastoredprocedure:

Listing30DROPPROCEDUREIFEXISTSBuildRoutes;DELIMITERgoCREATEPROCEDUREBuildRoutes()BEGINDECLARErowsINTDEFAULT0;TRUNCATEroutes;--STEP1,LISTING24:SEEDROUTESWITH1-HOPFLIGHTSINSERTINTOroutesSELECTNULL,depart,arrive,1,CONCAT(depart,',',arrive),distance,directionFROMflights;SETrows=ROW_COUNT();WHILE(rows>0)DO--STEP2,LISTINGS25,27:ADDNEXTSETOFROUTESINSERTINTOroutesSELECTNULL,nextroutes.depart,nextroutes.arrive,nextroutes.hops,nextroutes.route,nextroutes.distance,nextroutes.directionFROMnextroutesLEFTJOINroutesONnextroutes.depart=routes.departANDnextroutes.arrive=routes.arriveWHEREroutes.departISNULLANDroutes.arriveISNULL;SETrows=ROW_COUNT();ENDWHILE;END;goDELIMITER;

InMySQL5.0.6or5.0.7,
BuildRoutes()
failstoinsertfourrows:

+--------+--------+-----------------+------+----------+-----------+

|depart|arrive|route|hops|distance|direction|

+--------+--------+-----------------+------+----------+-----------+

|ARN|LAX|ARN,LHR,JFK,LAX|3|10942.07|-157.06|

|ARN|STL|ARN,LHR,JFK,STL|3|8409.00|-157.06|

|HEL|LAX|HEL,LHR,JFK,LAX|3|11333.86|-161.17|

|HEL|STL|HEL,LHR,JFK,STL|3|8800.79|-161.17|

+--------+--------+-----------------+------+----------+-----------+


ThatMySQLbug(#11302)wascorrectedin5.0.9.

Routequeries
Routequeriesarestraightforward.Howdowecheckthatthealgorithmproducednoduplicate
depart-arrive
pairs?Thefollowingqueryshouldyieldzerorows,

Listing31SELECTdepart,arrive,COUNT(*)FROMroutesGROUPBYdepart,arriveHAVINGCOUNT(*)>1;

anddoes.Reachabilityqueriesarejustassimple,forexamplewherecanweflytofromHelsinki?

Listing32SELECT*FROMroutesWHEREdepart='HEL'ORDERBYdistance;+----+--------+--------+------+-----------------+----------+-----------+|id|depart|arrive|hops|route|distance|direction|+----+--------+--------+------+-----------------+----------+-----------+|21|HEL|ARN|1|HEL,ARN|398.99|-173.08||8|HEL|LHR|1|HEL,LHR|1841.91|-161.17||16|HEL|CDG|1|HEL,CDG|1912.96|-153.46||26|HEL|JFK|2|HEL,LHR,JFK|7392.68|-161.17||38|HEL|STL|3|HEL,LHR,JFK,STL|8800.79|-161.17||37|HEL|LAX|3|HEL,LHR,JFK,LAX|11333.86|-161.17|+----+--------+--------+------+-----------------+----------+-----------+

Anextendededgelistmodelissimpletoimplement,gracefullyacceptsextendedattributesfornodes,edgeandpaths,doesnotundulypenaliseupdates,andrespondstoquerieswithreasonablespeed.

Partsexplosions

Abillofmaterialsforahousewouldincludethecementblock,lumber,shingles,doors,wallboard,windows,plumbing,electricalsystem,heatingsystem,andsoon.Eachsubassemblyalsohasabillofmaterials;theheatingsystemhasafurnace,ducts,and
soon.Abillofmaterialsimplosionlinkscomponentpiecestoamajorassembly.Abillofmaterialsexplosionbreaksapartassembliesandsubassembliesintotheircomponentparts.

Whichgraphmodelbesthandlesapartsexplosion?Combiningedgelistand"nestedsets"algorithmsseemsanaturalsolution.

Imagineanewcompanythatplanstomakevariouslysizedbookcases,eitherpackagedasdo-it-yourselfkitsof,orassembledfromsides,shelves,shelfbrackets,backboards,feetandscrews.Shelvesandsidesarecutfromplanks.Backboardsaretrimmedfrom
laminatedsheeting.Feetaremachine-carvedfromreadycutblocks.Screwsandshelfbracketsarepurchasedinbulk.Herearetheelementsofonebookcase:

1backboard,2x1m1laminate8screws2sides2mx30cm1planklength4m12screws8shelves1mx30cm(incl.topandbottom)2planks24shelfbrackets4feet4cmx4cm4cubes16screws

whichmaybepackagedinaboxforsaleatoneprice,orassembledasafinishedproductatadifferentprice.Atanytimeweneedtobeabletoanswerquestionslike

Dowehaveenoughpartstomakethebookcasesonorder?
Whatassembliesorpackageswouldbemostprofitabletomakegiventhecurrentinventory?

Thereisnoreasontobreakthenormalisingrulethatitemdetailbelongsina
nodestable,andgraphlogicbelongsinanedgestable.Edgesalsorequireaquantityattribute,forexampleashelfincludesfourshelfbrackets.Nodesandedgesmayalsohavecostsandprices:

itempurchasecost,
itemassemblycost,
assemblycost,
assemblysellingprice.

Inmanypartsproblemslikethisone,itemsoccurinmultipleassembliesandsubassemblies.Thegraphisnotatree.Also,itisoftendesirabletomodelmultiplegraphswithoutthetableglutthatwouldarisefromgivingeachgraphitsownedgestable.
Asimplewaytosolvethisproblemistorepresentmultiplegraphs(assemblies)intheedgestablebygivingeveryrownotonly
childID
and
parentID
pointers,butapointerwhichidentifiestheroot
itemID
ofthegraphtowhichtherowbelongs.

Sothedatamodelisjusttwotables,foritems(nodes)andforproductgraphsorassemblies(edges).Assumethatthecompanybeginswithaplantosellthe2mx1mbookcaseintwoforms,assembledandkit,andthatthepurchasingdepartment
hasboughtquantitiesofrawmaterials(laminate,planks,shelfsupports,screws,woodcubes,boxes).Herearethenodes(items)andedges(assemblies):

Listing33CREATETABLEitems(itemIDINTPRIMARYKEYAUTO_INCREMENT,nameCHAR(20)NOTNULL,onhandINTNOTNULLDEFAULT0,reservedINTNOTNULLDEFAULT0,purchasecostDECIMAL(10,2)NOTNULLDEFAULT0,assemblycostDECIMAL(10,2)NOTNULLDEFAULT0,priceDECIMAL(10,2)NOTNULLDEFAULT0);

CREATETABLEassemblies(

assemblyIDINTNOTNULL,

assemblyrootINTNOTNULL,

childIDINTNOTNULL,

parentIDINTNOTNULL,

quantityDECIMAL(10,2)NOTNULL,

assemblycostDECIMAL(10,2)NOTNULL,

PRIMARYKEY(assemblyID,childID,parentID)

);

INSERTINTOitemsVALUES--inventory

(1,'laminate',40,0,4,0,8),

(2,'screw',1000,0,0.1,0,.2),

(3,'plank',200,0,10,0,20),

(4,'shelfbracket',400,0,0.20,0,.4),

(5,'woodcube',100,0,0.5,0,1),

(6,'box',40,0,1,0,2),

(7,'backboard',0,0,0,3,0),

(8,'side',0,0,0,8,0),

(9,'shelf',0,0,0,4,0),

(10,'foot',0,0,0,1,0),

(11,'bookcase2x30',0,0,0,10,0),

(12,'bookcase2x30kit',0,0,0,2,0);

INSERTINTOassembliesVALUES

(1,11,1,7,1,0),--laminatetobackboard

(2,11,2,7,8,0),--screwstobackboard

(3,11,3,8,.5,0),--plankstoside

(4,11,2,8,6,0),--screwstoside

(5,11,3,9,0.25,0),--plankstoshelf

(6,11,4,9,4,0),--shelfbracketstoshelf

(7,11,5,10,1,0),--woodcubestofoot

(8,11,2,10,1,0),--screwstofoot

(9,11,7,11,1,0),--backboardtobookcase

(10,11,8,11,2,0),--sidestobookcase

(11,11,9,11,8,0),--shelvestobookcase

(12,11,10,11,4,0),--feettobookcase

(13,12,1,7,1,0),--laminatetobackboard

(14,12,2,7,8,0),--screwstobackboard

(15,12,3,8,0.5,0),--plankstoside

(16,12,2,8,6,0),--screwstosides

(17,12,3,9,0.25,0),--plankstoshelf

(18,12,4,9,4,0),--shelfbracketstoshelves

(19,12,5,10,1,0),--woodcubestofoot

(20,12,2,10,1,0),--screwstofoot

(21,12,7,12,1,0),--backboardtobookcasekit

(22,12,8,12,2,0),--sidestobookcasekit

(23,12,9,12,8,0),--shelvestobookcasekit

(24,12,10,12,4,0),--feettobookcasekit

(25,12,6,12,1,0);--containerboxtobookcasekit

Now,wewantapartslist,abillofmaterials,whichwilllistshowparent-childrelationshipsandquantities,andsumthecosts.Couldweadaptthedepth-first"nestedsets"treewalkalgorithm(Listing
10)tothisproblemeventhoughourgraphisnotatreeandoursetsarenotproperlynested?Yesindeed.Wejusthavetomodifythetreewalktohandlemultipleparentnodesforanychildnode,andaddcodetopercolatecostsandquantitiesupthegraph.
Navigationremainssimpleusing
leftedge
and
rightedge
values.ThisisjustthesortofproblemtheCelkoalgorithmisgoodfor:reporting!

Listing34DROPPROCEDUREIFEXISTSShowBOM;DELIMITERgoCREATEPROCEDUREShowBOM(INrootINT)BEGINDECLAREthischild,thisparent,rows,maxrightedgeINTDEFAULT0;DECLAREthislevel,nextedgenumINTDEFAULT1;DECLAREthisqty,thiscostDECIMAL(10,2);--Createandseedintermediatetable:DROPTABLEIFEXISTSedges;CREATETABLEedges(childIDsmallintNOTNULL,parentIDsmallintNOTNULL,PRIMARYKEY(childID,parentID))ENGINE=HEAP;INSERTINTOedgesSELECTchildID,parentIDFROMassembliesWHEREassemblyRoot=root;SETmaxrightedge=2*(1+(SELECTCOUNT(*)FROMedges));--Createandseedresulttable:DROPTABLEIFEXISTSbom;CREATETABLEbom(levelSMALLINT,nodeIDSMALLINT,parentIDSMALLINT,qtyDECIMAL(10,2),costDECIMAL(10,2),leftedgeSMALLINT,rightedgeSMALLINT)ENGINE=HEAP;INSERTINTObomVALUES(thislevel,root,0,0,0,nextedgenum,maxrightedge);SETnextedgenum=nextedgenum+1;WHILEnextedgenum<maxrightedgeDO--Howmanychildrenofthisnoderemainintheedgestable?SETrows=(SELECTCOUNT(*)FROMbomASsINNERJOINedgesAStONs.nodeID=t.parentIDANDs.level=thislevel);IFrows>0THEN--Thereisatleastonechildedge.--Computeqtyandcost,insertintobom,deletefromedges.BEGIN--AlasMySQLnullsMIN(t.childid)whenwecombinethenexttwoqueriesSETthischild=(SELECTMIN(t.childID)FROMbomASsINNERJOINedgesAStONs.nodeID=t.parentIDANDs.level=thislevel);SETthisparent=(SELECTDISTINCTt.parentIDFROMbomASsINNERJOINedgesAStONs.nodeID=t.parentIDANDs.level=thislevel);SETthisqty=(SELECTquantityFROMassembliesWHEREassemblyroot=rootANDchildID=thischildANDparentID=thisparent);SETthiscost=(SELECTa.assemblycost+(thisqty*(i.purchasecost+i.assemblycost))FROMassembliesASaINNERJOINitemsASiONa.childID=i.itemIDWHEREassemblyroot=rootANDa.parentID=thisparentANDa.childID=thischild);INSERTINTObomVALUES(thislevel+1,thischild,thisparent,thisqty,thiscost,nextedgenum,NULL);DELETEFROMedgesWHEREchildID=thischildANDparentID=thisparent;SETthislevel=thislevel+1;SETnextedgenum=nextedgenum+1;END;ELSEBEGIN--Setrightedge,removeitemfromedgesUPDATEbomSETrightedge=nextedgenum,level=-levelWHERElevel=thislevel;SETthislevel=thislevel-1;SETnextedgenum=nextedgenum+1;END;ENDIF;ENDWHILE;SETrows:=(SELECTCOUNT(*)FROMedges);IFrows>0THENSELECT'Orphanedrowsremain';ELSE--TotalSETthiscost=(SELECTSUM(qty*cost)FROMbom);UPDATEbomSETqty=1,cost=thiscostWHEREnodeID=root;--ShowtheresultSELECTCONCAT(Space(Abs(level)*2),ItemName(nodeid,root))ASItem,ROUND(qty,2)ASQty,ROUND(cost,2)ASCostFROMbomORDERBYleftedge;ENDIF;END;goDELIMITER;--FunctionusedbyShowBOM()toretrievebomitemnames:DROPFUNCTIONIFEXISTSItemName;SETGLOBALlog_bin_trust_function_creators=TRUE;DELIMITERgoCREATEFUNCTIONItemName(idINT,rootINT)RETURNSCHAR(20)BEGINDECLAREsCHAR(20)DEFAULT'';SELECTnameINTOsFROMitemsWHEREitemid=id;RETURNIF(id=root,UCASE(s),s);END;go

DELIMITER;CALLShowBOM(11);+---------------------+------+--------+

|Item|Qty|Cost|

+---------------------+------+--------+

|BOOKCASE2X30|1.0|327.93|

|backboard|1.0|3.00|

|laminate|1.0|4.00|

|screw|8.0|0.80|

|side|2.0|16.00|

|screw|6.0|0.60|

|plank|0.5|5.00|

|shelf|8.0|32.00|

|plank|0.3|2.50|

|shelfbracket|4.0|0.80|

|foot|4.0|4.00|

|screw|1.0|0.10|

|woodcube|1.0|0.50|

+---------------------+------+--------+

With
ShowBOM()
inhand,it'seasytocomparecostsofassembliesandsubassemblies.Byaddingpricecolumns,wecandothesameforpricesandprofitmargins.AndnowthatMySQLhasre-enabledpreparedstatementsinstoredprocedures,itwill
berelativelyeasytowriteamoregeneralversionof
ShowBOM()
.Weleavethattoyou.

Shorterandsweeter
ButShowBOM()isnotthesmall,efficientbitofnestedsetsreportingcodewewerehopingfor.Thereisasimplersolution:hidegraphcyclesfromtheedges
tablebymakingthemreferencestorowsinanodestable,sowecantreattheedgestablelikeatree;thenapplyabreadth-first

edge-listsubtreealgorithmtogeneratetheBillofMaterials.Againassumeacabinetmakingcompanymakingbookcases(withadifferentcostingmodel).Forclarity,skipinventorytrackingfornow.Anitemstableww_nodestrackspurchasedandassembledbookcase
elementswiththeirindividualcosts,andanassemblies/edgesww_edgestabletrackssetsofedgesthatcombinetomakeproducts.

Listing35:DDLforasimplerpartsexplosionDROPTABLEIFEXISTSww_nodes;

CREATETABLEww_nodes(

nodeIDint,

descriptionCHAR(50),

costdecimal(10,2)

);

INSERTINTOww_nodesVALUES(1,'finishedbookcase',10);

INSERTINTOww_nodesVALUES(2,'backboard2x1',1);

INSERTINTOww_nodesVALUES(3,'laminate2x1',8);

INSERTINTOww_nodesVALUES(4,'screw',.10);

INSERTINTOww_nodesVALUES(5,'side',4);

INSERTINTOww_nodesVALUES(6,'plank',20);

INSERTINTOww_nodesVALUES(7,'shelf',4);

INSERTINTOww_nodesVALUES(8,'shelfbracket',.5);

INSERTINTOww_nodesVALUES(9,'feet',1);

INSERTINTOww_nodesVALUES(10,'cube4cmx4cm',1);

INSERTINTOww_nodesVALUES(11,'bookcasekit',2);

INSERTINTOww_nodesVALUES(12,'carton',1);


DROPTABLEIFEXISTSww_edges;

CREATETABLEww_edges(

rootIDINT,

nodeIDint,

parentnodeIDint,

qtydecimal(10,2)

);

INSERTINTOww_edgesVALUES(1,1,null,1);

INSERTINTOww_edgesVALUES(1,2,1,1);

INSERTINTOww_edgesVALUES(1,3,2,1);

INSERTINTOww_edgesVALUES(1,4,2,8);

INSERTINTOww_edgesVALUES(1,5,1,2);

INSERTINTOww_edgesVALUES(1,6,5,1);

INSERTINTOww_edgesVALUES(1,4,5,12);

INSERTINTOww_edgesVALUES(1,7,1,8);

INSERTINTOww_edgesVALUES(1,6,7,.5);

INSERTINTOww_edgesVALUES(1,8,7,4);

INSERTINTOww_edgesVALUES(1,9,1,4);

INSERTINTOww_edgesVALUES(1,10,9,1);

INSERTINTOww_edgesVALUES(1,4,9,1);


INSERTINTOww_edgesVALUES(11,11,null,1);

INSERTINTOww_edgesVALUES(11,2,11,1);

INSERTINTOww_edgesVALUES(11,3,2,1);

INSERTINTOww_edgesVALUES(11,4,2,8);

INSERTINTOww_edgesVALUES(11,5,11,2);

INSERTINTOww_edgesVALUES(11,6,5,1);

INSERTINTOww_edgesVALUES(11,4,5,12);

INSERTINTOww_edgesVALUES(11,7,11,8);

INSERTINTOww_edgesVALUES(11,6,7,.5);

INSERTINTOww_edgesVALUES(11,8,7,4);

INSERTINTOww_edgesVALUES(11,9,11,4);

INSERTINTOww_edgesVALUES(11,10,9,1);

INSERTINTOww_edgesVALUES(11,4,9,11);

INSERTINTOww_edgesVALUES(11,12,11,1);

Hereisanadaptationofthebreadth-firstedgelistalgorithmtoretrieveaBillofMaterialsforaproductidentifiedbyarootID:

·Initialisealevel-trackingvariabletozero.

·SeedatempreportingtablewiththerootIDofthedesiredproduct.

·Whilerowsarebeingretrieved,incrementtheleveltrackingvariableandaddrowstothetemptablewhoseparentnodeIDsarenodesatthecurrentlevel.

·PrinttheBOMorderedbypathwithindentationproportionaltotreelevel.

Listing36:Asimplerpartsexplosion

DROPPROCEDUREIFEXISTSww_bom;

DELIMITERgo

CREATEPROCEDUREww_bom(rootINT)

BEGIN

DECLARElevINTDEFAULT0;

DECLAREtotalcostDECIMAL(10,2);

DROPTABLEIFEXISTStemp;

CREATETABLEtemp--initialisetemptablewithrootnode

SELECT

e.nodeIDASnodeID,

n.descriptionASItem,

e.parentnodeID,

e.qty,

n.costASnodecost,

e.qty*n.costAScost,

0aslevel,--treelevel

CONCAT(e.nodeID,'')ASpath--pathtothisnodeasastring

FROMww_nodesn

JOINww_edgeseUSING(nodeID)--rootnode

WHEREe.nodeID=rootANDe.parentnodeIDISNULL;

WHILEFOUND_ROWS()>0DO

BEGIN

SETlev=lev+1;--incrementlevel

INSERTINTOtemp--addchildrenofthislevel

SELECT

e.nodeID,

n.descriptionASItem,

e.parentnodeID,

e.qty,

n.costASnodecost,

e.qty*n.costAScost,

lev,

CONCAT(t.path,',',e.nodeID)

FROMww_nodesn

JOINww_edgeseUSING(nodeID)

JOINtemptONe.parentnodeID=t.nodeID

WHEREe.rootID=rootANDt.level=lev-1;

END;

ENDWHILE;

WHILElev>0DO--percolatecostsupthegraph

BEGIN

SETlev=lev-1;

DROPTABLEIFEXISTStempcost;

CREATETABLEtempcost--computechildcost

SELECTp.nodeID,SUM(c.nodecost*c.qty)ASchildcost

FROMtempp

JOINtempcONp.nodeid=c.parentnodeid

WHEREc.level=lev

GROUPbyp.nodeid;

UPDATEtempJOINtempcostUSING(nodeID)--updateparentitemcost

SETnodecost=nodecost+tempcost.childcost;

UPDATEtempSETcost=qty*nodecost--updateparentcost

WHERElevel=lev-1;

END;

ENDWHILE;

SELECT--listBoM

CONCAT(SPACE(level*2),Item)ASItem,

ROUND(nodecost,2)AS'UnitCost',

ROUND(Qty,0)ASQty,ROUND(cost,2)ASCostFROMtemp

ORDERbypath;

ENDgo

DELIMITER;

CALLww_bom(1);

+-------------------+-----------+------+--------+

|Item|UnitCost|Qty|Cost|

+-------------------+-----------+------+--------+

|finishedbookcase|206.60|1.0|206.60|

|backboard2x1|9.80|1.0|9.80|

|laminate2x1|8.00|1.0|8.00|

|screw|0.10|8.0|0.80|

|side|25.20|2.0|50.40|

|screw|0.10|12.0|1.20|

|plank|20.00|1.0|20.00|

|shelf|16.00|8.0|128.00|

|plank|20.00|0.5|10.00|

|shelfbracket|0.50|4.0|2.00|

|foot|2.10|4.0|8.40|

|cube4cmx4cm|1.00|1.0|1.00|

|screw|0.10|1.0|0.10|

+-------------------+-----------+------+--------+

Summary

Storedprocedures,storedfunctionsandViewsmakeitpossibletoimplementedgelistgraphmodels,nestedsetsgraphmodels,andbreadth-firstanddepth-firstgraphsearchalgorithmsinMySQL5&6.

FurtherReading

CelkoJ,"TreesandHierarchiesinSQLForSmarties",MorganKaufman,SanFrancisco,2004.

Codersource.net,"BranchandBoundAlgorithminC#",
http://www.codersource.net/csharp_branch_and_bound_algorithm_implementation.aspx.
MathForum,"Euler'sSolution:TheDegreeofaVertex",
http://mathforum.org/isaac/problems/bridges2.html

MuhammadRB,"Trees",http://www.personal.kent.edu/~rmuhamma/GraphTheory/MyGraphTheory/trees.htm.
MullinsC,"TheFutureofSQL",http://www.craigsmullins.com/idug_sql.htm.
RodrigueJ-P,"GraphTheory:DefinitionandProperties",http://people.hofstra.edu/geotrans/eng/ch2en/meth2en/ch2m1en.html.
SantryP,"RecursiveSQLUserDefinedFunctions",http://www.wwwcoder.com/main/parentid/191/site/1857/68/default.aspx.
ShashaD,WangJTL,andGiugnoR,"Algorithmicsandapplicationsoftreeandgraphsearching",InSymposiumonPrinciplesofDatabaseSystems,2002,p39--52.

StephensS,"SolvingdirectedgraphproblemswithSQL,PartI",
http://builder.com.com/5100-6388_14-5245017.html.

Stephens,S,"SolvingdirectedgraphproblemswithSQL,PartII",http://builder.com.com/5100-6388_14-5253701.html.
SteinbachT,"MigratingRecursiveSQLfromOracletoDB2UDB",http://www-106.ibm.com/developerworks/db2/library/techarticle/0307steinbach/0307steinbach.html.
TropashkoV,"NestedIntervalsTreeEncodinginSQL,
http://www.sigmod.org/sigmod/record/issues/0506/p47-article-tropashko.pdf
VanTulderG,"StoringHierarchicalDatainaDatabase",
http://www.sitepoint.com/print/hierarchical-data-database.

VenagallaS,"ExpandingRecursiveOpportunitieswithSQLUDFsinDB2v7.2",http://www-106.ibm.com/developerworks/db2/library/techarticle/0203venigalla/0203venigalla.html.
Wikipedia,"GraphTheory",http://en.wikipedia.org/wiki/Graph_theory.
Wikipedia,“Treetraversal”,
Wikipedia,“Treetraversal”,http://en.wikipedia.org/wiki/Tree_traversal.
WilletsK,"SQLGraphAlgorithms",http://willets.org/sqlgraphs.html.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: