【问题标题】:Using recursive CTE to resolve a group, not hierarchy使用递归 CTE 来解析组,而不是层次结构
【发布时间】:2012-11-29 12:33:41
【问题描述】:

我正在尝试使用以下示例数据在 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 有 'A' 和 'B',而值 'B' 在 Field1 中有值 1,2 和 3。我的示例代码中的问题是它无限循环,并且在没有找到新事件时不会停止。
  • 我认为您的数据集太小,无法清楚地说明您要做什么。请添加一些行,例如(4,C) (3,D) (10,A) 并告诉我们查询应该从该输入输出什么(以及为什么)。
  • 好的,编辑并写了应该找到哪些行。我可以使用 while 循环或光标来做到这一点,但我认为 CTE 会更专业!

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


【解决方案1】:

第一个问题是你有无限递归:Sally 和 Peter 一起学习英语,Sally 和 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”都出现了两次。

【讨论】:

  • 是的,我试了一下,它似乎奏效了!明天我会对其进行更多测试,如果可行的话,我会接受你的答案。谢谢!
猜你喜欢
  • 2023-03-24
  • 1970-01-01
  • 2013-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-22
  • 2017-02-04
  • 1970-01-01
相关资源
最近更新 更多