递归计算自引用分层表的每个项目的路径

时间:2015-04-03 08:06:36

标签: sql-server recursion hierarchical-data

我有一个这样的表(SQL Server 2008):

[ATTACHMENTS].[ID]
[ATTACHMENTS].[Name]
[ATTACHMENTS].[DocumentID],
[ATTACHMENTS].[ParentAttachmentID]

如果[ParentAttachmentID]NULL0 - 则记录最高。否则 - 记录是一个孩子。每个孩子都可以(并且不能成为)其他一些子元素的父母。

我需要计算"路径"对于表的每个记录。路径是:

ParentAttachment.Path + ' > ' + Attachment.Path(ParentAttachment.Path是递归的)

我尝试这样的事情:

WITH attachments AS (
 SELECT *,
     [ATTACHMENTS].[Name] AS Path
     FROM [ATTACHMENTS]
     WHERE  [ATTACHMENTS].[ParentAttachmentID] IS NULL
            OR [ATTACHMENTS].[ParentAttachmentID] = 0
 UNION ALL
 SELECT a.*,
     c.Path + ' > ' + a.[Name]
     FROM [ATTACHMENTS] a
         INNER JOIN attachments c
             ON a.[ParentAttachmentID] = c.[ID]
)

但它没有正常工作(由于我认为重复,因此某些元素的路径无效)。我犯了哪个错误?请帮我解决这个问题。

UPD 2 :包含来自表格[ATTACHMENTS]的数据的CSV - http://pastebin.com/WMd6HJ7j 带有递归查询结果的CSV:http://pastebin.com/7pqs0dx1

感谢名单!

3 个答案:

答案 0 :(得分:0)

你得到的错误是什么?

我认为您没有明确指定数据类型。 Path的大小是锚点起点的大小:ATTACHMENTS.Name。哪个可能是varchar(100)左右。

然后当你继续递增你的路径时,它可能会用完空间。 CTE本身看起来很好。我使用显式尺寸进行操作,然后它工作正常。

CREATE TABLE #Attachments
    (
      Id INT PRIMARY KEY
             NOT NULL ,
      ParentId INT NULL ,
      Name VARCHAR(50) NOT NULL
    );

-- some sample data:
-------------------------------------

-- 100 top level attachments (lvl0)
INSERT  INTO #Attachments
        ( Id ,
          ParentId ,
          Name
        )
        SELECT  A.Rnum ,
                NULL ,
                'A' + CAST(rnum AS VARCHAR(10))
        FROM    ( SELECT TOP 100
                            ROW_NUMBER() OVER ( ORDER BY ( SELECT
                                                              1
                                                         ) ) AS Rnum
                  FROM      sys.messages
                ) A;

-- attachments linked to lvl0
INSERT  INTO #Attachments
        ( Id ,
          ParentId ,
          Name         
        )
        SELECT  1000 + A.Rnum ,
                A.Rnum ,
                'A' + CAST(( rnum + 1000 ) AS VARCHAR(10))
        FROM    ( SELECT TOP 1000
                            ROW_NUMBER() OVER ( ORDER BY ( SELECT
                                                              1
                                                         ) ) AS Rnum
                  FROM      sys.messages
                ) A;

-- attachments linked to lvl1
INSERT  INTO #Attachments
        ( Id ,
          ParentId ,
          Name         
        )
        SELECT  10000 + A.Rnum ,
                1000 + A.Rnum ,
                'A' + CAST(( rnum + 10000 ) AS VARCHAR(10))
        FROM    ( SELECT TOP 1000
                            ROW_NUMBER() OVER ( ORDER BY ( SELECT
                                                              1
                                                         ) ) AS Rnum
                  FROM      sys.messages
                ) A;

-- Query:
-------------------------------------

WITH    Att ( Id, Name, ParentId, Lvl, NamePath, IdPath )
          AS ( SELECT   P.Id ,
                        P.Name ,
                        P.ParentId ,
                        CAST(0 AS INT) AS Lvl ,
                        CAST(P.[Name] AS VARCHAR(1000)) AS NamePath ,
                        CAST(P.[Id] AS VARCHAR(1000)) AS IdPath
               FROM     #ATTACHMENTS P
               WHERE    P.[ParentId] IS NULL
                        OR P.[ParentId] = 0
               UNION ALL
               SELECT   A.Id ,
                        A.Name ,
                        A.ParentId ,
                        P.Lvl + 1 ,
                        CAST(P.NamePath + ' > ' + A.Name AS VARCHAR(1000)) ,
                        CAST(P.IdPath + '\' + CAST(A.Id AS VARCHAR(10)) AS VARCHAR(1000))
               FROM     #ATTACHMENTS A
                        INNER JOIN Att P ON A.ParentId = P.Id
             )
    SELECT  *
    FROM    Att;

DROP TABLE #Attachments;

答案 1 :(得分:0)

你的陈述似乎对我不对:

DECLARE @t TABLE
    (
      Code NVARCHAR(MAX) ,
      ParentCode NVARCHAR(MAX)
    )

INSERT  INTO @t
VALUES  ( '1', NULL ),
        ( '2', NULL ),
        ( '1.1', '1' ),
        ( '1.1.1', '1.1' ),
        ( '1.1.2', '1.1' ),
        ( '1.2', '1' ),
        ( '1.2.1', '1.2' ),
        ( '2.1', '2' ),
        ( '2.2', '2' );
WITH    cte
          AS ( SELECT   Code ,
                        ParentCode ,
                        ISNULL(Code, '') AS Path
               FROM     @t
               WHERE    ParentCode IS NULL
               UNION ALL
               SELECT   t.Code ,
                        t.ParentCode ,
                        Path + ISNULL(' > ' + t.Code, '') AS Path
               FROM     @t t
                        JOIN cte c ON c.Code = t.ParentCode
             )
    SELECT  * FROM    cte

输出:

Code    ParentCode  Path
1       NULL        1
2       NULL        2
2.1     2           2 > 2.1
2.2     2           2 > 2.2
1.1     1           1 > 1.1
1.2     1           1 > 1.2
1.2.1   1.2         1 > 1.2 > 1.2.1
1.1.1   1.1         1 > 1.1 > 1.1.1
1.1.2   1.1         1 > 1.1 > 1.1.2

答案 2 :(得分:0)

您必须在WITH之后为表和名称设置不同的名称,并在连接的第二部分使用第二个名称。请在下面的代码中考虑attachments1

WITH attachments1 AS (
  -- anchor
 SELECT *,
     [ATTACHMENTS].[Name] AS [Path]
     FROM ATTACHMENTS
     WHERE  [ATTACHMENTS].[ParentAttachmentID] IS NULL
            OR [ATTACHMENTS].ParentAttachmentID = 0
 UNION ALL
 --recursive member
 SELECT a.*,
        cast((c.[Path] + N' > ' + a.[Name]) as nvarchar(500)) as [Path]
     FROM ATTACHMENTS a
         INNER JOIN attachments1 c
             ON a.ParentAttachmentID = c.[ID]
)

SELECT * FROM attachments1

您可以从递归成员获取Path