使用递归CTE来解析组,而不是层次结构

时间:2012-11-29 12:33:42

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

我正在尝试使用以下示例数据

在SQL Server中执行递归CTE
Class        Student
------       ------
English      Sally   <- Sally is what were searching for
English      Peter   <- Peter's on same Class as Sally
Swedish      Peter   <- Found because Peter's on this class
Dutch        Peter   <- Found because Peter's on this class
Finnish      Harry   <- Not found, no relation to class or student
Swedish      Tim     <- Found because Peter's on Swedish class
Spanish      Lauren  <- Not found, no relation to class or student
Spanish      Colin   <- Not found, no relation to class or student

所以我需要一个CTE,我将'Sally'作为参数,它将找出与Sally相关的所有不同的类,然后所有学生都与Sally所在的课程相关,然后所有课程都与学生相关。与Sally相同的类,依此类推,直到找不到更多的行。

但是我无法弄清楚如何编写连接,这就是我尝试但失败的失败:

WITH myCTE (Class, Student) AS
(
    SELECT Class, Student FROM TABLE1 WHERE TABLE1.Student= 'Sally'
    UNION ALL
    SELECT t.Class, t.Student FROM TABLE1 t
    JOIN myCTE t2 ON t2.Class = t.Class
)
SELECT * FROM myCTE

1 个答案:

答案 0 :(得分:2)

第一个问题是你得到了无限的递归:Sally和Peter一起学英语,他和Sally一起学英语,他和Peter一起学英语......

一旦你对它进行了排序,你需要在递归CTE中进行额外的查询。您目前正在加入Class以吸引同一班级的其他学生,但您还需要加入Student以便为学生提供其他课程。

这样的事情应该有效:

WITH cteSource As
(
   SELECT
      Class,
      Student,
      -- Create a unique ID for each record:
      ROW_NUMBER() OVER (ORDER BY Student, Class) As ID
   FROM
      TABLE1
),
cteRecursive (Class, Student, IDPath) As
(
   SELECT
      Class,
      Student,
      -- Used to exclude records we've already visited:
      Convert(varchar(max), '/' + Convert(varchar(10), ID) + '/')
   FROM
      cteSource
   WHERE
      Student = 'Sally'

   UNION ALL

   -- Students in the same class:
   SELECT
      T.Class,
      T.Student,
      R.IDPath + Convert(varchar(10), T.ID) + '/'
   FROM
      cteSource As T
      INNER JOIN cteRecursive As R
      ON T.Class = R.Class
   WHERE
      CharIndex('/' + Convert(varchar(10), t.ID) + '/', R.IDPath) = 0

   UNION ALL

   -- Other classes for the students:
   SELECT
      T.Class,
      T.Student,
      R.IDPath + Convert(varchar(10), T.ID) + '/'
   FROM
      cteSource As T
      INNER JOIN cteRecursive As R
      ON T.Student = R.Student
   WHERE
      CharIndex('/' + Convert(varchar(10), t.ID) + '/', R.IDPath) = 0
)
SELECT
   Class,
   Student,
   IDPath
FROM
   cteRecursive
;

使用您的测试数据,您将获得以下结果:

English   Sally   /7/
English   Peter   /7/5/
Dutch     Peter   /7/5/4/
Swedish   Peter   /7/5/6/
Swedish   Tim     /7/5/6/8/
Dutch     Peter   /7/5/6/4/
Swedish   Peter   /7/5/4/6/
Swedish   Tim     /7/5/4/6/8/

如果您使用的是SQL 2008或更高版本,如果您将IDPath设为HierarchyID,可能会获得更好的效果,但您需要使用实际数据进行测试。

修改
您可能需要将最终选择更改为:

SELECT DISTINCT
   Class,
   Student
FROM
   cteRecursive

处理同一记录有多条路径的情况。例如,“Dutch / Peter”,“Swedish / Peter”和“Swedish / Tim”都出现了两次。