Author |
Topic |
madrak
Starting Member
20 Posts |
Posted - 2007-03-07 : 17:32:04
|
Say that we have the following setup:Root Object|-SubObjectA| |-SubObjectA1| |-SubObjectA2|-SubObjectBwith a table definition like this:ObjectID | ObjectName | ParentObjectID1 SubObjectA 12 SubObjectB 13 SubObjectA1 24 SubObjectA2 2What I would like to do, is pass back to the calling application a table that looks like this:PK | ObjectID | ObjectName | ObjectDepth1 1 Object A 12 3 Object A1 23 4 Object A2 24 2 Object B 1I am currently doing this using recursion, but am failing miserably. I talked to another guy here at work and he mentioned the possibility of using a Cursor, but that they can add alot of overhead, so I would like to avoid that if possible.Is there a way to do this other than with a cursor?Any suggestions would be greatly appreciated-madrakthis was posted in the SQL Server 2005 forums initially (i goofed) and it was suggested there to use work tables or dynamic sql, i don't think work tables will work as i won't know the depth ahead of time, but dynamic sql might, however i don't know anything about dynamic sql (i don't think) so i will look into that, or if someone had more information about how dynamic sql will apply here, i'd love to hear it |
|
snSQL
Master Smack Fu Yak Hacker
1837 Posts |
Posted - 2007-03-07 : 17:47:46
|
Although you won't know the depth ahead of time, do you know a maximum depth? Using a work table won't mean you have to know depth, but sorting the rows nested according to the hierarchy will need to use a sort column and it will be easier to implement that if there is a known maximum. |
 |
|
madrak
Starting Member
20 Posts |
Posted - 2007-03-07 : 17:56:46
|
quote: Originally posted by snSQL Although you won't know the depth ahead of time, do you know a maximum depth? Using a work table won't mean you have to know depth, but sorting the rows nested according to the hierarchy will need to use a sort column and it will be easier to implement that if there is a known maximum.
There is no known maximum unfortunately |
 |
|
snSQL
Master Smack Fu Yak Hacker
1837 Posts |
Posted - 2007-03-07 : 19:15:17
|
This should be able to handle a very large number of levels and rows per level.--Create a sample table of hierarchically related objectscreate table #Objects(ObjectID int, ObjectName varchar(20), ParentObjectID int)insert #Objectsselect 1, 'Object A', NULL union allselect 2, 'Object B', NULL union allselect 3, 'SubObject A1', 1 union allselect 4, 'SubObject B1', 2 union allselect 5, 'SubObject A11', 3 union allselect 6, 'SubObject B2', 2 union allselect 7, 'SubObject A2', 1--This is the T-SQL solution--Create the work tablecreate table #Hierarchy(Sort bigint, ObjectID int, ParentObjectID int, ObjectDepth tinyint)--A variable for sorting the nested hierarchydeclare @StepSize bigintset @StepSize = 9223372036854775807 / (select count(*) from #Objects where ParentObjectID is null)--Initially populate the work table with all rows, setting the depth and sort for the top level rows onlyinsert #Hierarchyselect case when ParentObjectID is null then 1 + ((select count(*) from #Objects where ObjectID < o.ObjectID) * @StepSize) else null end , ObjectID, ParentObjectID , case when ParentObjectID is null then 1 else null end from #Objects o--Now loop, updating down the levels until all rows have their depth and sort setdeclare @Depth tinyintset @Depth = 1while exists (select * from #Hierarchy where ObjectDepth is null)begin set @StepSize = @StepSize / (select count(*) from #Hierarchy where ObjectDepth is null) set @Depth = @Depth + 1 update #Hierarchy set Sort = p.Sort + 1 + ((select count(*) from #Hierarchy x where x.ObjectID < #Hierarchy.ObjectID and x.ParentObjectID = #Hierarchy.ParentObjectID) * @StepSize) , ObjectDepth = @Depth from #Hierarchy inner join #Hierarchy p on #Hierarchy.ParentObjectID = p.ObjectID and p.ObjectDepth = @Depth - 1end--Final select to get the rows from the work table joined to the original dataselect (select count(*) from #Hierarchy where Sort <= h.Sort) as RowNumber , o.ObjectID, o.ObjectName, h.ObjectDepthfrom #Objects o inner join #Hierarchy h on o.ObjectID = h.ObjectIDorder by h.Sortdrop table #Hierarchy |
 |
