Storing tree structures with MongoDB


Published on

In a real life almost any project deals with the
tree structures. Different kinds of taxonomies,
site structures etc require modeling of
hierarchy relations.
Typical approaches used
● Model Tree Structures with Child References
● Model Tree Structures with Parent References
● Model Tree Structures with an Array of Ancestors
● Model Tree Structures with Materialized Paths
● Model Tree Structures with Nested Sets

Published in: Technology
  • Be the first to comment

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide

Storing tree structures with MongoDB

  1. 1. Storing tree structureswith MongoDB
  2. 2. IntroductionIn a real life almost any project deals with thetree structures. Different kinds of taxonomies,site structures etc require modeling ofhierarchy relations.Typical approaches used● Model Tree Structures with Child References● Model Tree Structures with Parent References● Model Tree Structures with an Array of Ancestors● Model Tree Structures with Materialized Paths● Model Tree Structures with Nested Sets
  3. 3. Demo dataset used
  4. 4. Challenges to addressIn a typical site scenario, we should be able to● Operate with tree (insert new node under specific parent, update/remove existing node, move node across the tree)● Get path to node (for example, in order to be build the breadcrumb section)● Get all node descendants (in order to be able, for example, to select goods from more general category, like Cell Phones and Accessories which should include goods from all subcategories.
  5. 5. Scope of the demoOn each of the examples below we:● Add new node called LG under electronics● Move LG node under Cell_Phones_And_Smartphones node● Remove LG node from the tree● Get child nodes of Electronics node● Get path to Nokia node● Get all descendants of the Cell_Phones_and_Accessories node
  6. 6. Lets start...
  7. 7. Tree structure withparent referenceThis is most commonly used approach. For each nodewe store (ID, ParentReference, Order)
  8. 8. Operating with treePretty simple, but changing the position of the nodewithin siblings will require additional calculations. You might want to set high numbers like item position *10^6 for sorting in order to be able to set new node orderas trunc (lower sibling order - higher sibling order)/2 - this will give you enough operations, until you will needto traverse whole the tree and set the order defaults tobig numbers again
  9. 9. Adding new nodeGood points: requires only one insertoperation to introduce the node.var existingelemscount = db.categoriesPCO.find({parent:Electronics}).count();var neworder = (existingelemscount+1)*10;db.categoriesPCO.insert({_id:LG, parent:Electronics,someadditionalattr:test, order:neworder})//{ "_id" : "LG", "parent" : "Electronics",// "someadditionalattr" : "test", "order" : 40 }
  10. 10. Updating / moving the nodeGood points: as during insert - requires onlyone update operation to amend the nodeexistingelemscount = db.categoriesPCO.find({parent:Cell_Phones_and_Smartphones}).count();neworder = (existingelemscount+1)*10;db.categoriesPCO.update({_id:LG},{$set:{parent:Cell_Phones_and_Smartphones, order:neworder}});//{ "_id" : "LG", "order" : 60, "parent" :// "Cell_Phones_and_Smartphones","someadditionalattr" : "test" }
  11. 11. Node removalGood points: requires single operation toremove the node from treedb.categoriesPCO.remove({_id:LG});
  12. 12. Getting node children, orderedGood points: all childs can be retrieved fromdatabase and ordered using single call.db.categoriesPCO.find({$query:{parent:Electronics},$orderby:{order:1}})//{ "_id" : "Cameras_and_Photography", "parent" :"Electronics", "order" : 10 }//{ "_id" : "Shop_Top_Products", "parent" : "Electronics","order" : 20 }//{ "_id" : "Cell_Phones_and_Accessories", "parent" :"Electronics", "order" : 30 }
  13. 13. Getting all node descendantsBad points: unfortunately, requires recursivecalls to database.var descendants=[]var stack=[];var item = db.categoriesPCO.findOne({_id:"Cell_Phones_and_Accessories"});stack.push(item);while (stack.length>0){ var currentnode = stack.pop(); var children = db.categoriesPCO.find({parent:currentnode._id}); while(true === children.hasNext()) { var child =; descendants.push(child._id); stack.push(child); }}descendants.join(",")//Cell_Phones_and_Smartphones,Headsets,Batteries,Cables_And_Adapters,Nokia,Samsung,Apple,HTC,Vyacheslav
  14. 14. Getting path to nodeBad points: unfortunately also requirerecursive operations to get the path.var path=[]var item = db.categoriesPCO.findOne({_id:"Nokia"})while (item.parent !== null) { item=db.categoriesPCO.findOne({_id:item.parent}); path.push(item._id);}path.reverse().join( / );//Electronics / Cell_Phones_and_Accessories /Cell_Phones_and_Smartphones
  15. 15. IndexesRecommended index is on fields parent andorderdb.categoriesPCO.ensureIndex( { parent: 1, order:1 } )
  16. 16. Tree structure with childsreferenceFor each node we store (ID,ChildReferences).
  17. 17. NotePlease note, that in this case we do not need order field,because Childs collection already provides thisinformation.Most of languages respect the array order. If this is not incase for your language, you might consider additionalcoding to preserve order, however this will make thingsmore complicated
  18. 18. Adding new nodeNote: requires one insert operation and oneupdate operation to insert the node.db.categoriesCRO.insert({_id:LG, childs:[]});db.categoriesCRO.update({_id:Electronics},{ $addToSet:{childs:LG}});//{ "_id" : "Electronics", "childs" : ["Cameras_and_Photography", "Shop_Top_Products","Cell_Phones_and_Accessories", "LG" ] }
  19. 19. Updating/moving the nodeRequires single update operation to change node orderwithin same parent, requires two update operations, ifnode is moved under another parent.Rearranging order under the same parentdb.categoriesCRO.update({_id:Electronics},{$set:{"childs.1":LG,"childs.3":Shop_Top_Products}});//{ "_id" : "Electronics", "childs" : [ "Cameras_and_Photography","LG", "Cell_Phones_and_Accessories", "Shop_Top_Products" ] }Moving the nodedb.categoriesCRO.update({_id:Cell_Phones_and_Smartphones},{ $addToSet:{childs:LG}});db.categoriesCRO.update({_id:Electronics},{$pull:{childs:LG}});//{ "_id" : "Cell_Phones_and_Smartphones", "childs" : [ "Nokia", "Samsung","Apple", "HTC", "Vyacheslav", "LG" ] }
  20. 20. Node removalNode removal also requires two operations:one update and one remove.db.categoriesCRO.update({_id:Cell_Phones_and_Smartphones},{$pull:{childs:LG}})db.categoriesCRO.remove({_id:LG});
  21. 21. Getting node children, orderedBad points: requires additional client sidesorting by parent array sequence. Dependingon result set, it may affect speed of yourcode.var parent = db.categoriesCRO.findOne({_id:Electronics})db.categoriesCRO.find({_id:{$in:parent.childs}})
  22. 22. Getting node children, orderedResult set{ "_id" : "Cameras_and_Photography", "childs" : [ "Digital_Cameras","Camcorders", "Lenses_and_Filters", "Tripods_and_supports","Lighting_and_studio" ] }{ "_id" : "Cell_Phones_and_Accessories", "childs" : ["Cell_Phones_and_Smartphones", "Headsets", "Batteries","Cables_And_Adapters" ] }{ "_id" : "Shop_Top_Products", "childs" : [ "IPad", "IPhone", "IPod","Blackberry" ] }//parent:{ "_id" : "Electronics", "childs" : [ "Cameras_and_Photography", "Cell_Phones_and_Accessories", "Shop_Top_Products" ]}As you see, we have ordered array childs, which can be used to sort the resultset on a client
  23. 23. Getting all node descendantsNote: also recursive operations, but we need less selectsto databases comparing to previous approachvar descendants=[]var stack=[];var item = db.categoriesCRO.findOne({_id:"Cell_Phones_and_Accessories"});stack.push(item);while (stack.length>0){ var currentnode = stack.pop(); var children = db.categoriesCRO.find({_id:{$in:currentnode.childs}}); while(true === children.hasNext()) { var child =; descendants.push(child._id); if(child.childs.length>0){ stack.push(child); } }}//Batteries,Cables_And_Adapters,Cell_Phones_and_Smartphones,Headsets,Apple,HTC,Nokia,Samsungdescendants.join(",")
  24. 24. Getting path to nodePath is calculated recursively, so we need toissue number of sequential calls to database.var path=[]var item = db.categoriesCRO.findOne({_id:"Nokia"})while ((item=db.categoriesCRO.findOne({childs:item._id}))){ path.push(item._id);}path.reverse().join( / );//Electronics / Cell_Phones_and_Accessories /Cell_Phones_and_Smartphones
  25. 25. IndexesRecommended action is putting index onchilds:db.categoriesCRO.ensureIndex( { childs: 1 } )
  26. 26. Tree structure using anArray of AncestorsFor each node we store (ID, ParentReference,AncestorReferences)
  27. 27. Adding new nodeYou need one insert operation to introducenew node, however you need to invoke selectin order to prepare the data for insertvar ancestorpath = db.categoriesAAO.findOne({_id:Electronics}).ancestors;ancestorpath.push(Electronics)db.categoriesAAO.insert({_id:LG, parent:Electronics,ancestors:ancestorpath});//{ "_id" : "LG", "parent" : "Electronics", "ancestors" :[ "Electronics" ] }
  28. 28. Updating/moving the nodemoving the node requires one select and oneupdate operationancestorpath = db.categoriesAAO.findOne({_id:Cell_Phones_and_Smartphones}).ancestors;ancestorpath.push(Cell_Phones_and_Smartphones)db.categoriesAAO.update({_id:LG},{$set:{parent:Cell_Phones_and_Smartphones, ancestors:ancestorpath}});//{ "_id" : "LG", "ancestors" : [ "Electronics","Cell_Phones_and_Accessories","Cell_Phones_and_Smartphones" ], "parent" :"Cell_Phones_and_Smartphones" }
  29. 29. Node removalis done with single operationdb.categoriesAAO.remove({_id:LG});
  30. 30. Getting node children, unorderedNote: unless you introduce the order field, itis impossible to get ordered list of nodechildren. You should consider anotherapproach if you need order.db.categoriesAAO.find({$query:{parent:Electronics}})
  31. 31. Getting all node descendantsThere are two options to get all nodedescendants. One is classic through recursion:var ancestors = db.categoriesAAO.find({ancestors:"Cell_Phones_and_Accessories"},{_id:1});while(true === ancestors.hasNext()) { var elem =; descendants.push(elem._id); }descendants.join(",")//Cell_Phones_and_Smartphones,Headsets,Batteries,Cables_And_Adapters,Nokia,Samsung,Apple,HTC,Vyacheslav
  32. 32. Getting all node descendantssecond is using aggregation frameworkintroduced in MongoDB 2.2:var aggrancestors = db.categoriesAAO.aggregate([ {$match:{ancestors:"Cell_Phones_and_Accessories"}}, {$project:{_id:1}}, {$group:{_id:{},ancestors:{$addToSet:"$_id"}}}])descendants = aggrancestors.result[0].ancestorsdescendants.join(",")//Vyacheslav,HTC,Samsung,Cables_And_Adapters,Batteries,Headsets,Apple,Nokia,Cell_Phones_and_Smartphones
  33. 33. Getting path to nodeThis operation is done with single call todatabase, which is advantage of thisapproach.var path=[]var item = db.categoriesAAO.findOne({_id:"Nokia"})itempath=item.ancestors;path.join( / );//Electronics / Cell_Phones_and_Accessories / Cell_Phones_and_Smartphones
  34. 34. IndexesRecommended index is putting index onancestors:db.categoriesAAO.ensureIndex( { ancestors: 1 } )
  35. 35. Tree structure using Materialized PathFor each node we store (ID, PathToNode)
  36. 36. IntroApproach looks similar to storing array ofancestors, but we store a path in form ofstring instead.In example above I intentionally use comma(,)as a path elements divider, in order to keepregular expression simpler
  37. 37. Adding new nodeNew node insertion is done with one selectand one insert operationvar ancestorpath = db.categoriesMP.findOne({_id:Electronics}).path;ancestorpath += Electronics,db.categoriesMP.insert({_id:LG, path:ancestorpath});//{ "_id" : "LG", "path" : "Electronics," }
  38. 38. Updating/moving the nodeNode can be moved using one select and oneupdate operationancestorpath = db.categoriesMP.findOne({_id:Cell_Phones_and_Smartphones}).path;ancestorpath +=Cell_Phones_and_Smartphones,db.categoriesMP.update({_id:LG},{$set:{path:ancestorpath}});//{ "_id" : "LG", "path" : "Electronics,Cell_Phones_and_Accessories,Cell_Phones_and_Smartphones," }
  39. 39. Node removalNode can be removed using single databasequerydb.categoriesMP.remove({_id:LG});
  40. 40. Getting node children, unorderedNote: unless you introduce the order field, itis impossible to get ordered list of nodechildren. You should consider anotherapproach if you need order.db.categoriesMP.find({$query:{path:Electronics,}})//{ "_id" : "Cameras_and_Photography", "path" : "Electronics," }//{ "_id" : "Shop_Top_Products", "path" : "Electronics," }//{ "_id" : "Cell_Phones_and_Accessories", "path" : "Electronics," }
  41. 41. Getting all node descendantsSingle select, regexp starts with ^ whichallows using the index for matchingvar descendants=[]var item = db.categoriesMP.findOne({_id:"Cell_Phones_and_Accessories"});var criteria = ^+item.path+item._id+,;var children = db.categoriesMP.find({path: { $regex: criteria, $options: i }});while(true === children.hasNext()) { var child =; descendants.push(child._id);}descendants.join(",")//Cell_Phones_and_Smartphones,Headsets,Batteries,Cables_And_Adapters,Nokia,Samsung,Apple,HTC,Vyacheslav
  42. 42. Getting path to nodeWe can obtain path directly from nodewithout issuing additional selects.var path=[]var item = db.categoriesMP.findOne({_id:"Nokia"})print (item.path)//Electronics,Cell_Phones_and_Accessories,Cell_Phones_and_Smartphones,
  43. 43. IndexesRecommended index is putting index on pathdb.categoriesAAO.ensureIndex( { path: 1 } )
  44. 44. Tree structure usingNested SetsFor each node we store (ID, left, right).
  45. 45. Adding new nodePlease refer to image above.Assume, we want to insert LG node aftershop_top_products(14,23).New node would have left value of 24, affecting allremaining left values according to traversal rules, and willhave right value of 25, affecting all remaining right valuesincluding root one.
  46. 46. Adding new nodeTake next node in traversal tree New node will have left value of the following sibling andright value - incremented by two following siblings leftone Now we have to create the place for the new node.Update affects right values of all ancestor nodes and alsoaffects all nodes that remain for traversalOnly after creating place new node can be inserted
  47. 47. Adding new nodevar followingsibling = db.categoriesNSO.findOne({_id:"Cell_Phones_and_Accessories"});var newnode = {_id:LG, left:followingsibling.left,right:followingsibling.left+1}db.categoriesNSO.update({right:{$gt:followingsibling.right}},{$inc:{right:2}}, false, true)db.categoriesNSO.update({left:{$gte:followingsibling.left}, right:{$lte:followingsibling.right}},{$inc:{left:2, right:2}}, false, true)db.categoriesNSO.insert(newnode)
  48. 48. Check the result+-Electronics (1,46) +---Cameras_and_Photography (2,13) +------Digital_Cameras (3,4) +------Camcorders (5,6) +------Lenses_and_Filters (7,8) +------Tripods_and_supports (9,10) +------Lighting_and_studio (11,12) +----Shop_Top_Products (14,23) +------IPad (15,16) +------IPhone (17,18) +------IPod (19,20) +------Blackberry (21,22) +----LG (24,25) +----Cell_Phones_and_Accessories (26,45) +------Cell_Phones_and_Smartphones (27,38) +---------Nokia (28,29) +---------Samsung (30,31) +---------Apple (32,33) +---------HTC (34,35) +---------Vyacheslav (36,37) +-------Headsets (39,40) +-------Batteries (41,42) +-------Cables_And_Adapters (43,44)
  49. 49. Node removal While potentially rearranging node order within same parent is identical toexchanging nodes left and right values,the formal way of moving the node isfirst removing node from the tree and later inserting it to new location.Note: node removal without removing its childs is out of scope for thisarticle.For now, we assume, that node to remove has no children, i.e. right-left=1 Steps are identical to adding the node - i.e. we adjusting the space bydecreasing affected left/right values, and removing original node.
  50. 50. Node removalvar nodetoremove = db.categoriesNSO.findOne({_id:"LG"});if((nodetoremove.right-nodetoremove.left-1)>0.001) { print("Only node without childs can be removed") exit}var followingsibling = db.categoriesNSO.findOne({_id:"Cell_Phones_and_Accessories"});//update all remaining nodesdb.categoriesNSO.update({right:{$gt:nodetoremove.right}},{$inc:{right:-2}}, false, true)db.categoriesNSO.update({left:{$gt:nodetoremove.right}},{$inc:{left:-2}}, false, true)db.categoriesNSO.remove({_id:"LG"});
  51. 51. Updating/moving the single node Moving the node can be within same parent, or to another parent. If thesame parent, and nodes are without childs, than you need just to exchangenodes (left,right) pairs. Formal way is to remove node and insert to new destination, thus the samerestriction apply - only node without children can be moved. If you need tomove subtree, consider creating mirror of the existing parent under newlocation, and move nodes under the new parent one by one. Once all nodesmoved, remove obsolete old parent. As an example, lets move LG node from the insertion example under theCell_Phones_and_Smartphones node, as a last sibling (i.e. you do not havefollowing sibling node as in the insertion example)
  52. 52. Updating/moving the single nodeSteps1. to remove LG node from tree using node removal procedure described above2. to take right value of the new parent.New node will have left value of the parents right value and right value - incremented by one parents right one. Now we have to create the place for the new node: update affects right values of all nodes on a further traversal pathvar newparent = db.categoriesNSO.findOne({_id:"Cell_Phones_and_Smartphones"});var nodetomove = {_id:LG, left:newparent.right,right:newparent.right+1}//3th and 4th parameters: false stands for upsert=false and true stands for multi=truedb.categoriesNSO.update({right:{$gte:newparent.right}},{$inc:{right:2}}, false, true)db.categoriesNSO.update({left:{$gte:newparent.right}},{$inc:{left:2}}, false, true)db.categoriesNSO.insert(nodetomove)
  53. 53. Check the result+-Electronics (1,46) +--Cameras_and_Photography (2,13) +-----Digital_Cameras (3,4) +-----Camcorders (5,6) +-----Lenses_and_Filters (7,8) +-----Tripods_and_supports (9,10) +-----Lighting_and_studio (11,12) +---Shop_Top_Products (14,23) +-----IPad (15,16) +-----IPhone (17,18) +-----IPod (19,20) +-----Blackberry (21,22) +---Cell_Phones_and_Accessories (24,45) +-----Cell_Phones_and_Smartphones (25,38) +---------Nokia (26,27) +---------Samsung (28,29) +---------Apple (30,31) +---------HTC (32,33) +---------Vyacheslav (34,35) +---------LG (36,37) +-------Headsets (39,40) +-------Batteries (41,42) +-------Cables_And_Adapters (43,44)
  54. 54. Getting all node descendantsThis is core strength of this approach - all descendantsretrieved using one select to DB. Moreover,by sorting bynode left - the dataset is ready for traversal in a correctordervar descendants=[]var item = db.categoriesNSO.findOne({_id:"Cell_Phones_and_Accessories"});print ((+item.left+,+item.right+))var children = db.categoriesNSO.find({left:{$gt:item.left}, right:{$lt:item.right}}).sort(left:1);while(true === children.hasNext()) { var child =; descendants.push(child._id);}descendants.join(",")//Cell_Phones_and_Smartphones,Headsets,Batteries,Cables_And_Adapters,Nokia,Samsung,Apple,HTC,Vyacheslav
  55. 55. Getting path to nodeRetrieving path to node is also elegant and canbe done using single query to database:var path=[]var item = db.categoriesNSO.findOne({_id:"Nokia"})var ancestors = db.categoriesNSO.find({left:{$lt:item.left}, right:{$gt:item.right}}).sort({left:1})while(true === ancestors.hasNext()) { var child =; path.push(child._id);}path.join(/)// Electronics/Cell_Phones_and_Accessories/Cell_Phones_and_Smartphones
  56. 56. IndexesRecommended index is putting index on leftand right values:db.categoriesAAO.ensureIndex( { left: 1, right:1 } )
  57. 57. Combination of NestedSets and classic Parentreference with orderapproachFor each node we store (ID, Parent, Order,left, right).
  58. 58. IntroLeft field also is treated as an order field, sowe could omit order field.But from other hand, we can leave it, so wecan use Parent Reference with order data toreconstruct left/right values in case ofaccidental corruption, or, for example duringinitial import.
  59. 59. Adding new nodeAdding new node can be adopted from NestedSets in this manner:var followingsibling = db.categoriesNSO.findOne({_id:"Cell_Phones_and_Accessories"});var previoussignling = db.categoriesNSO.findOne({_id:"Shop_Top_Products"});var neworder = parseInt((followingsibling.order + previoussignling.order)/2);var newnode = {_id:LG, left:followingsibling.left,right:followingsibling.left+1,parent:followingsibling.parent, order:neworder};db.categoriesNSO.update({right:{$gt:followingsibling.right}},{$inc:{right:2}}, false,true)db.categoriesNSO.update({left:{$gte:followingsibling.left}, right:{$lte:followingsibling.right}},{$inc:{left:2, right:2}}, false, true)db.categoriesNSO.insert(newnode)
  60. 60. Check the resultBefore insertion+----Cameras_and_Photography (2,13) ord.[10] +-----Shop_Top_Products (14,23) ord.[20] +-----Cell_Phones_and_Accessories (26,45) ord.[30]After insertion +--Electronics (1,46) +----Cameras_and_Photography (2,13) ord.[10] +-------Digital_Cameras (3,4) ord.[10] +-------Camcorders (5,6) ord.[20] +-------Lenses_and_Filters (7,8) ord.[30] +-------Tripods_and_supports (9,10) ord.[40] +-------Lighting_and_studio (11,12) ord.[50] +-----Shop_Top_Products (14,23) ord.[20] +-------IPad (15,16) ord.[10] +-------IPhone (17,18) ord.[20] +-------IPod (19,20) ord.[30] +-------Blackberry (21,22) ord.[40] +-----LG (24,25) ord.[25] +-----Cell_Phones_and_Accessories (26,45) ord.[30] +-------Cell_Phones_and_Smartphones (27,38) ord.[10] +----------Nokia (28,29) ord.[10] +----------Samsung (30,31) ord.[20] +----------Apple (32,33) ord.[30] +----------HTC (34,35) ord.[40] +----------Vyacheslav (36,37) ord.[50] +--------Headsets (39,40) ord.[20] +--------Batteries (41,42) ord.[30] +--------Cables_And_Adapters (43,44) ord.[40]
  61. 61. Updating/moving the single nodeIdentical to insertion approach
  62. 62. Node removalApproach from Nested Sets is used.
  63. 63. Getting node children, orderedNow is possible by using (Parent,Order) pairdb.categoriesNSO.find({parent:"Electronics"}).sort({order:1});/*{ "_id" : "Cameras_and_Photography", "parent" : "Electronics", "order" :10, "left" : 2, "right" : 13 }{ "_id" : "Shop_Top_Products", "parent" : "Electronics", "order" : 20,"left" : 14, "right" : 23 }{ "_id" : "LG", "left" : 24, "right" : 25, "parent" : "Electronics","order" : 25 }{ "_id" : "Cell_Phones_and_Accessories", "parent" : "Electronics", "order": 30, "left" : 26, "right" : 45 }*/
  64. 64. Getting all node descendantsApproach from Nested Sets is used.
  65. 65. Getting path to nodeApproach from nested sets is used
  66. 66. Code in action
  67. 67. Notes on using codeAll files are packaged according to the following naming convention:MODELReference.js - initialization file with tree data for MODEL approachMODELReference_operating.js - add/update/move/remove/get childrenexamplesMODELReference_pathtonode.js - code illustrating how to obtain path to nodeMODELReference_nodedescendants.js - code illustrating how to retrieve allthe descendants of the nodeAll files are ready to use in mongo shell. You can run examples by invokingmongo < file_to_execute, or, if you want, interactively in the shell or withRockMongo web shell.
  68. 68. Thanks! Vyacheslav Voronenko
  69. 69. Check the result+-Electronics (1,44) +--Cameras_and_Photography (2,13) +-----Digital_Cameras (3,4) +-----Camcorders (5,6) +-----Lenses_and_Filters (7,8) +-----Tripods_and_supports (9,10) +-----Lighting_and_studio (11,12) +---Shop_Top_Products (14,23) +-----IPad (15,16) +-----IPhone (17,18) +-----IPod (19,20) +-----Blackberry (21,22) +---Cell_Phones_and_Accessories (24,43) +-----Cell_Phones_and_Smartphones (25,36) +--------Nokia (26,27) +--------Samsung (28,29) +--------Apple (30,31) +--------HTC (32,33) +--------Vyacheslav (34,35) +------Headsets (37,38) +------Batteries (39,40) +------Cables_And_Adapters (41,42)