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 |
secuchalan
Starting Member
19 Posts |
Posted - 2012-04-27 : 03:37:50
|
I am trying to get the hierarchy level(s) of a classification using an already specified database hierarchy level. In addition to that, I’d like to provide a column that will store the full classification hierarchal path of any classification separated by a delimiter such as “/”.I’m looking at two tables namely:1) class structure table which identifies the parent of the classification by classtructureID 2) class ancestor table which already identifies the hierarchy levels per classstructureID (in reverse or descending order) My goal is to be able to provide the full path (from parent to child) of a classstructureID that is possibly 4 levels deep. For example: HARDWARE 33C1 MAINTENANCE FOR OTHER WITH ClassstructureID = 1034 Level 1: HWLevel 2: 3CC1Level 3: MAINTENANCELevel 4: OTHERFull Classification Path HW\3CC1\MAINTENANCE\OTHER--Populate test dataDECLARE @CLASSSTRUCTURETABLE (CSID INT, CLASSID VARCHAR(20), PARENT INT, DESCR VARCHAR(200))INSERT INTO @CLASSSTRUCTURE (CSID, CLASSID, PARENT, DESCR)SELECT 1002,'HW',NULL,'Hardware' UNION ALLSELECT 1029,'3CC1',1002,'3CC1' UNION ALLSELECT 1030,'ACCESS',1029,'Access' UNION ALLSELECT 14963,'BUILD',1029,'Build' UNION ALLSELECT 14964,'CODECHANGE',1029,'Code Change' UNION ALLSELECT 14965,'DBUPDATE',1029,'Database Update' UNION ALLSELECT 1032,'INSTALL',1029,'Install' UNION ALLSELECT 1033,'MAINTENANCE',1029,'Maintenance' UNION ALLSELECT 1034,'OTHER',1033,'Other Maint' --Populate test dataDECLARE @CLASSANCESTORTABLE (CSID INT, CLASSID VARCHAR(20), ANCESTORCSID INT, ANCESORTCLASSID VARCHAR(200), HIERARCHYLEVELS INT)INSERT INTO @CLASSANCESTOR (CSID, CLASSID, ANCESTORCSID, ANCESORTCLASSID,HIERARCHYLEVELS)SELECT 1001,'SW',1001,'SW',0 UNION ALLSELECT 1002,'HW',1002,'HW',0 UNION ALLSELECT 1029,'3CC1',1002,'HW',1 UNION ALLSELECT 1029,'3CC1',1029,'3CC1',0 UNION ALLSELECT 1030,'ACCESS',1002,'HW',2 UNION ALLSELECT 1030,'ACCESS',1029,'3CC1',1 UNION ALLSELECT 1030,'ACCESS',1030,'ACCESS',0 UNION ALLSELECT 1032,'INSTALL',1002,'HW',2 UNION ALLSELECT 1032,'INSTALL',1029,'3CC1',1 UNION ALLSELECT 1032,'INSTALL',1032,'INSTALL',0 UNION ALLSELECT 1033,'MAINTENANCE',1002,'HW',2 UNION ALLSELECT 1033,'MAINTENANCE',1029,'3CC1',1 UNION ALLSELECT 1033,'MAINTENANCE',1033,'MAINTENANCE',0 UNION ALLSELECT 14963,'BUILD',1002,'HW',2 UNION ALLSELECT 14963,'BUILD',1029,'3CC1',1 UNION ALLSELECT 14963,'BUILD',14963,'BUILD',0 UNION ALLSELECT 14964,'CODECHANGE',1002,'HW',2 UNION ALLSELECT 14964,'CODECHANGE',1029,'3CC1',1 UNION ALLSELECT 14964,'CODECHANGE',14964,'CODECHANGE',0 UNION ALLSELECT 14965,'DBUPDATE',1002,'HW',2 UNION ALLSELECT 14965,'DBUPDATE',1029,'3CC1',1 UNION ALLSELECT 14965,'DBUPDATE',14965,'DBUPDATE',0 UNION ALLSELECT 1034,'OTHER MAINT',1002,'HW',3 UNION ALLSELECT 1034,'OTHER MAINT',1029,'3CC1',2 UNION ALLSELECT 1034,'OTHER MAINT',1033,'MAINTENANCE',1 UNION ALLSELECT 1034,'OTHER MAINT',1034,'OTHER',0 --PARENT AND CHILD CLASSIFICATIONS--SELECT *FROM @CLASSSTRUCTURE CSID CLASSID PARENT DESCR 1002 HW NULL Hardware1029 3CC1 1002 3CC11030 ACCESS 1029 Access14963 BUILD 1029 Build14964 CODECHANGE 1029 Code Change14965 DBUPDATE 1029 Database Update1032 INSTALL 1029 Install1033 MAINTENANCE 1029 Maintenance1034 OTHER 1033 Other Maint ---CLASSANCESTOR TABLE THAT ENUMERATES HIERARCHYLEVELS IN REVERSE--SELECT *,ROW_NUMBER() OVER (PARTITION BY CSID ORDER BY hierarchylevels DESC) AS CSLEVEL --I USED ROW NUMBER TO GET LEVELSFROM @CLASSANCESTOR Note the level starts at 0 and if it has more than one level it will be in reverse order hence 2\1\0 is supposedly 0\1\2.CSID CLASSID ANCESTORCSID ANCESORTCLASSID HIERARCHYLEVELS CSLEVEL 1001 SW 1001 SW 0 11002 HW 1002 HW 0 11029 3CC1 1002 HW 1 11029 3CC1 1029 3CC1 0 21030 ACCESS 1002 HW 2 11030 ACCESS 1029 3CC1 1 21030 ACCESS 1030 ACCESS 0 31032 INSTALL 1002 HW 2 11032 INSTALL 1029 3CC1 1 21032 INSTALL 1032 INSTALL 0 31033 MAINTENANCE 1002 HW 2 11033 MAINTENANCE 1029 3CC1 1 21033 MAINTENANCE 1033 MAINTENANCE 0 31034 OTHER MAINT 1002 HW 3 11034 OTHER MAINT 1029 3CC1 2 21034 OTHER MAINT 1033 MAINTENANCE 1 31034 OTHER MAINT 1034 OTHER 0 414963 BUILD 1002 HW 2 114963 BUILD 1029 3CC1 1 214963 BUILD 14963 BUILD 0 314964 CODECHANGE 1002 HW 2 114964 CODECHANGE 1029 3CC1 1 214964 CODECHANGE 14964 CODECHANGE 0 314965 DBUPDATE 1002 HW 2 114965 DBUPDATE 1029 3CC1 1 214965 DBUPDATE 14965 DBUPDATE 0 3 I tried to use the rownumber to figure out the levels, however doing so makes the my query extremely slow. This is the query that sort of works, but produces incorrect levels.Here is my current query:SELECT classstructureid AS CSID ,classificationid AS CLASSID ,PARENT ,description AS DESCR --CLASSIFICATION LEVELS ,( SELECT ancestorclassid FROM classancestor WHERE (classstructureid = CS.classstructureid) AND (hierarchylevels = 2) ) AS LEVEL1 ,( SELECT ancestorclassid FROM classancestor WHERE (classstructureid = CS.classstructureid) AND (hierarchylevels = 1) ) AS LEVEL2 ,( SELECT ancestorclassid FROM classancestor WHERE (classstructureid = CS.classstructureid) AND (hierarchylevels = 0) ) AS LEVEL3 --BUILD CLASSIFICATION HIERARCHY ,CASE WHEN --HAS 3 CLASSIFICATIONS ( SELECT ancestorclassid FROM classancestor WHERE (classstructureid = CS.classstructureid) AND (hierarchylevels = 2) ) IS NOT NULL THEN ( SELECT ancestorclassid FROM classancestor WHERE (classstructureid = CS.classstructureid) AND (hierarchylevels = 2) ) + '\' + ( SELECT ancestorclassid FROM classancestor WHERE (classstructureid = CS.classstructureid) AND (hierarchylevels = 1) ) + '\' + ( SELECT ancestorclassid FROM classancestor WHERE (classstructureid = CS.classstructureid) AND (hierarchylevels = 0) ) WHEN --HAS 2 CLASSIFICATIONS ( SELECT ancestorclassid FROM classancestor WHERE (classstructureid = CS.classstructureid) AND (hierarchylevels = 2) ) IS NULL AND ( SELECT ancestorclassid FROM classancestor WHERE (classstructureid = CS.classstructureid) AND (hierarchylevels = 1) ) IS NOT NULL THEN ( SELECT ancestorclassid FROM classancestor WHERE (classstructureid = CS.classstructureid) AND (hierarchylevels = 1) ) + '\' + ( SELECT ancestorclassid FROM classancestor WHERE (classstructureid = CS.classstructureid) AND (hierarchylevels = 0) ) WHEN --HAS 1 CLASSIFICATION ( SELECT ancestorclassid FROM classancestor WHERE (classstructureid = CS.classstructureid) AND (hierarchylevels = 1) ) IS NULL AND ( SELECT ancestorclassid FROM classancestor WHERE (classstructureid = CS.classstructureid) AND (hierarchylevels = 0) ) IS NOT NULL THEN ( SELECT ancestorclassid FROM classancestor WHERE (classstructureid = CS.classstructureid) AND (hierarchylevels = 0)) END AS PATHFROM classstructure AS CSORDER BY PATHINCORRECT QUERY RESULT CSID CLASSID PARENT DESCR LEVEL1 LEVEL2 LEVEL3 PATH 1002 HW NULL Hardware NULL NULL HW HW 1029 3CC1 1002 3CC1 NULL HW 3CC1 HW\3CC1 1030 ACCESS 1029 Access HW 3CC1 ACCESS HW\3CC1\ACCESS 14963 BUILD 1029 Build HW 3CC1 BUILD HW\3CC1\BUILD 14964 CODECHANGE 1029 Code Change HW 3CC1 CODECHANGE HW\3CC1\CODECHANGE 14965 DBUPDATE 1029 Database Update HW 3CC1 DBUPDATE HW\3CC1\DBUPDATE 1032 INSTALL 1029 Install HW 3CC1 INSTALL HW\3CC1\INSTALL 1033 MAINTENANCE 1029 Maintenance HW 3CC1 MAINTENANCE HW\3CC1\MAINTENANCE 1034 OTHER 1033 Other Maint HW 3CC2 MAINTENANCE HW\3CC1\MAINTENANCE\OTHER DESIRED QUERY RESULT CSID CLASSID PARENT DESCR LEVEL1 LEVEL2 LEVEL3 LEVEL4 PATH 1002 HW NULL Hardware HW NULL NULL NULL HW1029 3CC1 1002 3CC1 HW 3CC1 NULL NULL HW\3CC11030 ACCESS 1029 Access HW 3CC1 ACCESS NULL HW\3CC1\ACCESS14963 BUILD 1029 Build HW 3CC1 BUILD NULL HW\3CC1\BUILD14964 CODECHANGE 1029 Code Change HW 3CC1 CODECHANGE NULL HW\3CC1\CODECHANGE14965 DBUPDATE 1029 Database Update HW 3CC1 DBUPDATE NULL HW\3CC1\DBUPDATE1032 INSTALL 1029 Install HW 3CC1 INSTALL NULL HW\3CC1\INSTALL1033 MAINTENANCE 1029 Maintenance HW 3CC1 MAINTENANCE NULL HW\3CC1\MAINTENANCE1034 OTHER 1033 Other Maint HW 3CC2 MAINTENANCE OTHER HW\3CC1\MAINTENANCE\OTHER Any input will be greatly appreciated. Thanks in advance. |
|
Lumbago
Norsk Yak Master
3271 Posts |
Posted - 2012-04-27 : 05:48:05
|
What is this @Classancestor-table for? Seems like none of it's data is used in the desired output. But your problem is far easier solved with a recursive cte query:;WITH cte AS ( SELECT x.CSID, x.CLASSID, x.PARENT, x.DESCR, Level = 0, Path = CAST('\' + x.CLASSID as varchar(max)) FROM @CLASSSTRUCTURE x WHERE x.PARENT IS NULL UNION ALL SELECT a.CSID, a.CLASSID, a.PARENT, a.DESCR, Level = b.Level + 1, Path = b.Path + '\' + a.CLASSID FROM @CLASSSTRUCTURE a INNER JOIN cte b ON b.CSID = a.PARENT )SELECT * FROM cte It doesn't have the level-columns in it (yet) but it's just a matter of splitting the Path properly...- LumbagoMy blog-> http://thefirstsql.com/2011/07/08/how-to-find-gaps-in-identity-columns-at-the-speed-of-light/ |
 |
