SQL 递归 CTE 链接链

SQL Recursive CTE Linked Chains

我为 copy/paste 添加了一个简化的数据集。将有一个 table 链式组件,按特定顺序链接。在生产中,这个 table 将包含数十万条链,每个链包含 5-10 个组件。

输入也将是链,我将在数据库中搜索数据库链具有与输入链相同顺序的相同组件的任何实例。例如,如果数据库有链A、B、C、D、E、F、G和I输入链B和A、B、C和D、F、Z;结果会告诉我: 输入链 B 匹配 输入链A、B、C是匹配的 输入链 D、F、Z 不匹配

理想情况下我希望能够输入数万条链来搜索数据库中的数十万条链。

我在 VB.Net 中有一个解决方案,它从数据库中获取与每个输入链的第一个组件匹配的所有链。然后它递归地向下导航数据库链,直到它到达末尾或者它发现数据库链不匹配。它既不优雅也不高效,因为它一次只能查看一个输入链。我可以很容易地在 SQL 中编写一些使用 Cursor 达到相同效果的东西,但同样,效率不高。

我正在尝试找到一个递归 CTE,它允许我一次 运行 数千个输入的整个查询。

这是一个简化的数据集:

DECLARE @Chains TABLE (ID int, Component varchar(15), ParentID int, DisplayOrder int, ChainID int)
DECLARE @SearchChains TABLE (Component varchar(15), DisplayOrder int, GroupID int)

INSERT INTO @Chains (ID, Component, ParentID, DisplayOrder, ChainID) VALUES (1, 'Head bone', 0, 0, 1)
INSERT INTO @Chains (ID, Component, ParentID, DisplayOrder, ChainID) VALUES (2, 'Neck bone', 1, 1, 1)
INSERT INTO @Chains (ID, Component, ParentID, DisplayOrder, ChainID) VALUES (3, 'Shoulder bone', 2, 2, 1)
INSERT INTO @Chains (ID, Component, ParentID, DisplayOrder, ChainID) VALUES (4, 'Back bone', 3, 3, 1)
INSERT INTO @Chains (ID, Component, ParentID, DisplayOrder, ChainID) VALUES (5, 'Hip bone', 4, 4, 1)
INSERT INTO @Chains (ID, Component, ParentID, DisplayOrder, ChainID) VALUES (6, 'Head bone', 0, 0, 2)
INSERT INTO @Chains (ID, Component, ParentID, DisplayOrder, ChainID) VALUES (7, 'Shoulder bone', 6, 1, 2)

INSERT INTO @SearchChains(Component, DisplayOrder, GroupID) VALUES ('Back bone', 0, 1)
INSERT INTO @SearchChains(Component, DisplayOrder, GroupID) VALUES ('Hip bone', 1, 1)
INSERT INTO @SearchChains(Component, DisplayOrder, GroupID) VALUES ('Leg bone', 2, 1)
INSERT INTO @SearchChains(Component, DisplayOrder, GroupID) VALUES ('Head bone', 0, 2)
INSERT INTO @SearchChains(Component, DisplayOrder, GroupID) VALUES ('Neck bone', 1, 2)
;

WITH cteMatching (ID, Component, ParentID, DisplayOrder, ChainID, RecLevel)
AS
(
SELECT  C.ID, C.Component, C.ParentID, C.DisplayOrder, C.ChainID, 1 as RecLevel
FROM @Chains C
WHERE DisplayOrder = 0
UNION ALL
SELECT C.ID, C.Component, C.ParentID, C.DisplayOrder, C.ChainID, cte.RecLevel + 1
FROM @Chains C
INNER JOIN cteMatching cte ON C.ParentID = cte.ID
)

SELECT SC.Component, SC.DisplayOrder, SC.GroupID, cte.ID, cte.Component, cte.ParentID,
    ISNULL(cte.DisplayOrder, 2147483647) as cteDisplayOrder, cte.ChainID, cte.RecLevel,
    ISNULL((SELECT 1 WHERE SC.Component = cte.Component), 0) as IsMatch
FROM @SearchChains SC
LEFT OUTER JOIN cteMatching cte ON SC.Component = cte.Component
ORDER BY SC.GroupID ASC, cte.ChainID ASC, SC.DisplayOrder ASC, cteDisplayOrder ASC

结果是:

    Component       DisplayOrder GroupID     ID          Component       ParentID    cteDisplayOrder ChainID     RecLevel    IsMatch
--------------- ------------ ----------- ----------- --------------- ----------- --------------- ----------- ----------- -----------
Leg bone        2            1           NULL        NULL            NULL        2147483647      NULL        NULL        0
Back bone       0            1           4           Back bone       3           3               1           4           1
Hip bone        1            1           5           Hip bone        4           4               1           5           1
Head bone       0            2           1           Head bone       0           0               1           1           1
Neck bone       1            2           2           Neck bone       1           1               1           2           1
Head bone       0            2           6           Head bone       0           0               2           1           1

