Please start any new threads on our new
site at https://forums.sqlteam.com. We've got lots of great SQL Server
experts to answer whatever question you can come up with.
Author |
Topic |
SQL_Rookie
Starting Member
32 Posts |
Posted - 2012-11-08 : 10:46:26
|
I have read a few post on this site but most or all post use a parent to child id relationship (adjanceny model) my table is nested set model.Here some sample datacreate table Accounts(ID int,TREE_ID int,[Name] nvarchar(60),LEFT_NUM int,RIGHT_NUM int)insert into Accounts (ID,Tree_ID,[Name],LEFT_NUM,RIGHT_NUM) Values (1, 250, 'Retailers',1,25)insert into Accounts (ID,Tree_ID,[Name],LEFT_NUM,RIGHT_NUM) Values (2, 250, 'A', 2,17)insert into Accounts (ID,Tree_ID,[Name],LEFT_NUM,RIGHT_NUM) Values (3, 250, 'AB', 3,4)insert into Accounts (ID,Tree_ID,[Name],LEFT_NUM,RIGHT_NUM) Values (4, 250, 'AC', 5,6)insert into Accounts (ID,Tree_ID,[Name],LEFT_NUM,RIGHT_NUM) Values (5, 250, 'AD', 7,8)insert into Accounts (ID,Tree_ID,[Name],LEFT_NUM,RIGHT_NUM) Values (6, 250, 'AE', 9,10)insert into Accounts (ID,Tree_ID,[Name],LEFT_NUM,RIGHT_NUM) Values (7, 250, 'AF', 11,12)insert into Accounts (ID,Tree_ID,[Name],LEFT_NUM,RIGHT_NUM) Values (8, 250, 'AG', 13,14)insert into Accounts (ID,Tree_ID,[Name],LEFT_NUM,RIGHT_NUM) Values (9, 250, 'AH', 15,16)insert into Accounts (ID,Tree_ID,[Name],LEFT_NUM,RIGHT_NUM) Values (10, 250, 'B', 20,21)insert into Accounts (ID,Tree_ID,[Name],LEFT_NUM,RIGHT_NUM) Values (11, 300, 'Whosalers',1,2)Data looks likeRetailers A AB AC AD AE AF AG AH B Whosalers I would like the result set to display likeLevel 0 Level 1 Level 3Retailers A ABRetailers A ACRetailers A ADRetailers A AERetailers A AFRetailers A AGRetailers A AHRetailers B nullWhosalers null nullI have tried to use a CTE statement but I get the "The maximum recursion 100 has been exhausted before statement completion"So I'm not sure if this is a good technique. |
|
sunitabeck
Master Smack Fu Yak Hacker
5155 Posts |
Posted - 2012-11-08 : 11:55:34
|
What is the rule you are using to identify a parent/child relationship? Can you post the query you tried? |
|
|
TG
Master Smack Fu Yak Hacker
6065 Posts |
Posted - 2012-11-08 : 12:03:58
|
ugh - I'm not crazy about the nested set model. Try this:;with rcte ([id],accounts_id,[name],left_num,right_num,hierPath,lev)as ( select [ID] ,accounts_id ,[Name] ,left_num ,right_num ,convert(varchar(50), replace(str([id],3),' ','0')) ,0 from Accounts where left_num = 1 union all select a.[ID] ,a.accounts_id ,a.[Name] ,a.left_num ,a.right_num ,convert(varchar(50), c.hierPath + '-' + replace(str(a.[id],3),' ','0')) ,c.lev + 1 from rcte c join Accounts a on a.accounts_id = c.accounts_id and a.left_num > c.left_num and a.left_num < c.right_num where not exists (--a does not have any parents that are not already in rcte select * from Accounts p where p.accounts_id = a.accounts_id and p.left_num < a.left_num and p.right_num > a.right_num and patindex('%' + replace(str(p.[id],3),' ','0') + '%', c.hierPath) = 0 ))select accounts_id ,left(name, 15) name ,left_num ,right_num ,hierPath ,lev from rcte order by hierPathOUTPUT:accounts_id name left_num right_num hierPath lev----------- --------------- ----------- ----------- -------------------------------------------------- -----------250 Retailers 1 25 001 0250 A 2 17 001-002 1250 AB 3 4 001-002-003 2250 AC 5 6 001-002-004 2250 AD 7 8 001-002-005 2250 AE 9 10 001-002-006 2250 AF 11 12 001-002-007 2250 AG 13 14 001-002-008 2250 AH 15 16 001-002-009 2250 B 20 21 001-010 1300 Whosalers 1 2 011 0 Be One with the OptimizerTG |
|
|
SQL_Rookie
Starting Member
32 Posts |
Posted - 2012-11-08 : 12:09:38
|
The Tree_ID links all the parents and child together then the left_num and right_num are used to position them as parents, child, child of child, etc...select a.Name, b.Name, c.Namefrom accounts a left join accounts b on a.TREE_ID =b.TREE_ID and b.LEFT_NUM between a.LEFT_NUM and a.RIGHT_NUM and a.Name != b.Name and a.LEFT_NUM =1 left join accounts c on b.TREE_ID =c.TREE_ID and b.LEFT_NUM between b.LEFT_NUM and c.RIGHT_NUM and c.Name != b.Name and a.LEFT_NUM !=1 |
|
|
TG
Master Smack Fu Yak Hacker
6065 Posts |
Posted - 2012-11-08 : 12:20:08
|
woops - my post is not right (yet)...hold on a sec.EDIT:Fixed the query in my original postBe One with the OptimizerTG |
|
|
Jeff Moden
Aged Yak Warrior
652 Posts |
Posted - 2012-11-11 : 23:13:36
|
quote: Originally posted by SQL_Rookie I have read a few post on this site but most or all post use a parent to child id relationship (adjanceny model) my table is nested set model.Here some sample datacreate table Accounts(ID int,TREE_ID int,[Name] nvarchar(60),LEFT_NUM int,RIGHT_NUM int)insert into Accounts (ID,Tree_ID,[Name],LEFT_NUM,RIGHT_NUM) Values (1, 250, 'Retailers',1,25)insert into Accounts (ID,Tree_ID,[Name],LEFT_NUM,RIGHT_NUM) Values (2, 250, 'A', 2,17)insert into Accounts (ID,Tree_ID,[Name],LEFT_NUM,RIGHT_NUM) Values (3, 250, 'AB', 3,4)insert into Accounts (ID,Tree_ID,[Name],LEFT_NUM,RIGHT_NUM) Values (4, 250, 'AC', 5,6)insert into Accounts (ID,Tree_ID,[Name],LEFT_NUM,RIGHT_NUM) Values (5, 250, 'AD', 7,8)insert into Accounts (ID,Tree_ID,[Name],LEFT_NUM,RIGHT_NUM) Values (6, 250, 'AE', 9,10)insert into Accounts (ID,Tree_ID,[Name],LEFT_NUM,RIGHT_NUM) Values (7, 250, 'AF', 11,12)insert into Accounts (ID,Tree_ID,[Name],LEFT_NUM,RIGHT_NUM) Values (8, 250, 'AG', 13,14)insert into Accounts (ID,Tree_ID,[Name],LEFT_NUM,RIGHT_NUM) Values (9, 250, 'AH', 15,16)insert into Accounts (ID,Tree_ID,[Name],LEFT_NUM,RIGHT_NUM) Values (10, 250, 'B', 20,21)insert into Accounts (ID,Tree_ID,[Name],LEFT_NUM,RIGHT_NUM) Values (11, 300, 'Whosalers',1,2)Data looks likeRetailers A AB AC AD AE AF AG AH B Whosalers I would like the result set to display likeLevel 0 Level 1 Level 3Retailers A ABRetailers A ACRetailers A ADRetailers A AERetailers A AFRetailers A AGRetailers A AHRetailers B nullWhosalers null nullI have tried to use a CTE statement but I get the "The maximum recursion 100 has been exhausted before statement completion"So I'm not sure if this is a good technique.
This will give you exactly what you're looking for.WITHcteLevels AS( SELECT a2.TREE_ID, a2.ID, a2.Name, a2.LEFT_NUM, a2.RIGHT_NUM, COUNT(a1.ID)-1 AS Level FROM Accounts AS a1 JOIN Accounts AS a2 ON a2.LEFT_NUM BETWEEN a1.LEFT_NUM AND a1.RIGHT_NUM AND a1.Tree_ID = a2.Tree_ID GROUP BY a2.TREE_ID, a2.ID, a2.Name, a2.LEFT_NUM, a2.RIGHT_NUM) SELECT Level0 = L0.Name, Level1 = L1.Name, Level2 = L2.Name FROM cteLevels L0 LEFT OUTER JOIN cteLevels L1 ON L0.TREE_ID = L1.TREE_ID AND L1.LEFT_NUM BETWEEN L0.LEFT_NUM AND L0.RIGHT_NUM AND L1.Level = 1 LEFT OUTER JOIN cteLevels L2 ON L1.TREE_ID = L2.TREE_ID AND L2.LEFT_NUM BETWEEN L1.LEFT_NUM AND L1.RIGHT_NUM AND L2.Level = 2 WHERE L0.Level = 0 ORDER BY L0.TREE_ID, L0.LEFT_NUM, L1.LEFT_NUM, L2.LEFT_NUM; Output:Level0 Level1 Level2--------- ------ ------Retailers A ABRetailers A ACRetailers A ADRetailers A AERetailers A AFRetailers A AGRetailers A AHRetailers B NULLWhosalers NULL NULL As a bit of a side bar, I'd recommmend using the SELECT in the CTE to update your nested sets with Level# to make your life easier in the future.--Jeff Moden |
|
|
SQL_Rookie
Starting Member
32 Posts |
Posted - 2012-11-12 : 07:53:25
|
Is there away or method to use subqueries? As CTE is limited and this table holds lots of data. I'm expecting back 30K plus rows. |
|
|
SQL_Rookie
Starting Member
32 Posts |
Posted - 2012-11-12 : 08:47:38
|
[quoteAs a bit of a side bar, I'd recommmend using the SELECT in the CTE to update your nested sets with Level# to make your life easier in the future.--Jeff Moden[/quote]what do you mean about update the nested sets with level#? |
|
|
TG
Master Smack Fu Yak Hacker
6065 Posts |
Posted - 2012-11-12 : 10:12:52
|
Will your 30K+ rows only ever have 3 levels? If so are you expecting an indefinite number of level columns created in the output?My solution assumed and unknown number of levels and simply was to illustrate how to use the [left] and [right] values to express the hierarchy.Be One with the OptimizerTG |
|
|
Jeff Moden
Aged Yak Warrior
652 Posts |
Posted - 2012-11-12 : 12:36:56
|
quote: Originally posted by SQL_Rookie Is there away or method to use subqueries? As CTE is limited and this table holds lots of data. I'm expecting back 30K plus rows.
I'm not sure where you get the idea that CTE's are limited. 30K rows is nothing to a CTE.--Jeff Moden |
|
|
Jeff Moden
Aged Yak Warrior
652 Posts |
Posted - 2012-11-12 : 12:39:13
|
quote: Originally posted by SQL_Rookie [quoteAs a bit of a side bar, I'd recommmend using the SELECT in the CTE to update your nested sets with Level# to make your life easier in the future.--Jeff Moden
what do you mean about update the nested sets with level#?[/quote]Pretty much what I stated. Add a column to your nested sets to hold Level. Since the SELECT from the CTE very specifically finds the level for each row, you could use it to update the new Level column in you're nested sets so you don't have to recalculate the Level everytime you need it.--Jeff Moden |
|
|
SQL_Rookie
Starting Member
32 Posts |
Posted - 2012-11-13 : 08:47:48
|
Is there away to get the parent id of each row ? I'm trying to have the end result to be a adjacency list basically. So if I took the query belowSELECT a2.TREE_ID, a2.ID, a2.Name, a2.LEFT_NUM, a2.RIGHT_NUM, COUNT(a1.ID)-1 AS Level FROM Accounts AS a1 JOIN Accounts AS a2 ON a2.LEFT_NUM BETWEEN a1.LEFT_NUM AND a1.RIGHT_NUM AND a1.Tree_ID = a2.Tree_ID GROUP BY a2.TREE_ID, a2.ID, a2.Name, a2.LEFT_NUM, a2.RIGHT_NUMit produces this results setTREE_ID ID Name LEFT_NUM RIGHT_NUM Level250 1 Retailers 1 25 0250 2 A 2 17 1250 3 AB 3 4 2250 4 AC 5 6 2250 5 AD 7 8 2250 6 AE 9 10 2250 7 AF 11 12 2250 8 AG 13 14 2250 9 AH 15 16 2250 10 B 20 21 1300 11 Whosalers 1 2 0but I would like to get the ID from the parent.TREE_ID ID Name LEFT_NUM RIGHT_NUM Level PARENT ID250 1 Retailers 1 25 0 null250 2 A 2 17 1 1250 3 AB 3 4 2 2250 4 AC 5 6 2 2250 5 AD 7 8 2 2250 6 AE 9 10 2 2250 7 AF 11 12 2 2250 8 AG 13 14 2 2250 9 AH 15 16 2 2250 10 B 20 21 1 1300 11 Whosalers 1 2 0 null |
|
|
TG
Master Smack Fu Yak Hacker
6065 Posts |
Posted - 2012-11-13 : 10:30:16
|
I'll let Jeff update his version. Here's mine:with rcte ([id],parentid,tree_id,[name],left_num,right_num,hierPath,lev)as ( select [ID] ,null ,tree_id ,[Name] ,left_num ,right_num ,convert(varchar(50), replace(str([id],3),' ','0')) ,0 from Accounts where left_num = 1 union all select a.[ID] ,c.[id] ,a.tree_id ,a.[Name] ,a.left_num ,a.right_num ,convert(varchar(50), c.hierPath + '-' + replace(str(a.[id],3),' ','0')) ,c.lev + 1 from rcte c join Accounts a on a.tree_id = c.tree_id and a.left_num > c.left_num and a.left_num < c.right_num where not exists (--a does not have any parents that are not already in rcte select * from Accounts p where p.tree_id = a.tree_id and p.left_num < a.left_num and p.right_num > a.right_num and patindex('%' + replace(str(p.[id],3),' ','0') + '%', c.hierPath) = 0 ))select tree_id ,id ,left(name, 15) name ,left_num ,right_num --,hierPath ,lev ,parentidfrom rcte order by hierPathOUTPUT:tree_id id name left_num right_num lev parentid----------- ----------- --------------- ----------- ----------- ----------- -----------250 1 Retailers 1 25 0 NULL250 2 A 2 17 1 1250 3 AB 3 4 2 2250 4 AC 5 6 2 2250 5 AD 7 8 2 2250 6 AE 9 10 2 2250 7 AF 11 12 2 2250 8 AG 13 14 2 2250 9 AH 15 16 2 2250 10 B 20 21 1 1300 11 Whosalers 1 2 0 NULL Be One with the OptimizerTG |
|
|
Jeff Moden
Aged Yak Warrior
652 Posts |
Posted - 2012-11-13 : 11:12:36
|
Post removed. There was a bug in the code |
|
|
TG
Master Smack Fu Yak Hacker
6065 Posts |
Posted - 2012-11-13 : 11:32:12
|
>> Might as well use the power built into the Nested Sets to do thisagreed. I would be curious to see stat comparisons for real data.recursive cte:Table 'Accounts'. Scan count 3, logical reads 28Table 'Worktable'. Scan count 2, logical reads 65intended query for nested sets:Table 'Accounts'. Scan count 12, logical reads 12Be One with the OptimizerTG |
|
|
Jeff Moden
Aged Yak Warrior
652 Posts |
Posted - 2012-11-14 : 04:04:54
|
quote: Originally posted by TG >> Might as well use the power built into the Nested Sets to do thisagreed. I would be curious to see stat comparisons for real data.recursive cte:Table 'Accounts'. Scan count 3, logical reads 28Table 'Worktable'. Scan count 2, logical reads 65intended query for nested sets:Table 'Accounts'. Scan count 12, logical reads 12Be One with the OptimizerTG
Especially if there were an index on the left and right bowers.I will admit, though, that Nestd Sets aren't known for the "upline" capabilities. I suspect that a TOP 1 with a descending ORDER BY instead of using the "MAX" would be more gentle on the scan count especially in the presence of an index.If you'd like to test, I just published an article on how to build a million node hierarchy at the following URL. It has an Adjacency List generator that will build a million row Adjacency List in about 7 seconds and a converter to convert it to Nested Sets in about 54.[url]http://www.sqlservercentral.com/articles/Hierarchy/94040/[/url]--Jeff Moden |
|
|
|
|
|
|
|