omaits


I am attempting to do the following....

I have standard tree setup. The tree can be up to 4 nodes deep. User permissions may be assigned at any level in the tree. Any

permission should cascade down the tree to the lowest child node.

For example, if a user had a role of 1 for the root node (101), the sql should return:

OrgID RoleID
101     1
102     1
103     1
etc...

My table structure is as follows....

Org

OrgID ParentID
101     Null
102     101
103     101
105     102
106     102
107     105
108     105
109     106
110     106
111     106

UserOrgRole

UserID OrgID RoleID
User1   101     1
User1   102     2
User1   103     2
User1   107     2
User2   101     1
User3   106     3
etc...

What I would like to retrive from the above table data is....

OrgID RoleID
102     2
105     2
106     2
107     2
108     2
109     2
110     2
111     2

This is so because all the nodes (except for 101 and 103) are somehow decedent from the 102 node and 102 has a roleid of 2. I am only concerned with the RoleID 2 and User1.

I have worked for two days trying to figure out how to do this. I am not a DBA or SQL expert by any means. I cannot seem to figure

out how to traverse multiple levels of the tree. I have been using the new CTE and made some progress, but I think I reached my

plateau and haven't been able to get any further.

If someone could help me, I would be forever in your debt! I am really starting to get very frustrated and I know there are some of

you experts out there that would know exactly what to do.

thanks!



Re: Recursive Multi-Level Query using CTE. Attn: Experts!

Kent Waldrop 2007 Mar


I cannot give this a serious look until tomorrow; here are a couple of posts I have made previously concerning recursive queries and hierarchies:

http://forums.microsoft.com/MSDN/ShowPost.aspx PostID=1174303&SiteID=1

http://forums.microsoft.com/MSDN/ShowPost.aspx PostID=1219738&SiteID=1

I am puzzling over the way roleID is used in the example.  I can't see where the '2' comes from for your desired results.

I am afraid I don't understand how the RoleIDs work.

Also, I don't understand why OrgID 103 does not appear in the target output -- it is in the UserOrgRole table.

Also, please describe the order you wish for the output.  Do you wish the output ordered by only OrgID or by (1) hierarchy depth and then (2) Orgid






Re: Recursive Multi-Level Query using CTE. Attn: Experts!

Louis Davidson

Agreed, the 2 had me a bit confused too :).

Also, can you provide a table structure with insert statements so we don't have to (not you Kent :)







Re: Recursive Multi-Level Query using CTE. Attn: Experts!

Mark - SQL

Not really clear what you're after, but this may help

with OrgRoles(OrgID,ParentID,RoleID) as
(select t.OrgID,
t.ParentID,
r.RoleID
from Org t
left outer join UserOrgRole r on r.OrgID=t.OrgID),
OrgRolesRec(OrgID,RoleID) as
(select OrgID,
RoleID
from OrgRoles
where ParentID is null
union all
select r.OrgID,
coalesce(r.RoleID,t.RoleID)
from OrgRolesRec t
inner join OrgRoles r on r.ParentID=t.OrgID)
select OrgID,
RoleID
from OrgRolesRec
--where RoleID=2
order by OrgID






Re: Recursive Multi-Level Query using CTE. Attn: Experts!

omaits

Thank you all for your prompt responses. I apologize if I was not clear yesterday. I have been reading and reading and reading about this the last 2 days and I am seeing OrgID's and UserID's in my sleep!

I modified the above tables to contain more intuitive data. Basically, it works like so....

Each user can have a different role for each org. For Org 101, their role may be 1 (Public User). However, for Org 102, the role is 2. In this scenario, I am only concerned with Role 2 so only Role 2 will traverse down the tree to all children/grandchildren. I am also only concerned with User1.

The result set would return 0 (or nothing) for org 101 since the user is not a role 2. However, all children/grandchildren of org 102 would return 2. I hope this clarifies.

Also, I am attaching the table scripts. I apologize for not doing this earlier. I am new to this complex sql stuff and didn't even cross my mind.

Finally, I am working on the sql provided in the last post. So far, not quite what I need. Hopefully this clarification will help.

Thank you all so much for your help with this. I am very very grateful.

