从sql中具有多个公共分组字段的一组数据生成两级层次结构

从sql中具有多个公共分组字段的一组数据生成两级层次结构,sql,sql-server,hierarchical-grouping,Sql,Sql Server,Hierarchical Grouping,我的数据库是MS SQL 2008 我基本上是将来自两个或多个数据库的一些数据集合并在一起,最终得到可能由两个字段关联的一组数据的一个所有者 桌子 数据是无序的,父项不重要,只要每个组有一个。 父项的子项是一组记录,它们通过具有相同的名称或相同的代码或两者而相关。每条记录在结果中只能出现一次,即不能属于多个组 以下是一种可能的结果层次结构,不必以这种方式表示: ID Name Code ParentID 1 Ben 1 NULL 2

我的数据库是MS SQL 2008

我基本上是将来自两个或多个数据库的一些数据集合并在一起,最终得到可能由两个字段关联的一组数据的一个所有者

桌子

数据是无序的,父项不重要,只要每个组有一个。 父项的子项是一组记录,它们通过具有相同的名称或相同的代码或两者而相关。每条记录在结果中只能出现一次,即不能属于多个组

以下是一种可能的结果层次结构,不必以这种方式表示:

ID     Name     Code     ParentID
1      Ben      1        NULL
2      Ben      1        1
3      Frank    1        1
4      Frank    2        1
5      Mark     2        1
6      Mary     3        NULL
7      Chuck    3        6
记录ID{1}是拾取的组1的父级,因为它是公共集的第一个

{2} 共享相同的名称,因此其包含的内容也可以包含,因为相同的代码

{3} 共享相同的代码,因此其包含

{4} 与{3}共享相同的名称,因此包含它

{5} 与{4}共享相同的代码,因此包含它

{6} 和{7}共享相同的代码,因此形成一个新组

{8} 和{9}被排除在结果之外,因为没有其他公共记录


我想我已经想出了一个解决方案,它使用了这个表本身的3到4个连接,而且相当复杂。有没有关于如何解决这个问题的建议?我感觉到可能有递归CTE的使用,但我无法将我的大脑围绕它。

我认为递归CTE在这里不起作用。查询完全基于顺序逻辑,对于任何给定的状态都没有逻辑下一集,因为如果不先逐个扫描每一行,就无法知道停止点在哪里;换句话说,结果基本上需要逐行评估。使用递归CTE的目的是能够附加集合;如果只是追加行,那么最终得到的结果并不比光标好多少

实际上,我会使用CLR用户定义聚合来实现类似的功能,因为我想不出性能良好的纯SQL解决方案,但如果您需要纯SQL解决方案,这里有一个使用常规非递归CTE和窗口函数的解决方案:

;WITH Rows_CTE AS
(
    SELECT
        ID, Name, Code,
        ROW_NUMBER() OVER (ORDER BY ID) AS RowNum
    FROM @Tbl
),
Changes_CTE AS
(
    SELECT
        r1.ID, r1.Name, r1.Code,
        CASE
            WHEN r1.Name = r2.Name OR r1.Code = r2.Code THEN NULL
            ELSE r1.ID
        END AS BeginGroupID
    FROM Rows_CTE r1
    LEFT JOIN Rows_CTE r2
        ON r2.RowNum = r1.RowNum - 1
),
Groups_CTE AS
(
    SELECT ID, Name, Code, BeginGroupID, m.EndGroupID
    FROM Changes_CTE c1
    CROSS APPLY
    (
        SELECT MIN(ID) AS EndGroupID
        FROM Changes_CTE c2
        WHERE c2.ID > c1.BeginGroupID
        AND c2.BeginGroupID IS NOT NULL
    ) m
)
SELECT
    t.*,
    CASE
        WHEN t.ID = g.BeginGroupID THEN NULL
        ELSE g.BeginGroupID
    END AS ParentID
FROM Groups_CTE g
INNER JOIN @Tbl t
    ON t.ID >= g.BeginGroupID
    AND t.ID < g.EndGroupID
这就是你想要的结果。它可以写得更紧凑一些,但我一直在努力提高可读性


附录:如果我们一开始就知道每个名称/代码只能在一个父项下,那么我们可以使用递归CTE并显著改进这一点,但这一假设在任何地方都没有记录,因此,我们真的必须假设最坏的情况。

请发布所有要求-在提供答案后添加/更改这些要求是对已回答者的不尊重,因为我们没有收到更改通知,因此存在被评分的风险,因为答案不再与问题同步。这一点很好。我删除了关于额外可能要求的评论。效果很好。然而,它确实在结果中留下了流氓一个,而它应该被排除在外,因为它没有孩子可以通过对组进行计数来解决。我不知道您可以使用没有分区的行号来创建行号。我也从未在子查询上使用过交叉应用,我只在函数上使用过它。缺点是,我从你的回答中意识到,我的要求中遗漏了一些项目,我向你道歉。为了清晰起见,我尽量简化,省略了一些关键部分。我将创建一个新问题,因为这是一个有正确答案的问题。
;WITH Rows_CTE AS
(
    SELECT
        ID, Name, Code,
        ROW_NUMBER() OVER (ORDER BY ID) AS RowNum
    FROM @Tbl
),
Changes_CTE AS
(
    SELECT
        r1.ID, r1.Name, r1.Code,
        CASE
            WHEN r1.Name = r2.Name OR r1.Code = r2.Code THEN NULL
            ELSE r1.ID
        END AS BeginGroupID
    FROM Rows_CTE r1
    LEFT JOIN Rows_CTE r2
        ON r2.RowNum = r1.RowNum - 1
),
Groups_CTE AS
(
    SELECT ID, Name, Code, BeginGroupID, m.EndGroupID
    FROM Changes_CTE c1
    CROSS APPLY
    (
        SELECT MIN(ID) AS EndGroupID
        FROM Changes_CTE c2
        WHERE c2.ID > c1.BeginGroupID
        AND c2.BeginGroupID IS NOT NULL
    ) m
)
SELECT
    t.*,
    CASE
        WHEN t.ID = g.BeginGroupID THEN NULL
        ELSE g.BeginGroupID
    END AS ParentID
FROM Groups_CTE g
INNER JOIN @Tbl t
    ON t.ID >= g.BeginGroupID
    AND t.ID < g.EndGroupID