【问题标题】:SQL Crawl Hierarchy - Get All RelationshipsSQL Crawl Hierarchy - 获取所有关系
【发布时间】:2014-03-28 04:33:33
【问题描述】:

我认为这将更容易解释为一个故事而不是示例代码。

  • 我有一张父母和孩子的表格。
  • 父级可能是另一个父级的子级,依此类推。
  • 有顶级父母。
  • 兄弟姐妹是可能的(同一父母的两个或更多孩子)

我正在尝试在 SQL 中抓取此表并获取所有关系。

以下是关系如何运作的项目符号示例。

o 6220 (Top Level Parent)
    - 6219 (Child1 of 6220)
    - 6221 (Child2 of 6220)
       * 6222 (Child1 of 6221/GrandChild1 of 6220)    
       * 6223 (Child2 of 6221/GrandChild2 of 6220)
           x 6224 (Child1 of 6223/GrandChild1 of 6221/GreatGrandChild1 of 6220)
o 6225 (Top Level Parent)

这是表格形式的外观:

Parent, Child
NULL,    6220
6220,    6221
6220,    6219
6221,    6222
6221,    6223
6223,    6224
NULL,    6225

我正在寻找的(最佳)结果是:

Parent, Child, Level
6220,    6220,   0
6220,    6221,   1
6220,    6219,   1
6220,    6222,   2
6220,    6223,   2
6220,    6224,   4
6221,    6222,   1
6221,    6223,   1
6221,    6224,   2
6223,    6224,   1
6225,    6225,   0

我尝试了递归 CTE 和循环,我可以接近,但不完全。希望接近这个或类似的东西是有意义的。我想将其放入一个表中,我可以查询该表以查找父母或孩子的所有父母、祖父母、子女、孙子女等。兄弟姐妹关系并不重要。

感谢收看。如果我能澄清一下,请告诉我。

这是我一直在尝试的方法:

    DECLARE @InsertedCount INT
DECLARE @RelationshipLevel TINYINT 
DECLARE @AgyCount INT 
DECLARE @LoopCount INT = 1
DECLARE @AgencyId INT

SELECT @AgyCount = COUNT(AgencyId) FROM dbo.Agency AS A


/* ==============================================================================
    Get All AgencyIDs into a table with an incremented column
============================================================================== */

IF (OBJECT_ID('tempdb.dbo.#tmpAllAgy') > 0)
    DROP TABLE #tmpAllAgy

CREATE TABLE #tmpAllAgy (tmpAllAgyId INT IDENTITY(1,1) PRIMARY KEY, AgencyId INT)

INSERT INTO #tmpAllAgy (AgencyId)
SELECT AgencyId FROM dbo.Agency ORDER BY AgencyId


/* ==============================================================================
    Temp table for the results of the matrix to compare to physical table
============================================================================== */
IF (OBJECT_ID('tempdb.dbo.#ChildAgencies') > 0)
        DROP TABLE #ChildAgencies

CREATE TABLE #ChildAgencies (LoopOrder INT, ParentAgencyId INT, ChildAgencyId INT,          RelationshipLevel TINYINT)

/* ==============================================================================
    Outer loop to get the next Agency Id from Temp Table
============================================================================== */

WHILE @LoopCount <= @AgyCount

BEGIN

    SET @RelationshipLevel = 0  

    SELECT @AgencyId = AgencyId FROM #tmpAllAgy WHERE tmpAllAgyId = @LoopCount  

    INSERT INTO #ChildAgencies(LoopOrder, ParentAgencyId, ChildAgencyId, RelationshipLevel)
    SELECT @LoopCount, NULL, AgencyId, @RelationshipLevel   
    FROM dbo.Agency
    WHERE AgencyId = @AgencyID

    SET @InsertedCount = 1--@@ROWCOUNT


/* ==============================================================================
    Inner loop to create the hierarchy for each AgencyId
============================================================================== */

