开发者

SQL Server CTE Recursion - Tracing a tree back to a certain tree level for each node

I have two tables, Item (ItemID, CatID, Name) and Category (CatID, ParentID, Name, CatLevel). Category is a tree hierarchy of categories that items can be in. The table also tracks what category level each node is in. Here's an example:

1. Animal (CatLevel = 1)
    2. Dog (CatLevel = 2)
        3. Beagle (CatLevel = 3)
        4. Labrador (CatLevel = 3)
            5. Yellow (CatLevel = 4)
            6. Chocolate (CatLevel = 4)
            7. Black (CatLevel = 4)
    8. Cat (CatLevel = 2)
        9. Tabby (CatLevel = 3)
    10. Horse (CatLevel = 2)
        11. Stallion (CatLevel = 3)

I'm trying to come up with a query that gives me a list of items with their regular CatID and a CatID at a level I specify (for example, level 2). Example output:

Item      CatID    LevelTwoCatID
______    _____    _____________
Dog       2        2
Beagle    3 开发者_高级运维       2
Labrador  4        2
Yellow    5        2
Chocolate 6        2
Black     7        2
Cat       8        8
Tabby     9        8
Horse     10       10
Stallion  11       10

I know how to trace back up to the category of a single node, but I don't know how to make a more generic query that will provide the "LevelTwoCatID" for every node. Here's how I'd do it for one:

WITH Tree AS
(
    SELECT * FROM Category WHERE CatID = 6 -- Chocolate lab, change for some other

    UNION ALL

    SELECT * FROM Category AS a
    INNER JOIN Tree t ON a.CatID = t.ParentID
)
SELECT * FROM Tree WHERE CatLevel = 2

How can I get the CatLevel = 2 node ID for all nodes/leaves?

Thanks!


I know the post is quite old, but here is how I did that.

In my table there are several root categories, therefore I use @RootId to specify which one I am after. Root category has ParentID set to NULL.

If you do not have CatLevel column in your categories table - cte Tree creates it for you. If you are going to use the query frequently, it is better to add it to the table (UPDATE code is commented in the end).

If you wish to have dynamic number of columns (not fixed to 5 as in my case), make a dynamic SQL query for it.

DECLARE @RootId INT, @SearchId INT
SET @RootId = 0 
SET @SearchId = 0

--SELECT it.Id, it.ParentId, it.Name, it.RootId FROM ItemTree it 
--WHERE (@RootId = 0 OR it.RootId = @RootId) AND (@SearchId = 0 OR it.Id = @SearchId)

;WITH Tree AS --gets category levels
(
  SELECT 1 as CatLevel, it.Id, it.ParentId, it.Name, it.RootId
  FROM ItemTree it WHERE it.ParentId IS NULL AND (@RootId = 0 OR it.RootId = @RootId) AND (@SearchId = 0 OR it.Id = @SearchId)

  UNION ALL

  SELECT t.CatLevel + 1 as CatLevel, it.Id, it.ParentId, it.Name, it.RootId
  FROM ItemTree it
  INNER JOIN Tree t ON it.ParentId = t.Id
  WHERE (@RootId = 0 OR it.RootId = @RootId) AND (@SearchId = 0 OR it.Id = @SearchId)

  ----if CatLevel already exists in the table, just use it instead for better performance
  --SELECT it.CatLevel, it.Id, it.ParentId, it.Name FROM ItemTree it 
  --WHERE (@RootId = 0 OR it.RootId = @RootId) AND (@SearchId = 0 OR it.Id = @SearchId)
)
, LeafIds AS --use it to show only leaf categories
(
  SELECT DISTINCT Id FROM ItemTree it WHERE (@RootId = 0 OR it.RootId = @RootId)
  EXCEPT
  SELECT DISTINCT ParentId FROM ItemTree it WHERE (@RootId = 0 OR it.RootId = @RootId)
)
, LeafTree AS --prepare data for pivots
(
  SELECT t.CatLevel, t.Id, t.ParentId, t.Name, Id AS LeafId
  FROM Tree t --WHERE Id IN (SELECT Id FROM LeafIds)

  UNION ALL

  SELECT t.CatLevel, t.Id, t.ParentId, t.Name, lt.LeafId
  FROM Tree t
  INNER JOIN LeafTree lt ON lt.ParentId = t.Id
)
, TreeFinal AS --make pivots for Id and Name over same leafIds
(
  SELECT t.*, pt1.[1] AS ID_1, pt1.[2] AS ID_2, pt1.[3] AS ID_3, pt1.[4] AS ID_4, pt1.[5] AS ID_5
  , pt2.[1] AS Name_1, pt2.[2] AS Name_2, pt2.[3] AS Name_3, pt2.[4] AS Name_4, pt2.[5] AS Name_5
  , ISNULL(pt2.[1], '') + ISNULL(' - ' + pt2.[2], '') + ISNULL(' - ' + pt2.[3], '') + ISNULL(' - ' + pt2.[4], '') + ISNULL(' - ' + pt2.[5], '')  AS Path
  FROM Tree t

  JOIN (
    SELECT LeafId, CatLevel, Id
    FROM LeafTree
  ) source
  PIVOT ( MAX(Id) FOR CatLevel IN ([1], [2], [3], [4], [5]) ) AS pt1 ON pt1.LeafId = t.Id

  JOIN (
    SELECT LeafId, CatLevel, Name
    FROM LeafTree
  ) source
  PIVOT ( MAX(Name) FOR CatLevel IN ([1], [2], [3], [4], [5]) ) AS pt2 ON pt2.LeafId = t.Id
) 
--Use following to update CatLevel in your table, if it was not there before
--UPDATE it SET CatLevel = Tree.CatLevel FROM Tree JOIN dbo.ItemTree it ON it.Id = Tree.Id
SELECT * FROM TreeFinal ORDER BY Path

Here's screenshot of the before and after queries

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