sql adjacency list CTE:filter / limit to only path

时间:2012-05-03 19:34:08

标签: sql tree adjacency-list

我已经构建了一个函数/查询,它将[paths]作为邻接列表返回,如下所示,它运行良好, 但我想弄清楚如何将结果限制为仅完整路径,而不是每次深度(n)的迭代。

例如,在下面显示的以下结果中,只有这些是有效的,我想要返回或过滤的完整路径:

S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Close>B-GARNER>VB1>VB1_Close
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Close>B-GARNER>VB1>VB1_Open
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Close>B-GARNER>VB1>VB2
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Open>V30A2>V30A2_Close>S2-UG
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Open>V30A2>V30A2_Open>S1-UG

TEST

通过DPath从fn_Get_SubTreePaths('S11',11)中选择*

结果

S11>S11-LG
S11>S11-LG>V613
S11>S11-LG>V613>V613_Close
S11>S11-LG>V613>V613_Close>B31A
S11>S11-LG>V613>V613_Close>B31A>B30
S11>S11-LG>V613>V613_Close>B31A>B30>B30A
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Close
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Close>B-GARNER
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Close>B-GARNER>VB1
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Close>B-GARNER>VB1>VB1_Close
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Close>B-GARNER>VB1>VB1_Open
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Close>B-GARNER>VB1>VB2
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Open
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Open>V30A2
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Open>V30A2>V30A2_Close
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Open>V30A2>V30A2_Close>S2-UG
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Open>V30A2>V30A2_Open
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Open>V30A2>V30A2_Open>S1-UG

换句话说 - 我怎样才能确定那些包含遍历/深度中最底部项目的路径?

任何帮助将不胜感激!我正在阅读Joe Celko在SQL for Treesies中的树和层次结构 - 但到目前为止还没有把我的大脑包裹起来如何正确地限制结果......

这是代码;

create function [dbo].[fn_Get_SubTreePaths]( @Start varchar(20), @MaxLevels int)
returns table 
as
RETURN(
    WITH CTE AS (
    SELECT 
        c.DeviceID AS Start,
        CAST(d.DeviceName AS VARCHAR(2000)) AS Path, 
        c.ConnectedDeviceID, 
        1 AS Level
    FROM Connections c
    INNER JOIN Devices d ON d.ID=c.DeviceID
                        AND d.DeviceName=@Start
    UNION ALL
    SELECT 
        r.Start, 
        CAST(r.Path + '>'+ d.DeviceName AS VARCHAR(2000)), 
        --CAST(r.Path + ' -> ' + d.DeviceName + '-' + cast(r.SLevel as varchar) AS VARCHAR),
        c.ConnectedDeviceID, 
        Level = r.Level + 1
    FROM Connections c
    INNER JOIN Devices d ON d.ID=c.DeviceID
                        and d.DeviceName<>@Start
    INNER JOIN CTE r ON c.DeviceID=r.ConnectedDeviceID
      AND r.Level < @MaxLevels
    )

    SELECT 
      DISTINCT a.DPath
      FROM (
        SELECT c.Path + '>' + ISNULL(d.DeviceName,'?')  AS DPath
        FROM CTE c
        INNER JOIN Devices d ON d.ID=c.ConnectedDeviceID
        ) a    
    )                                  




CREATE TABLE [dbo].[Connections](
    [ID] [int] NOT NULL,
    [DeviceID] [int] NULL,
    [ConnectedDeviceID] [int] NULL
 CONSTRAINT [PK_Connections] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]



CREATE TABLE [dbo].[Devices](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [DeviceName] [varchar](50) NULL
 CONSTRAINT [PK_Devices] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

CTE使用的观点:

ALTER view [dbo].[vw_DeviceConnections] as
select 
  c.ID as ID,
  c.DeviceID as DeviceID,
  d.DeviceName,
  d.DeviceType,
  c.ConnectedDeviceID as ConnDeviceID,
  cd.DeviceName as ConnDeviceName,
  cd.DeviceType as ConnDeviceType
from Devices d  
inner join Connections c on
   d.id=c.DeviceID
inner join Devices cd on
   cd.id=c.ConnectedDeviceID  

2 个答案:

答案 0 :(得分:2)

查看有关SQLFiddle here的答案。

这是我的策略:让你的递归CTE返回当前节点以及它之前的节点。然后,您可以将其连接到自身并将其限制为最后一个节点未用作另一个节点的父节点的行。

以下是您的功能的外观(用参数替换'S11'和15):

WITH CTE AS (
  SELECT
    d.ID AS Start,
    CAST(d.DeviceName AS VARCHAR(2000)) AS Path, 
    d.ID AS node,
    NULL AS parent,
    1 AS Level
  FROM Devices d
  WHERE d.DeviceName= 'S11'
UNION ALL
  SELECT 
    r.Start, 
    CAST(r.Path + '>'+ d.DeviceName AS VARCHAR(2000)), 
    d.ID as node,
    r.node as parent,
    r.Level + 1 as Level
  FROM CTE r
    INNER JOIN Connections c ON c.DeviceID = r.node
    INNER JOIN Devices d ON d.ID = c.ConnectedDeviceID
  WHERE r.Level < 15
),
Trimmed as (
  SELECT L.*
  FROM CTE L
    LEFT JOIN CTE R on L.node = R.parent
  WHERE R.parent IS NULL
)   
SELECT * FROM Trimmed

如果您想澄清它是如何运作的,请告诉我,我可以尝试更好地解释它。

答案 1 :(得分:0)

尝试下一个解决方案

select 
    *
from 
    fn_Get_SubTreePaths('S11', 11) f1
where
    (
        select
            count(*)
        from
            fn_Get_SubTreePaths('S11', 11) f2
        where
            charindex(f1.dPath, f2.dPath) > 0
    ) = 1

order by DPath