这里的问题是 SearchChain GroupID 2(Neck bone-> Head bone)应该显示与数据库 ChainID 1 的匹配(确实如此),它还应该显示 Head 组件在 GroupID 2 和 ChainID 2 之间匹配, Neck 与 ChainID 2 中的 Shoulder 不匹配。但是当我注释掉 ChainID 1 和 SearchChain GroupID 1 时,ChainID 2 和 SearchChain GroupID 2 的结果是正确的。这就是让我难过的地方。

目标是能够同时通过多个数据库链 运行 多个 SearchChain,目前,我的尝试都失败了。有人有什么建议吗?

-E

除此之外,你的链的设计有问题(见下文)我想为你提供这个解决方案:两个链都连接到一个路径,然后通过 LIKE[=15 执行搜索=]

DECLARE @Chains TABLE (ID int, Component varchar(15), ParentID int, DisplayOrder int, ChainID int)
DECLARE @SearchChains TABLE (Component varchar(15), DisplayOrder int, GroupID int)

INSERT INTO @Chains (ID, Component, ParentID, DisplayOrder, ChainID) VALUES (1, 'Head bone', 0, 0, 1)
INSERT INTO @Chains (ID, Component, ParentID, DisplayOrder, ChainID) VALUES (2, 'Neck bone', 1, 1, 1)
INSERT INTO @Chains (ID, Component, ParentID, DisplayOrder, ChainID) VALUES (3, 'Shoulder bone', 2, 2, 1)
INSERT INTO @Chains (ID, Component, ParentID, DisplayOrder, ChainID) VALUES (4, 'Back bone', 3, 3, 1)
INSERT INTO @Chains (ID, Component, ParentID, DisplayOrder, ChainID) VALUES (5, 'Hip bone', 4, 4, 1)
INSERT INTO @Chains (ID, Component, ParentID, DisplayOrder, ChainID) VALUES (6, 'Head bone', 0, 0, 2)
INSERT INTO @Chains (ID, Component, ParentID, DisplayOrder, ChainID) VALUES (7, 'Shoulder bone', 6, 1, 2)

INSERT INTO @SearchChains(Component, DisplayOrder, GroupID) VALUES ('Back bone', 0, 1)
INSERT INTO @SearchChains(Component, DisplayOrder, GroupID) VALUES ('Hip bone', 1, 1)
INSERT INTO @SearchChains(Component, DisplayOrder, GroupID) VALUES ('Leg bone', 2, 1)
INSERT INTO @SearchChains(Component, DisplayOrder, GroupID) VALUES ('Head bone', 0, 2)
INSERT INTO @SearchChains(Component, DisplayOrder, GroupID) VALUES ('Neck bone', 1, 2);

--这是您的查询

WITH recChains AS
(
    SELECT c.ChainID,c.ID,c.ParentID,CAST('|' + c.Component AS NVARCHAR(MAX)) AS ChainAsPath,1 AS Step
    FROM @Chains AS c
    WHERE c.ParentID=0
    UNION ALL
    SELECT nxt.ChainID,nxt.ID,nxt.ParentID,prv.ChainAsPath + '|' + nxt.Component,prv.Step+1
    FROM recChains AS prv
    INNER JOIN @Chains AS nxt ON prv.ChainID=nxt.ChainID AND prv.ID=nxt.ParentID
)
,ChainsPath AS
(
    SELECT ChainID,ChainAsPath
    FROM
    (
        SELECT ChainAsPath
              ,ChainID
              ,ROW_NUMBER() OVER(PARTITION BY ChainID ORDER BY Step DESC) AS StepOrderRev
        FROM recChains
    ) AS tbl
    WHERE StepOrderRev=1
)
,SearchPath AS
(
    SELECT GroupID
          ,(
            SELECT CAST('|' + Component AS NVARCHAR(MAX)) 
            FROM @SearchChains AS p
            WHERE p.GroupID=sc.GroupID
            FOR XML PATH('')
           ) AS SearchPath
    FROM @SearchChains AS sc
    GROUP BY GroupID
)

SELECT *
      ,CASE WHEN ChainAsPath LIKE '%' + SearchPath + '%' THEN 'X' ELSE '' END AS Match 
FROM ChainsPath
CROSS JOIN SearchPath

我必须承认,我没有完全理解您的预期结果,希望这是您需要的:

+---------+-------------------------------------------------------+---------+------------------------------+-------+
| ChainID | ChainAsPath                                           | GroupID | SearchPath                   | Match |
+---------+-------------------------------------------------------+---------+------------------------------+-------+
| 1       | |Head bone|Neck bone|Shoulder bone|Back bone|Hip bone | 1       | |Back bone|Hip bone|Leg bone |       |
+---------+-------------------------------------------------------+---------+------------------------------+-------+
| 2       | |Head bone|Shoulder bone                              | 1       | |Back bone|Hip bone|Leg bone |       |
+---------+-------------------------------------------------------+---------+------------------------------+-------+
| 1       | |Head bone|Neck bone|Shoulder bone|Back bone|Hip bone | 2       | |Head bone|Neck bone         | X     |
+---------+-------------------------------------------------------+---------+------------------------------+-------+
| 2       | |Head bone|Shoulder bone                              | 2       | |Head bone|Neck bone         |       |
+---------+-------------------------------------------------------+---------+------------------------------+-------+

