4

我有下表(ObjectStates)并且想要获取根以及根的第一个孩子:

ID    Title    ParentID
1     Draft    null
2     Green    null
3     Red      null
4     Foo      1
5     Bar      4
6     Some1    1
7     Some2    6
8     XYZ      2
9     Some3    7

我想要以下输出:

GetState(5)
-- returns root: 1, first-child: 4
GetState(6)
-- returns root: 1, first-child: 6
GetState(7)
-- returns root: 1, first-child: 6
GetState(9)
-- returns root: 1, first-child: 6
GetState(8)
-- returns root: 2, first-child: 8

所以无论我查询的层次结构有多深——我总是想要根元素以及第一个子元素。如果你考虑这棵树,我总是想要红色和蓝色元素,不管我在树里有多深。

在此处输入图像描述

我可以像这样获得“根”状态:

WITH CTEHierarchy
AS (
    SELECT 
    ID
        ,0 AS LEVEL
        ,ID AS root

    FROM ObjectStates
    WHERE  ParentID IS NULL

    UNION ALL

    SELECT 
    ObjectStates.ID
        ,LEVEL + 1 AS LEVEL
        ,[root]

    FROM ObjectStates
    INNER JOIN CTEHierarchy uh ON uh.id = ObjectStates.ParentID
    )    
    SELECT [root]
    FROM CTEHierarchy
    WHERE ID = @ObjectStateID 

这给了我我想要的根结果:

GetState(5)
-- returns root: 1
GetState(9)
-- returns root: 1
GetState(2)
-- returns root: 2

我怎样才能从那里穿越?那么从根获取树中的下一个孩子?或者反过来 - 获取根以及第一级。递归让我头疼。

4

2 回答 2

2

我认为你需要先走上层级,然后获得前两个级别:

WITH cteHierarchy As
(
   SELECT
      ID,
      ParentID,
      0 As Level
   FROM
      ObjectStates
   WHERE
      ID = @ObjectStateID

   UNION ALL

   SELECT
      OS.ID,
      OS.ParentID,
      H.Level + 1
   FROM
      cteHierarchy As H
      INNER JOIN ObjectStates As OS
      ON H.ParentID = OS.ID
),
cteReveresedHierarchy As
(
   SELECT
      ID,
      ROW_NUMBER() OVER (ORDER BY Level DESC) As RowNumber
   FROM
      cteHierarchy
)
SELECT
   ID
FROM
   cteReveresedHierarchy
WHERE
   RowNumber In (1, 2)
;

编辑
要在一行中获取两个项目:

如果您可以保证您永远不会从根目录开始,您可以将过滤器更改为WHERE RowNumber = 2,并包含该ParentID列。但是,如果您从根开始,则只有一行,因此该查询将不起作用。

要允许查询从根开始,您需要取第 2 行(如果存在),否则取第 1 行:

WITH cteHierarchy As
(
   SELECT
      ID,
      ParentID,
      0 As Level
   FROM
      ObjectStates
   WHERE
      ID = @ObjectStateID

   UNION ALL

   SELECT
      OS.ID,
      OS.ParentID,
      H.Level + 1
   FROM
      cteHierarchy As H
      INNER JOIN ObjectStates As OS
      ON H.ParentID = OS.ID
),
cteReveresedHierarchy As
(
   SELECT
      ID,
      ParentID,
      ROW_NUMBER() OVER (ORDER BY Level DESC) As RowNumber
   FROM
      cteHierarchy
)
SELECT TOP 1
   ParentID As [root]
   ID As [FirstChild]
FROM
   cteReveresedHierarchy
WHERE
   RowNumber In (1, 2)
ORDER BY
   RowNumber DESC
;
于 2012-11-27T14:36:47.470 回答
2

我今天正在玩这个查询来遍历主键外键关系以跟踪整个路径,这个问题似乎很相似。所以只需粘贴相同的代码。您可以直接运行此代码并检查这是否是您所需要的。

该查询在 CTE 中增加了两列,即 Hops 和 Path,其中 Hops 是元素的级别,Path 是从头到尾遍历的节点。

WITH cte
AS
(
    SELECT 
        fk.create_date
        , fk.modify_date
        , fkc.constraint_object_id AS ConstraintId
        , OBJECT_NAME(fkc.constraint_object_id) AS ConstraintName
        , OBJECT_NAME(fkc.referenced_object_id) AS PrimaryKeyTableName
        , rc.name AS PrimaryKeyColumnName
        , OBJECT_NAME(fk.parent_object_id) AS ForeignKeyTableName
        , lc.name AS ForeignKeyColumnName
    FROM sys.foreign_key_columns fkc
    INNER JOIN sys.columns rc 
        ON  rc.OBJECT_ID = fkc.referenced_object_id 
        AND fkc.referenced_column_id = rc.column_id
    INNER JOIN sys.foreign_keys fk 
        ON  fk.OBJECT_ID = fkc.constraint_object_id
    INNER JOIN sys.columns lc 
        ON  lc.OBJECT_ID = fk.parent_object_id
        AND fkc.parent_column_id = lc.column_id
)
, cte2(create_date, modify_date, ConstraintName
        , PrimaryKeyTableName, PrimaryKeyColumnName
        , ForeignKeyTableName, ForeignKeyColumnName
        , Hops, path ) AS 
    (
        SELECT
            create_date, modify_date, ConstraintName
            , PrimaryKeyTableName, PrimaryKeyColumnName
            , ForeignKeyTableName, ForeignKeyColumnName 
            , 1 , CAST(QUOTENAME(PrimaryKeyTableName + '.' + PrimaryKeyColumnName) AS VARCHAR(4000))
        FROM cte
    UNION ALL
        SELECT 
            cte.create_date, cte.modify_date, cte.ConstraintName
            , cte.PrimaryKeyTableName, cte.PrimaryKeyColumnName
            , cte.ForeignKeyTableName, cte.ForeignKeyColumnName
            , cte2.Hops +1, CAST(cte2.path + '-> ' +QUOTENAME(cte.PrimaryKeyTableName+ '.' + cte.PrimaryKeyColumnName) AS VARCHAR(4000))
        FROM cte2 INNER JOIN cte ON cte2.ForeignKeyTableName = cte.PrimaryKeyTableName
        AND cte2.ForeignKeyColumnName != cte.PrimaryKeyColumnName
    )
SELECT 
ConstraintName
        , PrimaryKeyTableName, PrimaryKeyColumnName
        , ForeignKeyTableName, ForeignKeyColumnName
        , Hops, path + '-> ' + QUOTENAME(ForeignKeyTableName + '.' + ForeignKeyColumnName) AS Path
FROM cte2

仅当外键约束物理存在于数据库中时,此查询才有效。

于 2012-11-27T14:38:50.953 回答