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.

 All Forums
 SQL Server 2000 Forums
 Transact-SQL (2000)
 Trying to avoid using a cursor

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
|-SubObjectB

with a table definition like this:
ObjectID | ObjectName | ParentObjectID
1 SubObjectA 1
2 SubObjectB 1
3 SubObjectA1 2
4 SubObjectA2 2

What I would like to do, is pass back to the calling application a table that looks like this:

PK | ObjectID | ObjectName | ObjectDepth
1 1 Object A 1
2 3 Object A1 2
3 4 Object A2 2
4 2 Object B 1

I 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

-madrak

this 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.
Go to Top of Page

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
Go to Top of Page

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 objects
create table #Objects
(ObjectID int, ObjectName varchar(20), ParentObjectID int)
insert #Objects
select 1, 'Object A', NULL union all
select 2, 'Object B', NULL union all
select 3, 'SubObject A1', 1 union all
select 4, 'SubObject B1', 2 union all
select 5, 'SubObject A11', 3 union all
select 6, 'SubObject B2', 2 union all
select 7, 'SubObject A2', 1

--This is the T-SQL solution
--Create the work table
create table #Hierarchy
(Sort bigint, ObjectID int, ParentObjectID int, ObjectDepth tinyint)

--A variable for sorting the nested hierarchy
declare @StepSize bigint
set @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 only
insert #Hierarchy
select 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 set
declare @Depth tinyint
set @Depth = 1
while 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 - 1
end

--Final select to get the rows from the work table joined to the original data
select (select count(*) from #Hierarchy where Sort <= h.Sort) as RowNumber
, o.ObjectID, o.ObjectName, h.ObjectDepth
from #Objects o
inner join #Hierarchy h on o.ObjectID = h.ObjectID
order by h.Sort

drop table #Hierarchy
Go to Top of Page

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
Go to Top of Page

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 to
WHERE 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 objects
create table #Objects
(ObjectID int identity, ObjectName varchar(20), ParentObjectID int)
insert #Objects
select 'Object A', NULL union all
select 'Object B', NULL union all
select 'SubObject A1', 1 union all
select 'SubObject B1', 2 union all
select 'SubObject A11', 3 union all
select 'SubObject B2', 2 union all
select 'SubObject A2', 1

--This is the T-SQL solution
--Create the work table
create table #Hierarchy
(Sort bigint, ObjectID int, ParentObjectID int, ObjectDepth tinyint)

--Initially populate the work table with the top level rows only
insert #Hierarchy
select 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 hierarchy
declare @StepSize bigint
set @StepSize = 9223372036854775807 / (select count(*) from #Hierarchy)
--Set the sort for the top level rows
update #Hierarchy
set Sort = 1 + ((select count(*) from #Hierarchy h where h.ObjectID < #Hierarchy.ObjectID) * @StepSize)

--Get the nested rows for the selected top level rows
declare @Depth tinyint
set @Depth = 1
while 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 + 1
end

--Now loop, updating down the levels until all rows have their depth and sort set
set @Depth = 1
while 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 - 1
end

--Final select to get the rows from the work table joined to the original data
select (select count(*) from #Hierarchy where Sort <= h.Sort) as RowNumber
, o.ObjectID, o.ObjectName, h.ObjectDepth
from #Objects o
inner join #Hierarchy h on o.ObjectID = h.ObjectID
order by h.Sort

drop table #Hierarchy
Go to Top of Page

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
Go to Top of Page

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.
Go to Top of Page

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!
Go to Top of Page

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
Go to Top of Page

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 here
http://www.yafla.com/papers/sqlhierarchies/sqlhierarchies.htm
Go to Top of Page

madrak
Starting Member

20 Posts

Posted - 2007-03-08 : 16:54:18
quote:
Originally posted by tkizer

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



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.
Go to Top of Page

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 here
http://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
Go to Top of Page

tkizer
Almighty SQL Goddess

38200 Posts

Posted - 2007-03-08 : 16:57:55
quote:
Originally posted by madrak

quote:
Originally posted by tkizer

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



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
Go to Top of Page

madrak
Starting Member

20 Posts

Posted - 2007-03-08 : 17:02:26
quote:
Originally posted by tkizer

quote:
Originally posted by madrak

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



Got it, thank you.
Go to Top of Page
   

- Advertisement -