|
madrak
Starting Member
20 Posts |
Posted - 2007-03-08 : 13:42:46
|
This works great! Thank you so much!What would need to be changed if I wanted to get all of the subobjects of SubObjectA only in your example? I'd think the addition of a where clause to the population of #Hierarchy should do it and I'll look into that, but if you could offer any assistance I would really appreciate it.Thank you!-madrak |
 |
|
snSQL
Master Smack Fu Yak Hacker
1837 Posts |
Posted - 2007-03-08 : 14:40:08
|
Actually I had to change some things to make it simple to select any top level rows you want, so use this updated version. I have added a comment on the WHERE clause that you need to change, it currently selects all top level rows by selecting rows where ParentObjectID is null, but you can change that WHERE clause to select any row/s you like as the top level. For example, try changing it toWHERE ObjectID in (2, 3)and you'll get two trees from different levels. I haven't put in any checks for cases where you select rows at the top level that are children of other top level rows and that will cause duplication.--Create a sample table of hierarchically related objectscreate table #Objects(ObjectID int identity, ObjectName varchar(20), ParentObjectID int)insert #Objectsselect 'Object A', NULL union allselect 'Object B', NULL union allselect 'SubObject A1', 1 union allselect 'SubObject B1', 2 union allselect 'SubObject A11', 3 union allselect 'SubObject B2', 2 union allselect 'SubObject A2', 1--This is the T-SQL solution--Create the work tablecreate table #Hierarchy(Sort bigint, ObjectID int, ParentObjectID int, ObjectDepth tinyint)--Initially populate the work table with the top level rows onlyinsert #Hierarchyselect null, ObjectID, ParentObjectID, 1 from #Objects o where ParentObjectID IS NULL --Change this where clause to select the top level rows only--A variable for sorting the nested hierarchydeclare @StepSize bigintset @StepSize = 9223372036854775807 / (select count(*) from #Hierarchy)--Set the sort for the top level rowsupdate #Hierarchyset Sort = 1 + ((select count(*) from #Hierarchy h where h.ObjectID < #Hierarchy.ObjectID) * @StepSize)--Get the nested rows for the selected top level rowsdeclare @Depth tinyintset @Depth = 1while exists (select * from #Objects o inner join #Hierarchy h on o.ParentObjectID = h.ObjectID and h.ObjectDepth = @Depth)begin insert #Hierarchy select null, o.ObjectID, o.ParentObjectID, @Depth + 1 from #Objects o inner join #Hierarchy h on o.ParentObjectID = h.ObjectID and h.ObjectDepth = @Depth set @Depth = @Depth + 1end--Now loop, updating down the levels until all rows have their depth and sort setset @Depth = 1while exists (select * from #Hierarchy where Sort is null)begin set @Depth = @Depth + 1 set @StepSize = @StepSize / (1 + (select count(*) from #Hierarchy where ObjectDepth = @Depth)) update #Hierarchy set Sort = p.Sort + 1 + ((select count(*) from #Hierarchy x where x.ObjectID < #Hierarchy.ObjectID and x.ParentObjectID = #Hierarchy.ParentObjectID) * @StepSize) from #Hierarchy inner join #Hierarchy p on #Hierarchy.ParentObjectID = p.ObjectID and p.ObjectDepth = @Depth - 1end--Final select to get the rows from the work table joined to the original dataselect (select count(*) from #Hierarchy where Sort <= h.Sort) as RowNumber , o.ObjectID, o.ObjectName, h.ObjectDepthfrom #Objects o inner join #Hierarchy h on o.ObjectID = h.ObjectIDorder by h.Sortdrop table #Hierarchy |
 |
