SQL 递归 CTE 链接链

标签 sql sql-server recursion common-table-expression

我包含了一个用于复制/粘贴的简化数据集。将有一个链式组件表,按特定顺序链接。在生产中,该表将包含数十万条链,每个链包含 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

最佳答案

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

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 + '|' + '%'

关于SQL 递归 CTE 链接链,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39154716/

相关文章:

SQL Server 从现有日期列中确定日期

mysql - SQL 过程 - 多值

sql-server - 如何让我的 webApp 在不同的 DBMS 之间平滑切换(DB Independency)?

bash - 递归计算特定文件BASH

javascript - 在嵌套对象数组中查找父对象。如何?

sql - 如何强制 SQLAlchemy 包含重复的列?

php - 列表问题,GROUP mysql

sql - 如何计算一段时间内每月的记录数

sql-server - 删除游标SQL语句

Python:对象的递归创建