设计

parentID 指向同一组的另一行的模式(self-reference)很笨拙......你选择这个用于树和路径以及 - 通常 - 图形. 它必须无间隙 普通链更容易配置为带有排名的列表。您可以将一列用于显示等级,将另一列用于技术链。就像你用你的搜索链做的那样...... ORDER BY 比递归 CTE 更快更干净(并且允许间隙),递归 CTE 是 隐藏的 RBAR 因此相当慢...

设计 2

我建议您不要使用单词本身作为路径,而是为您的组件使用目录 table 并根据它们的 ID 构建路径。这更清洁、更易于维护且速度更快。

这是我使用新结构的最终解决方案:

DECLARE @Chains TABLE (ID int IDENTITY(1,1), ComponentID int, DisplayOrder int, ChainID int)
DECLARE @SearchChains TABLE (ComponentID int, DisplayOrder int, GroupID int)
DECLARE @Components TABLE (ID int IDENTITY(10,1), Component varchar(15))

INSERT INTO @Components (Component) VALUES ('Head bone')
INSERT INTO @Components (Component) VALUES ('Neck bone')
INSERT INTO @Components (Component) VALUES ('Shoulder bone')
INSERT INTO @Components (Component) VALUES ('Back bone')
INSERT INTO @Components (Component) VALUES ('Hip bone')
INSERT INTO @Components (Component) VALUES ('Thigh bone')
INSERT INTO @Components (Component) VALUES ('Knee bone')
INSERT INTO @Components (Component) VALUES ('Shin bone')
INSERT INTO @Components (Component) VALUES ('Ankle bone')
INSERT INTO @Components (Component) VALUES ('Heel bone')
INSERT INTO @Components (Component) VALUES ('Foot bone')
INSERT INTO @Components (Component) VALUES ('Toe bone')
INSERT INTO @Components (Component) VALUES ('Leg bone')

INSERT INTO @Chains (ComponentID, DisplayOrder, ChainID) VALUES (10, 0, 1)
INSERT INTO @Chains (ComponentID, DisplayOrder, ChainID) VALUES (11, 1, 1)
INSERT INTO @Chains (ComponentID, DisplayOrder, ChainID) VALUES (12, 2, 1)
INSERT INTO @Chains (ComponentID, DisplayOrder, ChainID) VALUES (13, 3, 1)
INSERT INTO @Chains (ComponentID, DisplayOrder, ChainID) VALUES (14, 4, 1)
INSERT INTO @Chains (ComponentID, DisplayOrder, ChainID) VALUES (10, 0, 2)
INSERT INTO @Chains (ComponentID, DisplayOrder, ChainID) VALUES (12, 1, 2)

INSERT INTO @SearchChains(ComponentID, DisplayOrder, GroupID) VALUES (13, 0, 1)
INSERT INTO @SearchChains(ComponentID, DisplayOrder, GroupID) VALUES (14, 1, 1)
INSERT INTO @SearchChains(ComponentID, DisplayOrder, GroupID) VALUES (22, 2, 1)
INSERT INTO @SearchChains(ComponentID, DisplayOrder, GroupID) VALUES (10, 0, 2)
INSERT INTO @SearchChains(ComponentID, DisplayOrder, GroupID) VALUES (11, 1, 2)
INSERT INTO @SearchChains(ComponentID, DisplayOrder, GroupID) VALUES (10, 0, 3)
;

WITH ChainPath AS
(
    SELECT ChainID
        ,(
            SELECT '|' + CAST(co.ID AS NVARCHAR(MAX))
            FROM @Chains AS ch
                INNER JOIN @Components co ON ch.ComponentID = co.ID
            WHERE ch.ChainID = cc.ChainID
            FOR XML PATH('')
        ) as ChainPath
    FROM @Chains cc
    GROUP BY ChainID
)
,SearchPath AS
(
    SELECT GroupID
          ,(
            SELECT '|' + CAST(c.ID AS NVARCHAR(MAX)) 
            FROM @SearchChains AS p
                INNER JOIN @Components c ON p.ComponentID = c.ID
            WHERE p.GroupID=sc.GroupID
            FOR XML PATH('')
           ) AS SearchPath
    FROM @SearchChains AS sc
    GROUP BY GroupID
)


SELECT SP.*, CP.*
FROM ChainPath CP
INNER JOIN SearchPath SP ON CP.ChainPath + '|' LIKE '%' + SP.SearchPath + '|' + '%'