NOTES : this paper is a complement of an earlier one: Recursive Queries in SQL:1999 and SQL Server 2005
All the examples refer to this earlier article.
1) transforming a tree from auto reference to interval model (nested sets)
The technique is much simpler than it appears. You need a stack table and a tree table. The job is done by a procedure which has been inspired by Joe Celko's book "Trees and Hierarchies in SQL for Smarties".
/*************************************** * object needed for the transformation * ***************************************/ ----------------------------------------- -- a table to store the tree in auto ref ----------------------------------------- CREATE TABLE T_ARBRE (NOD_ACTUEL INT NOT NULL, -- actual node NOD_PARENT INT); -- parent node GO ----------------------------------------- -- a table for the working stack ----------------------------------------- CREATE TABLE T_PILE (PIL_TOP INTEGER NOT NULL, -- top of the stack PIL_ID INT NOT NULL, -- current value PIL_BG INT, -- left bound PIL_BD INT); -- right bound GO -------------------------------------------------------- -- a procedure to do the transform -------------------------------------------------------- CREATE PROCEDURE P_AUTOREF_VERS_INTERVAL AS BEGIN DECLARE @TID INT, @TID_MAX INT, @TID_TOP INT; SELECT @TID = 2, @TID_MAX = 2 * (SELECT COUNT(*) FROM T_ARBRE), @TID_TOP = 1; -- taking the root INSERT INTO T_PILE SELECT 1, NOD_ACTUEL, 1, @TID_MAX FROM T_ARBRE WHERE NOD_PARENT IS NULL; -- deleting the treated lines DELETE FROM T_ARBRE WHERE NOD_PARENT IS NULL; -- process loop while the gap is over 1 WHILE @TID <= @TID_MAX - 1 BEGIN -- if there is some relations between parents and children IF EXISTS(SELECT * FROM T_PILE AS S1 INNER JOIN T_ARBRE AS T1 ON S1.PIL_ID = T1.NOD_PARENT WHERE S1.PIL_TOP = @TID_TOP) BEGIN -- insert it in the stack INSERT INTO T_PILE SELECT @TID_TOP + 1,MIN(T1.NOD_ACTUEL), @TID, NULL FROM T_PILE AS S1 INNER JOIN T_ARBRE AS T1 ON S1.PIL_ID = T1.NOD_PARENT WHERE S1.PIL_TOP = @TID_TOP; -- delete them from the stack DELETE FROM T_ARBRE WHERE NOD_ACTUEL = (SELECT PIL_ID FROM T_PILE WHERE PIL_TOP = @TID_TOP + 1) -- going on into the hierarchy SELECT @TID = @TID + 1, @TID_TOP = @TID_TOP + 1; END ELSE -- no more relations between parents and children BEGIN -- updating the stack UPDATE T_PILE SET PIL_BD = @TID, PIL_TOP = - PIL_TOP WHERE PIL_TOP = @TID_TOP; -- doing a gap in the tree SELECT @TID = @TID + 1, @TID_TOP = @TID_TOP - 1; END; END; END; GO /************************************** * doing the transform * **************************************/ -- inserting id from autoref in the T_ARBRE table INSERT INTO T_ARBRE (NOD_ACTUEL, NOD_PARENT) SELECT VHC_ID, VHC_ID_FATHER FROM T_VEHICULE; GO -- doing the process EXECUTE P_AUTOREF_VERS_INTERVAL; GO -- viewing the result : SELECT V.*, PIL_BD, PIL_BG FROM T_VEHICULE AS V INNERJOIN T_PILE AS P ON V.VHC_ID = P.PIL_ID
At this point two possibilities :
- altering the actual autoref table by adding the bounds
- adding a new table in relationship with the autoref one to manage the bounds
Let us see the two methods :
------------------------------------------- -- SOLUTION 1 : modifying the autoref table ------------------------------------------- -- adding the bounds ALTER TABLE T_VEHICULE ADD VHC_BORNE_GAUCHE INT; ALTER TABLE T_VEHICULE ADD VHC_BORNE_DROITE INT; GO -- updating the bounds UPDATE T_VEHICULE SET VHC_BORNE_GAUCHE = PIL_BG, VHC_BORNE_DROITE = PIL_BD FROM T_VEHICULE AS V INNER JOIN T_PILE AS P ON V.VHC_ID = P.PIL_ID; GO ---------------------------------- -- SOLUTION 2 : adding a new table ---------------------------------- -- creating the new table to manage the interval model -- with a referential integrity to the autoref one CREATE TABLE T_VEHICULE_INTERVAL_VHI (VHC_ID INT NOT NULL PRIMARY KEY, VHI_BG INT NOT NULL, VHI_BD INT NOT NULL, CONSTRAINT FK_VHC FOREIGN KEY(VHC_ID) REFERENCES T_VEHICULE (VHC_ID)); GO -- inserting bounds INSERT INTO T_VEHICULE_INTERVAL_VHI SELECT PIL_ID, PIL_BD, PIL_BG FROM T_PILE; GO
2) adding and calculating level
At last we can add level's information in the table :
-- adding the level columns in the tree table ALTER TABLE T_VEHICULE ADD VHC_NIVEAU INT; GO -- updating the level column UPDATE T_VEHICULE SET VHC_NIVEAU = COALESCE( (SELECT COUNT(*) FROM T_VEHICULE AS VC1 INNER JOIN T_VEHICULE AS VC2 ON VC2.VHC_BORNE_GAUCHE < VC1.VHC_BORNE_GAUCHE AND VC2.VHC_BORNE_DROITE > VC1.VHC_BORNE_DROITE WHERE VC1.VHC_ID = VHC.VHC_ID GROUP BY VC1.VHC_ID ), 0) FROM T_VEHICULE VHC GO
3) adding a view to reproduce the autoref table from an interval model table
The problem is : we have a interval model table for a tree and we want to have it, for some needs (like using some graphical components which usually use an auto ref representation) as an autoref table. Can wee do that ? For sure, here is a method...
This is the SQL view doing the job. No difficulties !
CREATE VIEW dbo.V_VEHICULE_AUTOREF AS SELECT VHC.*, -- calculating father (SELECT VHC_ID FROM dbo.T_VEHICULE T WHERE T.VHC_BORNE_GAUCHE < VHC.VHC_BORNE_GAUCHE AND T.VHC_BORNE_DROITE > VHC.VHC_BORNE_DROITE AND T.VHC_NIVEAU = VHC.VHC_NIVEAU -1)AS VHC_ID_PERE FROM dbo.T_VEHICULE VHC; GO
3) Hierarchical numbering of items in a tree
Recently, Mr. Bruno Petit from Gomez Technologies Corp. asked me this question : in a tree, is it possible to number the elements as a hierarchical matter content table ?
It is a very interesting feature to number a tree in a hierarchical style. You can create a good representation of, for instance, numbering sections such as chapters, items, paragraphs ... from a scriptural document.
This can be seen like this :
N VHC_NAME -------- ---------------- 1 ALL 1.1 SEA 1.1.1 SUBMARINE 1.1.2 BOAT 1.2 EARTH 1.2.1 CAR 1.2.2 TWO WHEELES 1.2.2.1 MOTORCYCLE 1.2.2.2 BICYCLE 1.2.3 TRUCK 1.3 AIR 1.3.1 ROCKET 1.3.2 PLANE
For this I proceed in two steps :
- First, I create a function that calculates the ordinal position regarding any younger brother nodes
- Second, I use a recursive query with the CTA technique to concatenate the different collateral indexes
The collateral function :
CREATE FUNCTION dbo.F_COUNTCOLLATERAUX (@VHC_BORNE_GAUCHE INT, @VHC_ID_PERE INT) RETURNS INT AS BEGIN RETURN (SELECT COUNT(*) FROM dbo.V_VEHICULE_AUTOREF AS T WHERE T.VHC_ID_PERE = @VHC_ID_PERE AND T.VHC_BORNE_GAUCHE <= @VHC_BORNE_GAUCHE); END; GO
The query that does the hierarchical numbering :
WITH T AS ( SELECT VHC_ID, VHC_ID_PERE, VHC_BORNE_GAUCHE, VHC_BORNE_DROITE, VHC_NIVEAU, VHC_NAME, CAST(dbo.F_COUNTCOLLATERAUX (VHC_BORNE_GAUCHE, VHC_ID_PERE)+ 1 AS VARCHAR(max))AS N FROM dbo.V_VEHICULE_AUTOREF AS TOUT1 WHERE VHC_ID_PERE IS NULL UNION ALL SELECT TIN2.VHC_ID, TIN2.VHC_ID_PERE, TIN2.VHC_BORNE_GAUCHE, TIN2.VHC_BORNE_DROITE, TIN2.VHC_NIVEAU, TIN2.VHC_NAME, N +'.' + CAST(dbo.F_COUNTCOLLATERAUX (TIN2.VHC_BORNE_GAUCHE, TIN2.VHC_ID_PERE) AS VARCHAR(32))AS N FROM dbo.V_VEHICULE_AUTOREF AS TIN2 INNER JOIN T ON TIN2.VHC_ID_PERE = T.VHC_ID ) SELECT N, SPACE(VHC_NIVEAU)+ VHC_NAME AS VHC_NAME FROM T ORDER BY VHC_BORNE_GAUCHE;
And the result :
N VHC_NAME -------- ------------ 1 ALL 1.1 SEA 1.1.1 SUBMARINE 1.1.2 BOAT 1.2 EARTH 1.2.1 CAR 1.2.2 TWO WHEELES 1.2.2.1 MOTORCYCLE 1.2.2.2 BICYCLE 1.2.3 TRUCK 1.3 AIR 1.3.1 ROCKET 1.3.2 PLANE
LAST, but not least...
SQL Server 2008 seems to have a new Transact SQL type for hierarchies. I did not have test it actually. Time seems to be missing for me !
This will be done in a future paper comparing these 5 models :
- autoref
- interval
- hierarchies type
- path model
- xml doc
You now should know the first two of these four models. The third one is from SQL Server 2008. The "path model" is the one in which you add a column with the complete path from the root to the node for each item. The last one (XML) is the same as the "path model" in an XML doc.
By Frédéric Brouard - MVP SQL Server
SQL and RDBMS expert, author of :
SQL, Développement, Campus Press 2001
SQL, collection Synthex, Pearson Education 2005, co writer Christian Soutou
http://sqlpro.developpez.com (the most important French web site about the SQL language and RDBMS)
Teacher at Arts & Métiers University and ISEN (engineer school) Toulon