GO
CREATE TABLE [dbo].[Org](
[OrgID] [int] IDENTITY(100,1) NOT NULL,
[ParentID] [int] NULL,
[Description] [varchar](250) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[OrgLevelID] [int] NULL,
PRIMARY KEY CLUSTERED
(
[OrgID] ASC
)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

GO
CREATE TABLE [dbo].[UserOrgRole](
[UserID] [varchar](20) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[OrgID] [int] NOT NULL,
[RoleID] [int] NOT NULL,
PRIMARY KEY CLUSTERED
(
[UserID] ASC,
[OrgID] ASC,
[RoleID] ASC
)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]






Re: Recursive Multi-Level Query using CTE. Attn: Experts!

Kent Waldrop 2007 Mar

Omaits:

I have assumed that you want the output ordered according to the hierarchy.  Also, I used data that was different than what you originally posted because I feel that the data provided does not test the routine sufficiently.  Actually, this data also might not test the routine sufficiently.  This is my first-pass guess:

-- --------------------------------------------------------
--   One problem that frequents lists of hierarchies is
--   that frequently it is desired to order the output
--   according to the hierarchy.  The problem with this is
--   that the order of an item is not only dependent on the
--   item itself but also its parent, its parent's parent,
--   etc.  This is not a simple ordering mechanism.
--
--   Therefore, this routine keeps string-list of the
--   hierarchy and sorts based on the hierarchy contained
--   in the string-list.  The hierarchy can be described as
--   a vector; the string-list is one way of abstracting
--   this kind of vector.
-- --------------------------------------------------------
declare @Org table
   (    OrgID       integer,
        ParentId    integer
   )
insert into @org values (200, null)
insert into @org values (111, 200)
insert into @org values (112, 200)
insert into @org values (105, 111)
insert into @org values (106, 111)
insert into @org values (107, 105)
insert into @org values (120, 105)
insert into @org values (131, 106)
insert into @org values (122, 106)
insert into @org values (121, 106)
select * from @org

--   OrgID       ParentId
--   ----------- -----------
--   200         NULL
--   111         200
--   112         200
--   105         111
--   106         111
--   107         105
--   120         105
--   131         106
--   122         106
--   121         106

declare @UserOrgRole table
   (    UserID      varchar(10),
        OrgID       integer,
        RoleID      integer
   )
insert into @UserOrgRole values ('User1', 200, 1)
insert into @UserOrgRole values ('User1', 111, 2)
insert into @UserOrgRole values ('User1', 112, 1)
insert into @UserOrgRole values ('User1', 105, 1)
insert into @UserOrgRole values ('User1', 106, 1)
insert into @UserOrgRole values ('User1', 107, 1)
insert into @UserOrgRole values ('User1', 120, 1)
insert into @UserOrgRole values ('User1', 131, 1)
insert into @UserOrgRole values ('User1', 122, 1)
insert into @UserOrgRole values ('User1', 121, 1)

--select * from @UserOrgRole

declare @prm_targetUser varchar(10)
declare @prm_targetRole integer

set @prm_targetUser = 'User1'
set @prm_targetRole = 2

;with userOrgRoleCTE
as
( select cast(1 as integer) as depth,
         a.OrgID,
         case when RoleId <> @prm_targetRole then 0
              else roleId
         end as RoleId,
         cast(convert(char(11), a.orgId) as varchar(111))
            as hierarchy
   from @UserOrgRole a
  inner join @org b
     on a.OrgId = b.orgId
    and b.parentId is null
    and UserID = @prm_targetUser
  union all
  select depth + 1 as depth,
         c.orgId,
         case when a.RoleId = @prm_targetRole then a.roleId
              when a.roleId <> @prm_targetRole
               and c.roleId = @prm_targetRole then c.roleId
              else 0
         end as RoleId,
         cast(a.hierarchy + convert(char(11), c.orgId) as varchar(111))
             as hierarchy
    from userOrgRoleCte a
   inner join @org b
      on a.orgId = b.parentId
   inner join @userOrgRole c
      on b.orgId = c.orgId
     and c.UserId = @prm_targetUser
)
select * from userOrgRoleCTE
--where roleId <> 0
order by hierarchy
--order by OrgId

-- --------------------------------------------
--   To remove the rows in which "RoleID" is
--   zero, simply uncomment the WHERE clause
-- --------------------------------------------

--   depth  OrgID  RoleId hierarchy
--   ------ ------ ------ ---------------------
--   1      200    0      200       
--   2      111    2      200   111       
--   3      105    2      200   111   105     
--   4      107    2      200   111   105   107
--   4      120    2      200   111   105   120
--   3      106    2      200   111   106     
--   4      121    2      200   111   106   121
--   4      122    2      200   111   106   122
--   4      131    2      200   111   106   131
--   2      112    0      200   112  
 


-- --------------------------------------------
--  If you simply order by "OrgID" you receive
--  the output in this order; this is probably
--  not what is wanted.
-- --------------------------------------------

--   depth  OrgID  RoleId hierarchy
--   ------ ------ ------ ---------------------
--   3      105    2      200   111   105     
--   3      106    2      200   111   106     
--   4      107    2      200   111   105   107
--   2      111    2      200   111  
--   2      112    0      200   112  
--   4      120    2      200   111   105   120
--   4      121    2      200   111   106   121
--   4      122    2      200   111   106   122
--   4      131    2      200   111   106   131
--   1      200    0      200       

A couple of things: first, if the desired sort order IS by OrgID the "hierarchy" column can be removed from the CTE -- for sorting by OrgID it brings unwanted overhead.

Also, I just realized that I did not build my vectors correctly.  The individual strings need to be right-justified instead of left-justified so that they order correctly.  I will shortly post a modified version of this query that fixes this problem.





Re: Recursive Multi-Level Query using CTE. Attn: Experts!

omaits

Sweet Mother of God, that is the most beautiful thing I have ever seen. I have no idea how you came up with that so quickly. I plugged it into my table structure and it is 99.9% the way there. The only thing is that it does not return a record for a child if no record exists in the UserOrgTable for that User.

It returns only records for children where there is a record in the UserOrgRole table for that particular org.

In our tables, child records do not necessarily have a role. If the root node is 2, all other nodes could have no record associated and it should still return 2. I apologize if I did not make this clear initially. Does that make sense

Is this a minor modification to the script Hopefully, it will be a different join type, but I wouldn't know where to begin with that statement you wrote! I am so excited to have this working so I can continue writing code.




Re: Recursive Multi-Level Query using CTE. Attn: Experts!

Kent Waldrop 2007 Mar

Yes, I think this is a minor mod; I will include this modification when I switch from a left-justified to a right-justified vector.  I think a LEFT JOIN and use of the ISNULL function will fix this.  Give me a few minutes.


Kent





Re: Recursive Multi-Level Query using CTE. Attn: Experts!

Kent Waldrop 2007 Mar

I forgot: You can't outer join in a CTE; not as simple as I had hoped.



Re: Recursive Multi-Level Query using CTE. Attn: Experts!

omaits

No luck yet for me either. The first thing I tried was the outer join as well. That seems like a big limitation, but I can understand why you can't use one.




Re: Recursive Multi-Level Query using CTE. Attn: Experts!

Kent Waldrop 2007 Mar

Here is what I have come up with. This bothers me because it is now more complex than I envisioned and this concerns me because simple code is often better than complicated code. Hopefully, somebody else -- maybe Mark -- can come up with a better way of doing this. It seems to work:

-- --------------------------------------------------------
-- One problem that frequents lists of hierarchies is
-- that frequently it is desired to order the output
-- according to the hierarchy. The problem with this is
-- that the order of an item is not only dependent on the
-- item itself but also its parent, its parent's parent,
-- etc. This is not a simple ordering mechanism.
--
-- Therefore, this routine keeps string-list of the
-- hierarchy and sorts based on the hierarchy contained
-- in the string-list. The hierarchy can be described as
-- a vector; the string-list is one way of abstracting
-- this kind of vector.
-- --------------------------------------------------------
declare @Org table
( OrgID integer,
ParentId integer
)
insert into @org values (200, null)
insert into @org values (111, 200)
insert into @org values (112, 200)
insert into @org values (105, 111)
insert into @org values (106, 111)
insert into @org values (107, 105)
insert into @org values (120, 105)
insert into @org values (131, 106)
insert into @org values (122, 106)
insert into @org values (121, 106)
--select * from @org

-- OrgID ParentId
-- ----------- -----------
-- 200 NULL
-- 111 200
-- 112 200
-- 105 111
-- 106 111
-- 107 105
-- 120 105
-- 131 106
-- 122 106
-- 121 106

declare @UserOrgRole table
( UserID varchar(10),
OrgID integer,
RoleID integer
)
/*
insert into @UserOrgRole values ('User1', 200, 1)
insert into @UserOrgRole values ('User1', 111, 2)
insert into @UserOrgRole values ('User1', 112, 1)
insert into @UserOrgRole values ('User1', 105, 1)
insert into @UserOrgRole values ('User1', 106, 1)
insert into @UserOrgRole values ('User1', 107, 1)
insert into @UserOrgRole values ('User1', 120, 1)
insert into @UserOrgRole values ('User1', 131, 1)
insert into @UserOrgRole values ('User1', 122, 1)
insert into @UserOrgRole values ('User1', 121, 1)
*/

insert into @UserOrgRole values ('User1', 111, 2)
--select * from @UserOrgRole

declare @prm_targetUser varchar(10)
declare @prm_targetRole integer

set @prm_targetUser = 'User1'
set @prm_targetRole = 2

-- --------------------------------------------------------
-- This version is modified to build the hierarchy
-- vectors right-justified instead of left-justified.
-- --------------------------------------------------------
;with userOrgRoleCTE
as
( select cast(1 as integer) as depth,
a.OrgID,
case when RoleId <> @prm_targetRole then 0
else roleId
end as RoleId,
cast(right(' ' + convert(varchar(11),
a.orgId), 11) as varchar(111))
as hierarchy
from @UserOrgRole a
inner join @org b
on a.OrgId = b.orgId
and b.parentId is null
and UserID = @prm_targetUser
union all
select cast(1 as integer) as depth,
a.OrgID,
0 as RoleId,
cast(right(' ' + convert(varchar(11),
a.orgId), 11) as varchar(111))
as hierarchy
from @org a
where a.parentId is null
and not exists
( select 0 from @UserOrgRole b
where a.orgId = b.orgId
and a.parentId is null
and UserId = @prm_targetUser
)
union all
select depth + 1 as depth,
b.orgId,
case when a.RoleId = @prm_targetRole then a.roleId
when a.roleId <> @prm_targetRole
and c.roleId = @prm_targetRole then c.roleId
else 0
end as RoleId,
cast(a.hierarchy + right(' '
+ convert(varchar(11), b.orgId), 11) as varchar(111))
as hierarchy
from userOrgRoleCte a
inner join @org b
on a.orgId = b.parentId
inner join @userOrgRole c
on b.orgId = c.orgId
and c.UserId = @prm_targetUser
union all
select depth + 1 as depth,
b.orgId,
case when a.RoleId = @prm_targetRole then a.roleId
else 0
end as RoleId,
cast(a.hierarchy + right(' '
+ convert(varchar(11), b.orgId), 11) as varchar(111))
as hierarchy
from userOrgRoleCte a
inner join @org b
on a.orgId = b.parentId
and not exists
( select 0 from @userOrgRole d
where b.orgId = d.orgId
and d.UserId = @prm_targetUser
)
)
select * from userOrgRoleCTE
--where roleId <> 0
order by hierarchy

-- --------------------------------------------
-- To remove the rows in which "RoleID" is
-- zero, simply uncomment the WHERE clause
-- --------------------------------------------

-- depth OrgID RoleId hierarchy
-- ------ ------ ------ ------------------------
-- 1 200 0 200
-- 2 111 2 200 111
-- 3 105 2 200 111 105
-- 4 107 2 200 111 105 107
-- 4 120 2 200 111 105 120
-- 3 106 2 200 111 106
-- 4 121 2 200 111 106 121
-- 4 122 2 200 111 106 122
-- 4 131 2 200 111 106 131
-- 2 112 0 200 112





Re: Recursive Multi-Level Query using CTE. Attn: Experts!

omaits

Kent, I am forever if your debt for this. I worked on this for days and never got close. What you provided seems to do the job. I wish I could offer something in return for your favor. If you ever need anything .Net related, omaits_is(-at-)hotmail(-dot-)com.

Once again thank you so much for your time.




Re: Recursive Multi-Level Query using CTE. Attn: Experts!

Kent Waldrop 2007 Mar

Actually, you have given a very good gift -- you have offered to also give. And there lies my request; please pass the blessing that you have received in turn to someone else. When you have an opportunity to help someone else, please do so. I am also trying to pass it on. You are welcome!


Kent





Re: Recursive Multi-Level Query using CTE. Attn: Experts!

FransM

Hi Kent,

I have a similar issue like the post above.

Hopefully you are willing to translate this code to my situation.
This is what I would like to achieve:

ReceptuurID ArtikelID ParentArtikelID Level Sorting
22386 50580 NULL 0 22386/50580
22386 50159 50580 1 22386/50580/50159
22386 50239 50580 1 22386/50580/50239
22386 50150 50580 1 22386/50580/50150
22386 50153 50580 1 22386/50580/50153
22386 50154 50580 1 22386/50580/50154
22386 50155 50580 1 22386/50580/50155
22386 50156 50580 1 22386/50580/50156
22386 50157 50580 1 22386/50580/50157
22386 50151 50150 2 22386/50580/50150/50151
22386 50152 50150 2 22386/50580/50150/50152
22386 51568 50151 3 22386/50580/50150/50/151/51568
22386 51569 50580 1 22386/50580/51569
22386 51570 50580 1 22386/50580/51570

I have a view which gives me the the first three columns. Group by on ReceptuurID (only 1 shown in thius example)

Level and Sorting are the columns which I want to add in another view.

Thanks in advance for your help.

Frans





Re: Recursive Multi-Level Query using CTE. Attn: Experts!

Kent Waldrop No07

Hello, Frans; I will be glad to see what I can do. I will try to get you an answer shortly.

I am not sure I understand the sort of this list, so I will need help understanding the sort. Here is my first pass:

Code Block

declare @xample table
( ReceptuurID int,
ArtikelID int,
ParentArtikelID int
)
insert into @xample
select 22386, 50580, NULL union all
select 22386, 50159, 50580 union all
select 22386, 50239, 50580 union all
select 22386, 50150, 50580 union all
select 22386, 50153, 50580 union all
select 22386, 50154, 50580 union all
select 22386, 50155, 50580 union all
select 22386, 50156, 50580 union all
select 22386, 50157, 50580 union all
select 22386, 50151, 50150 union all
select 22386, 50152, 50150 union all
select 22386, 51568, 50151 union all
select 22386, 51569, 50580 union all
select 22386, 51570, 50580
--select * from @xample

;with hier as
( select ReceptuurID,
ArtikelID,
ParentArtikelID,
cast(0 as int) as Level,
cast(
right(' ' + convert
(varchar(11), ReceptuurID),11) + '/' +
right(' ' + convert
(varchar(11), ArtikelID), 11)
as varchar(240))
as Sorting
from @xample
where ParentArtikelID is null
union all
select b.ReceptuurID,
b.ArtikelID,
b.ParentArtikelID,
a.Level + 1 as Level,
cast(a.Sorting + '/' +
right(' ' + convert
(varchar(11), b.ArtikelID), 11)
as varchar(240))
as Sorting
from hier a
join @xample b
on a.ArtikelID = b.ParentArtikelID
and a.Level <= 20 -- to avoid an infinite loop
)
select ReceptuurID,
ArtikelID,
ParentArtikelID,
Level,
replace(Sorting, ' ', '') as Sorting
from hier
order by Sorting

/* -------- Sample Output: --------
ReceptuurID ArtikelID ParentArtikelID Level Sorting
----------- ----------- --------------- ----------- -----------------------------
22386 50580 NULL 0 22386/50580
22386 50150 50580 1 22386/50580/50150
22386 50151 50150 2 22386/50580/50150/50151
22386 51568 50151 3 22386/50580/50150/50151/51568
22386 50152 50150 2 22386/50580/50150/50152
22386 50153 50580 1 22386/50580/50153
22386 50154 50580 1 22386/50580/50154
22386 50155 50580 1 22386/50580/50155
22386 50156 50580 1 22386/50580/50156
22386 50157 50580 1 22386/50580/50157
22386 50159 50580 1 22386/50580/50159
22386 50239 50580 1 22386/50580/50239
22386 51569 50580 1 22386/50580/51569
22386 51570 50580 1 22386/50580/51570
*/