SQL爬网层次结构 - 获取所有关系

时间:2014-02-25 18:24:26

标签: sql loops recursion hierarchy recursive-query

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

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

我正在尝试在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似乎只会带来直接的父母和孩子,同时随意增加计数。 上面的循环代码很接近,但似乎以奇怪的顺序做事。

4 个答案:

答案 0 :(得分:1)

开始时有点难以理解,但你需要的只是尝试更多。 我从MSDN获得了一个例子

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

并设法使用广告SQLFiddle,只删除了一些“胖”

希望它能帮助你

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

答案 1 :(得分:1)

我相信你提出的建议是不可能使用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

答案 2 :(得分:0)

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

下一步:查找父母的Level值为not NULL的行。将其更新为Level值= parent.Level + 1

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

答案 3 :(得分:0)

所以,我终于有了一些工作要做。这和我一直在做的几乎一样。而不是循环中的循环,我采用了内循环并使其成为一个函数。然后我创建了一个循环的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并将其传递给它。