Multiple trees can bury you in table glut. Can we combine them in one table and still query them conveniently? Yes. What's more, it's useful to combine edge list and nested sets representations of the same trees in one table. Here is a table definition for the job:
CREATE TABLE trees ( root int(11) DEFAULT 0, -- to which tree does this node belong? nodeID int(11) NOT NULL, -- edge list representation of this node parentID int(11) DEFAULT NULL, level smallint(6) DEFAULT 0, -- depth of this node in this tree lft int(11) DEFAULT 0, -- nested sets representation of this node rgt int(11) DEFAULT 0, PRIMARY KEY (root,nodeID) ) ENGINE=InnoDB;Why combine edge list columns ( nodeID , parentID ) and nested sets columns (lft , rgt ) columns in the same table?
Two main reasons. First, most trees come to us as sets of nodes and their parents, that is they come to us as edge lists, and as we'll see, it's easy to write nested sets trees from their edge list versions when the two are stored in the same table. Second, some tree queries are easier from an edge list model, and some are easier from a nested sets model, so it's inexpensive and convenient to combine the two. Populate the table with four trees: INSERT INTO trees (nodeID, parentID) VALUES (11, 53),(44, NULL),(45, 44),(50, 53),(51, 45),(52, 51),(53, 44),(57, 52), (64, 50),(67, 53),(68, 53),(69, 53),(70, 53),(71, 53),(72, 64),(73, 64), (74, 64),(75, 53),(76, 53),(77, 53),(78, 53),(79, 53),(93, NULL),(94, 93), (95, 74),(96, 95),(97, NULL),(98, NULL);How do we know this dataset has four trees? Four nodes have NULL parents.
To generalise from writing a single nested sets tree from one edge list tree to writing many such trees in one pass, we add logic to compute the root of each tree, save that root value in each row. and have the routine loop through all trees in the table: DROP PROCEDURE IF EXISTS WriteNestedSets; DELIMITER go CREATE PROCEDURE WriteNestedSets(); BEGIN DECLARE current, nextleft, nextright, maxrightedge, trees, n, rows, iter INT; BEGIN DECLARE col_exists CONDITION FOR SQLSTATE '42S21'; DECLARE CONTINUE HANDLER FOR col_exists SET @root_exists=1; END; UPDATE trees SET root=0,level=0,lft=0,rgt=0; DROP TEMPORARY TABLE IF EXISTS _tree; CREATE TEMPORARY TABLE _tree( childID INT, parentID INT ) ENGINE=HEAP; INSERT INTO _tree SELECT nodeID, parentID from trees; DROP TEMPORARY TABLE IF EXISTS _result; CREATE TEMPORARY TABLE _result ( root INT, level INT, nodeID INT, leftedge INT, rightedge INT, KEY(root,nodeID,leftedge,rightedge) ) ENGINE=HEAP; REPEAT SET @root = ( SELECT childID FROM _tree WHERE parentID IS NULL LIMIT 1); IF @root IS NOT NULL THEN DELETE FROM _tree WHERE childID=@root; ELSE SELECT a.parentID INTO @root FROM _tree a LEFT JOIN trees b ON a.parentID=b.nodeID WHERE b.nodeID IS NULL LIMIT 1; END IF; IF @root IS NOT NULL THEN SET trees = ( SELECT Count(*) FROM _tree WHERE parentID IS NULL ); SET maxrightedge = 2 * ( (SELECT COUNT(*) FROM _tree ) - trees + 1 ); INSERT INTO _result VALUES( @root, 1, @root, 1, maxrightedge ); SET iter=0, n=1, current=1, nextright=2; WHILE nextright < maxrightedge DO IF (SELECT Count(*) FROM _result s JOIN _tree t ON s.nodeID=t.parentID AND s.level=current) > 0 THEN SET nextleft = (SELECT Min(t.childID) FROM _result s JOIN _tree t ON s.nodeID=t.parentID AND s.level=current); INSERT INTO _result VALUES(@root, current+1, nextleft, nextright, NULL); SET rows = Row_Count(); DELETE FROM _tree WHERE childID = ( SELECT nodeID FROM _result WHERE level=(current+1) AND root=@root); SET nextright=nextright+1, current=current+1; ELSE UPDATE _result SET rightedge=nextright,level=-level WHERE root=@root AND level=current; SET nextright=nextright+1, current=current-1; SET rows=Row_Count(); END IF; SET n=n+rows, iter=iter+1; END WHILE; SET maxrightedge= (SELECT 1+Max(rightedge) FROM _result WHERE root=@root AND leftedge<>@root); UPDATE _result SET rightedge=maxrightedge WHERE root=@root AND leftedge=@root; END IF; UNTIL @root IS NULL END REPEAT ; UPDATE _result SET level=Abs(level); UPDATE trees e JOIN _result r ON e.nodeID=r.nodeID SET e.root=r.root,e.level=r.level,e.lft=r.leftedge,e.rgt=r.rightedge; DROP TEMPORARY TABLE _tree; DROP TEMPORARY TABLE _result; END; go CALL WriteNestedSets();Note that the table definition and procedure do not verify that the given edge list tree values are valid. Arguably, that job belongs to the process that generated the edge list trees. If you disagree, you can add this recursive constraint to the table: ALTER TABLE trees ADD FOREIGN KEY(root,parentID) REFERENCES trees(root,nodeID) ON UPDATE CASCADE ON DELETE CASCADE;You will then have to bracket commands, like the command used above to populate the table, with ...
|
![]() |