|
Lumbago
Norsk Yak Master
3271 Posts |
Posted - 2012-04-27 : 06:06:44
|
Here you go, a bit lame with the sub-selects but it does the trick:WITH cte AS ( SELECT x.CSID, x.CLASSID, x.PARENT, x.DESCR, Level = 1, Path = CAST('\' + x.CLASSID as varchar(max)) FROM @CLASSSTRUCTURE x WHERE x.PARENT IS NULL UNION ALL SELECT a.CSID, a.CLASSID, a.PARENT, a.DESCR, Level = b.Level + 1, Path = b.Path + '\' + a.CLASSID FROM @CLASSSTRUCTURE a INNER JOIN cte b ON b.CSID = a.PARENT )SELECT *, Level1 = (SELECT TOP 1 CLASSID FROM cte WHERE Level = 1 AND Level <= a.Level), Level2 = (SELECT TOP 1 CLASSID FROM cte WHERE Level = 2 AND Level <= a.Level), Level3 = (SELECT TOP 1 CLASSID FROM cte WHERE Level = 3 AND Level <= a.Level), Level4 = (SELECT TOP 1 CLASSID FROM cte WHERE Level = 4 AND Level <= a.Level)FROM cte a - LumbagoMy blog-> http://thefirstsql.com/2011/07/08/how-to-find-gaps-in-identity-columns-at-the-speed-of-light/ |
 |
|
Lumbago
Norsk Yak Master
3271 Posts |
Posted - 2012-04-27 : 06:10:42
|
Arrrgh...crap...not right after all. Close but not there yet.- LumbagoMy blog-> http://thefirstsql.com/2011/07/08/how-to-find-gaps-in-identity-columns-at-the-speed-of-light/ |
 |
|
Lumbago
Norsk Yak Master
3271 Posts |
Posted - 2012-04-27 : 07:24:42
|
Reeeally lame subselects, there are almost certainly better ways to split this Path-string, but I think it gives the right result now ;WITH cte AS ( SELECT x.CSID, x.CLASSID, x.PARENT, x.DESCR, Level = 1, Path = CAST('\' + x.CLASSID as varchar(max)) FROM @CLASSSTRUCTURE x WHERE x.PARENT IS NULL UNION ALL SELECT a.CSID, a.CLASSID, a.PARENT, a.DESCR, Level = b.Level + 1, Path = b.Path + '\' + a.CLASSID FROM @CLASSSTRUCTURE a INNER JOIN cte b ON b.CSID = a.PARENT )SELECT *, Level1 = CASE WHEN Level = 1 THEN CLASSID ELSE (SELECT TOP 1 CLASSID FROM cte WHERE Level = 1 AND Level <= a.Level) END, Level2 = CASE WHEN Level = 2 THEN CLASSID ELSE (SELECT TOP 1 CLASSID FROM cte WHERE Level = 2 AND Level <= a.Level) END, Level3 = CASE WHEN Level = 3 THEN CLASSID ELSE (SELECT TOP 1 CLASSID FROM cte WHERE Level = 3 AND Level <= a.Level) END, Level4 = CASE WHEN Level = 4 THEN CLASSID ELSE (SELECT TOP 1 CLASSID FROM cte WHERE Level = 4 AND Level <= a.Level) ENDFROM cte a - LumbagoMy blog-> http://thefirstsql.com/2011/07/08/how-to-find-gaps-in-identity-columns-at-the-speed-of-light/ |
 |
|
secuchalan
Starting Member
19 Posts |
Posted - 2012-04-27 : 18:28:18
|
Lumbago thank you for your help, however when I ran this exact query against the classstructure table with 13514 rows it took more than 6 minutes to finish and in turn provided the correct path but gave incorrect level results. I think it's because it is referencing previous records?This list will be joined to a ticket table for reference and that has 100,000+ records currently. I'm afraid joining the tables will make it slow.classstructureid classificationid parent description Level Path Level1 Level2 Level3 Level41002 HW NULL Hardware 1 \HW HW NULL NULL NULL1029 3CC1 1002 3CC1 2 \HW\3CC1 44000000 3CC1 NULL NULL1030 ACCESS 1029 Access 3 \HW\3CC1\ACCESS 44000000 81112305 ACCESS NULL14963 BUILD 1029 Build 3 \HW\3CC1\BUILD 44000000 81112305 BUILD NULL14964 CODECHANGE 1029 Code Change 3 \HW\3CC1\CODECHANGE 44000000 81112305 CODECHANGE14965 DBUPDATE 1029 Database Update 3 \HW\3CC1\DBUPDATE 44000000 81112305 DBUPDATE1032 INSTALL 1029 Install 3 \HW\3CC1\INSTALL 44000000 81112305 INSTALL NULL1033 MAINTENANCE 1029 Maintenance 3 \HW\3CC1\MAINTENANCE 44000000 81112305 MAINTENANCE NULL (8 row(s) affected) |
 |
|
Vinnie881
Master Smack Fu Yak Hacker
1231 Posts |
Posted - 2012-04-27 : 22:51:28
|
This gives you your desired results, but but I am not sure how you want to use your class ansester table, but it should be easy to figure out from here.drop function f_GetEntryDelimiittedgoCreate function f_GetEntryDelimiitted ( @line varchar(4000) , @fldnum int , @delim varchar(10) , @quoted varchar(1) -- Y/N )returns varchar(400)asbegindeclare @i int , @j int , @delimUsed varchar(11) , @s varchar(4000) select @i = 1 while @fldnum > 0 begin select @fldnum = @fldnum - 1 if substring(@line, @i, 1) = '"' and @Quoted = 'Y' begin select @delimUsed = '"' + @Delim , @i = @i + 1 end else begin select @delimUsed = @Delim end select @j = charindex(@delimUsed, @line, @i) if @j = 0 select @j = datalength(@line) + 1 if @fldnum > 0 select @i = @j +len(@delimused) end select @s = substring(@line, @i, @j - @i) return @sendgo--Populate test dataDECLARE @CLASSSTRUCTURETABLE (CSID INT, CLASSID VARCHAR(20), PARENT INT, DESCR VARCHAR(200))INSERT INTO @CLASSSTRUCTURE (CSID, CLASSID, PARENT, DESCR)SELECT 1002,'HW',NULL,'Hardware' UNION ALLSELECT 1029,'3CC1',1002,'3CC1' UNION ALLSELECT 1030,'ACCESS',1029,'Access' UNION ALLSELECT 14963,'BUILD',1029,'Build' UNION ALLSELECT 14964,'CODECHANGE',1029,'Code Change' UNION ALLSELECT 14965,'DBUPDATE',1029,'Database Update' UNION ALLSELECT 1032,'INSTALL',1029,'Install' UNION ALLSELECT 1033,'MAINTENANCE',1029,'Maintenance' UNION ALLSELECT 1034,'OTHER',1033,'Other Maint' --Populate test data;with mycteas(select *, cast(classid as varchar(max)) as p,0 as itterationfrom @CLASSSTRUCTUREwhere parent is nullunion allselect b.*, p + '\' + b.CLASSID ,a.itteration + 1from mycte ainner join @CLASSSTRUCTURE bon a.CSID = b.PARENT )select csid,classid,parent,descr,dbo.f_GetEntryDelimiitted(p,1,'\','N') as level1,case when itteration >= 1 then dbo.f_GetEntryDelimiitted(p,2,'\','N') else null end as level2 ,case when itteration >= 2 then dbo.f_GetEntryDelimiitted(p,3,'\','N') else null end as level3 ,case when itteration >= 3 then dbo.f_GetEntryDelimiitted(p,4,'\','N') else null end as level4,p as pathfrommycte aborder by p Success is 10% Intelligence, 70% Determination, and 22% Stupidity.\_/ _/ _/\_/ _/\_/ _/ _/- 881 |
 |
|
Lumbago
Norsk Yak Master
3271 Posts |
Posted - 2012-04-28 : 12:10:46
|
Can you try to run the query against the real table with just the "select * from cte" and see how long it takes? Figuring out the Level1-4 columns is just a matter of decomposing the Path and that can be done far more efficiently than what I did. And how is the index situation? Do the CSID and Parent columns have indexes?- LumbagoMy blog-> http://thefirstsql.com/2011/07/08/how-to-find-gaps-in-identity-columns-at-the-speed-of-light/ |
 |
|
Lumbago
Norsk Yak Master
3271 Posts |
Posted - 2012-04-28 : 12:14:52
|
You should also look at this link, sql server has some built-in fuctions to handle hierarchy data:http://msdn.microsoft.com/en-us/library/bb677173(v=sql.105).aspx- LumbagoMy blog-> http://thefirstsql.com/2011/07/08/how-to-find-gaps-in-identity-columns-at-the-speed-of-light/ |
 |
|
|
|
|
|
|