查询以对多对多联结表中的完全递归关系进行分组

时间:2018-09-25 17:40:33

标签: sql sql-server tsql

很抱歉,如果这是重复的地方,请在此处搜索,网络似乎与我的问题相似但不完全匹配,因此我决定发布。

我称其为多对多关系中的完全递归分组。我已经尝试编写联接和ctes来做到这一点,但是没有完全递归,我只能深入一层,而对于编写嵌套的动态游标我也不感到很兴奋。

可以从此“交界处”表派生假设表达式以查找不同的学生/班级,因此可以仅使用一组来总结问题。

SELECT * FROM student_class

+-------------+----------+--------------+
| student_id  | class_id | group_number |
+-------------+----------+--------------+
| 1           | A        | null         |
| 1           | C        | null         |
| 2           | A        | null         |
| 2           | B        | null         |
| 2           | C        | null         |
| 3           | E        | null         |
| 4           | B        | null         |
| 4           | F        | null         |
+-------------+----------+--------------+ 

问题是如何通过递归关系为每个学生和每个班级填充组号。例如:如果student_id 1具有class_id A,那么还有哪些其他学生具有class_id A?对于其他学生,他们还开设哪些其他课程?对于其他每个班级,其他哪些学生也有这些班级?然后继续遍历结果,直到找不到更多依赖项为止。

因此,在此示例中,最终更新将仅包含两个组,因此应这样读取,因为没有其他学生拥有class_id C,student_id 3没有其他班级:

+-------------+----------+--------------+
| student_id  | class_id | group_number |
+-------------+----------+--------------+
| 1           | A        | 1            |
| 1           | C        | 1            |
| 2           | A        | 1            |
| 2           | B        | 1            |
| 2           | C        | 1            |
| 3           | E        | 2            |
| 4           | B        | 1            |
| 4           | F        | 1            |
+-------------+----------+--------------+ 

1 个答案:

答案 0 :(得分:0)

这真的很棘手。这是一种图形遍历算法(并不难)。但是您必须定义图。您可以使用自连接定义两个类之间的链接。然后可以将其用于递归CTE。

因此,要获得等价类(此处有点挑剔),您可以这样做:

with cs as (
      select *
      from (values (1, 'A'), (1, 'C'), (2, 'A'), (2, 'B'), (2, 'C'), (3, 'E'), (4, 'B'), (4, 'F')) v(student, class)
     ),
     cc as (
      select distinct cs1.class as class1, cs2.class as class2
      from cs cs1 join
           cs cs2
           on cs1.student = cs2.student
     ),
     cte as (
      select cc.class1 as class, cc.class2 as grp, cast(',' + cc.class1 + ',' as varchar(max)) as grps
      from cc
      union all
      select cte.class, cc.class2, 
             cast(grps + cc.class2 + ',' as varchar(max)) as grps
      from cte join
           cc
           on cc.class1 = cte.grp and cte.grps not like '%,' + cc.class1 + ',%'
    )
select cte.class, min(cte.grp)
from cte
group by cte.class;

如果要将它们转换为数字:

select cte.class, min(cte.grp),
       dense_rank() over (order by min(cte_grp)) as group_number
from cte
group by cte.class;