|
madrak
Starting Member
20 Posts |
Posted - 2007-03-08 : 14:57:54
|
Wow, thank you so much, that works beautifully!So I know, what are the limitations on number of levels and number of objects per level? As long as they are over 100 or so everything should be ok.Again, thank you so much for this, big time lifesaver-madrak |
 |
|
snSQL
Master Smack Fu Yak Hacker
1837 Posts |
Posted - 2007-03-08 : 15:58:31
|
I'm not sure - it won't handle large amounts of data though, I checked it to about 2000 total rows in the hierarchy and up to 11 levels deep. It will work after that but the sorting within the branches becomes unpredictable. |
 |
|
madrak
Starting Member
20 Posts |
Posted - 2007-03-08 : 16:38:11
|
Great! That should be enough for what we are trying to do. Thank you again! |
 |
|
tkizer
Almighty SQL Goddess
38200 Posts |
Posted - 2007-03-08 : 16:44:58
|
I just wanted to point this out since the subject mentioned trying to avoid a cursor. The reason why a cursor shouldn't be used is due to the looping involved since you aren't processing the data in sets. A WHILE loop is no different. So even though the solution offered here isn't using a cursor that doesn't mean that it doesn't suffer from the problems of a cursor, since the solution uses a WHILE loop.But there probably isn't a way to write this as a set-based solution anyway, so either a cursor or a WHILE loop has to be used!Tara Kizer |
 |
|
snSQL
Master Smack Fu Yak Hacker
1837 Posts |
Posted - 2007-03-08 : 16:49:37
|
Absolutely! Also, putting the row number in the final result is very expensive, so you could improve things by taking that out if you don't need it.And if you want a cursor based solution, (which will work for any size data), there is a nice one herehttp://www.yafla.com/papers/sqlhierarchies/sqlhierarchies.htm |
 |
|
madrak
Starting Member
20 Posts |
Posted - 2007-03-08 : 16:54:18
|
quote: Originally posted by tkizerBut there probably isn't a way to write this as a set-based solution anyway, so either a cursor or a WHILE loop has to be used!Tara Kizer
Is there an advantage to the WHILE loop over a cursor? I understood that there was some additional overhead incurred by a cursor that made it even worse than a while loop, although this may certainly be incorrect. |
 |
|
madrak
Starting Member
20 Posts |
Posted - 2007-03-08 : 16:56:14
|
quote: Originally posted by snSQL Absolutely! Also, putting the row number in the final result is very expensive, so you could improve things by taking that out if you don't need it.And if you want a cursor based solution, (which will work for any size data), there is a nice one herehttp://www.yafla.com/papers/sqlhierarchies/sqlhierarchies.htm
You mean the:select count(*) from #Hierarchy where Sort <= h.Sort statement? That isn't something that I need, so it can probably pretty easily be removed upon review.And I will definitely check out that link!-madrak |
 |
|
tkizer
Almighty SQL Goddess
38200 Posts |
Posted - 2007-03-08 : 16:57:55
|
quote: Originally posted by madrak
quote: Originally posted by tkizerBut there probably isn't a way to write this as a set-based solution anyway, so either a cursor or a WHILE loop has to be used!Tara Kizer
Is there an advantage to the WHILE loop over a cursor? I understood that there was some additional overhead incurred by a cursor that made it even worse than a while loop, although this may certainly be incorrect.
You would need to test the performance difference.Tara Kizer |
 |
|
madrak
Starting Member
20 Posts |
Posted - 2007-03-08 : 17:02:26
|
quote: Originally posted by tkizer
quote: Originally posted by madrakIs there an advantage to the WHILE loop over a cursor? I understood that there was some additional overhead incurred by a cursor that made it even worse than a while loop, although this may certainly be incorrect.
You would need to test the performance difference.Tara Kizer
Got it, thank you. |
 |
|
|