WHILE @InsertedCount > 0
BEGIN

    SET @InsertedCount = NULL
    SET @RelationshipLevel = @RelationshipLevel + 1      

    INSERT INTO #ChildAgencies (LoopOrder, ParentAgencyId, ChildAgencyId, RelationshipLevel)
    SELECT @LoopCount, @AgencyId, AgencyId, @RelationshipLevel  
    FROM dbo.Agency
    WHERE AgencyId NOT IN (SELECT ChildAgencyId FROM #ChildAgencies)
        AND ParentAgencyId IN (SELECT ChildAgencyId FROM #ChildAgencies)
        AND StatusCode <> 109 /*QA-Deleted*/

    SET @InsertedCount = @@ROWCOUNT

END

    SET @LoopCount = @LoopCount + 1
END      

递归 CTE 似乎只带来直接的父母和孩子,同时任意增加计数。 上面的循环代码很接近,但似乎以奇怪的顺序做事。

【问题讨论】:

  • 示例代码中的“LoopOrder”仅用于故障排除。我正在验证循环的循环顺序。作为一个领域,它是无关紧要的。

标签: sql loops recursion hierarchy recursive-query


【解决方案1】:

这在开始时有点难以理解,但您需要做的只是多尝试。 我从 MSDN 得到了一个例子

http://technet.microsoft.com/en-us/library/ms186243(v=sql.105).aspx

并设法在 SQLFiddle 上工作,只是去掉了一些“胖子”

希望对你有帮助

http://sqlfiddle.com/#!6/c1438/17

【讨论】:

  • 注意:最好获取您在此处使用的基本查询,以防万一小提琴丢失......当然,这仅适用于 SQL Server。
  • 注意:较小的修改将是 COALESCE(e.ManagerID, 1) 因此对于第一级,因为 ManagerId 为空,所以值将是 1,与employeeId 相同。
  • 当我转到 SQL Fiddle 链接时,它似乎将我带到了“创建新小提琴”页面。
  • 当我使用 SQLFiddle 中的递归 CTE 时,它只返回直接的子级和父级,但会增加级别。例如,它给了我 6223,6224,3 而不是 6220,6224,3。
【解决方案2】:

从基本情况开始:Parent 值为NULL 的人总是拥有Level0。这意味着您可以更新 Parent is NULL 并设置 Level = 0 的每一行。

下一步:查找父项的 Level 值为 not NULL 的行。将它们更新为具有 Level 值 = parent.Level + 1

重复上述步骤,直到表格的每一行都有一个Level 值。

【讨论】:

    【解决方案3】:

    我相信您的提议无法使用 SQL 查询。从理论上讲,你们的关系可能有周期。您基本上是在 SQL 表中实现图形,因为兄弟姐妹是可能的。假设6221是6222的父级,6222是6223的父级,6223是6221的父级?然后你有一个循环。

    如果保证不存在这样的结构,那么在最坏的情况下你仍然有一个完全连接的图需要处理。有最大的“年龄差”吗?如果是这样,那么您可以只进行有限数量的连接来执行此操作,使用外部连接来确保仍然包含那些没有关系的连接。对于关系的每个“级别”,您必须执行三个连接:一个用于父级,一个用于兄弟姐妹,一个用于子级。您还需要确保不会返回到原始节点。

    简而言之,假设没有循环,您将在包含树木森林的表中实现树遍历。我不知道你怎么能在 SQL 中做到这一点。你可以用编程语言来做,或者作为一个使用循环的存储过程,但前提是没有循环,或者你以某种方式实现循环检测。

    祝你好运。

    编辑:此外,除非您获得树根(顶级节点),否则树中的任何节点都可以被视为根。但是,如果基础图是有向图(父子关系是明确的),那么根是没有父节点的节点。对于具有定义的根和有限深度的 N-Ary 树,您可以实现任何标准的图搜索算法,其遍历成本为 1,直到所有节点都有与其相关的成本。有广度优先搜索、深度优先搜索、最佳优先搜索、A*等。

    编辑: 试试这样的:

    create procedure update_node_depths
    as
    begin
        set nocount on
    
        update nodes
        set depth = null
    
        update nodes
        set depth = 0
        where parent_id is null
    
        while exists (select node_id from nodes where depth is null)
        begin
            update nodes
            set depth = (select n2.depth+1
                         from nodes n2
                         where n2.node_id = nodes.parent_id)
            where parent_id in (select node_id
                                from nodes n3
                                where n3.depth is not null)
         end
    
         select * 
         from nodes
    end
    go
    

    然后您可以执行类似的操作来获取您的表格,并在您执行此操作时自动更新深度。

    execute update_node_depths
    go
    

    【讨论】:

    • 兄弟姐妹关系无关紧要,只要兄弟姐妹都出现在各自的父母和祖父母之下。它就像一棵家谱,但我感兴趣的关系只是直上直下。
    • 在这种情况下,您正在遍历 N-Ary 树,并寻找深度。您仍然需要为 n 个级别运行查询和/或创建外连接,其中 n 是最大深度。您可以通过设置每个节点的级别以编程方式(在 SQL 之外或使用存储过程)来执行此操作。首先,设置顶级父母的级别。然后将所有节点的级别设置为 1 + 父级具有级别的所有节点的级别,当所有节点都分配了级别时结束,并返回结果集的节点、父级和级别的元组。
    【解决方案4】:

    所以,我终于有工作要做了。这几乎和我一直在做的一样。我没有使用循环中的循环,而是采用了内部循环并将其变成了一个函数。然后我创建了一个循环的 SP,一次为函数提​​供一个 ID,并将结果附加到一个临时表中。

    这里基本上是内部循环:

    DECLARE @AgencyId INT = 6220
    DECLARE @InsertedCount INT
    DECLARE @LevelCount INT = 0
    
    
    CREATE TABLE #ChildAgencies(AgencyId INT, LevelCount INT)
    
    
    INSERT INTO #ChildAgencies(AgencyId, LevelCount)
    SELECT AgencyId, @LevelCount    
    FROM dbo.Agency
    WHERE AgencyId = @AgencyId
    
    SET @InsertedCount = @@ROWCOUNT
    
    WHILE @InsertedCount > 0
    BEGIN
    
        SET @InsertedCount = NULL
        SET @LevelCount = @LevelCount + 1      
    
        INSERT INTO #ChildAgencies(AgencyId, LevelCount)
        SELECT AgencyId, @LevelCount    
        FROM dbo.Agency
        WHERE AgencyId NOT IN (SELECT AgencyId FROM #ChildAgencies)
            AND ParentAgencyId IN (SELECT AgencyId FROM #ChildAgencies)
            AND StatusCode <> 109 /*QA-Deleted*/
    
        SET @InsertedCount = @@ROWCOUNT
    
    END
    

    产生:

    AgencyId    LevelCount
    6220              0
    6219                1
    6221                1
    6222                2
    6223                2
    6224                3
    

    显然,我没有在最终产品中默认使用 @AgencyId 变量。我在存储过程中设置了@AgencyId 并将其传递给它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-15
      • 2021-12-27
      • 1970-01-01
      • 2021-02-09
      • 2023-01-22
      • 2019-08-12
      相关资源
      最近更新 更多