【问题标题】:Recursive CTE with three tables具有三个表的递归 CTE
【发布时间】:2018-02-13 14:39:28
【问题描述】:

我使用的是 SQL Server 2008 R2 SP1。 我想通过“上树”递归地找到某个组织单位的第一个非空经理。

我有一个包含组织单位“ORG”的表,一个包含每个组织的父级的表。在“ORG”中的单位,我们称该表为“ORG_PARENTS”,并且一个包含每个组织单位的经理的表,我们称该表为“ORG_MANAGERS”。

ORG 有一列 ORG_ID:

ORG_ID

1

2

3

ORG_PARENTS 有两列。

ORG_ID, ORG_PARENT

1, NULL

2, 1

3, 2

MANAGERS 有两列。

ORG_ID, MANAGER

1, John Doe

2, Jane Doe

3, NULL

我正在尝试创建一个递归查询,它将找到某个组织单位的第一个非空经理。

基本上,如果我今天对 ORG_ID=3 的经理进行查询,我将得到 NULL。

SELECT MANAGER FROM ORG_MANAGERS WHERE ORG_ID = '3'

我希望查询使用 ORG_PARENTS 表来获取 ORG_ID=3 的父级,在本例中获取“2”并针对 ORG_ID=2 的 ORG_MANAGERS 表重复查询,并在此示例中返回“Jane Doe”。

如果查询也返回 NULL,我想用 ORG_ID=2 的父级重复该过程,即 ORG_ID=1 等等。

到目前为止,我的 CTE 尝试都失败了,一个例子是:

WITH BOSS (MANAGER, ORG_ID, ORG_PARENT)
AS
( SELECT m.MANAGER, m.ORG_ID, p.ORG_PARENT
FROM dbo.MANAGERS m INNER JOIN
dbo.ORG_PARENTS p ON p.ORG_ID = m.ORG_ID
UNION ALL
SELECT m1.MANAGER, m1.ORG_ID, b.ORG_PARENT
FROM BOSS b
INNER JOIN dbo.MANAGERS m1 ON m1.ORG_ID = b.ORG_PARENT
)

SELECT * FROM BOSS WHERE ORG_ID = 3

返回:

消息 530,第 16 级,状态 1,第 4 行 声明终止。在语句完成之前,最大递归 100 已用完。

MANAGER ORG_ID  ORG_PARENT
NULL      3        2

【问题讨论】:

  • option (maxrecursion 0) 在来自Bossselect 语句的末尾。参考stackoverflow.com/questions/45408509/…
  • 这会导致无限循环,我知道我发布的 CTE 是错误的。
  • 表未标记为 ORG_PARENTS
  • @WEI_DBA...option (maxrecursion 0) (根据我的经验)从来没有,或者至少极少,是这类问题的答案。即使在您引用的链接中,那里的 OP 也声明它不是解决方案。
  • 你应该举更多的例子

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


【解决方案1】:

您需要跟踪开始时使用的原始 ID。试试这个:

DECLARE @ORG_PARENTS TABLE (ORG_ID INT, ORG_PARENT INT ) 
DECLARE @MANAGERS TABLE (ORG_ID INT, MANAGER VARCHAR(100))

INSERT @ORG_PARENTS (ORG_ID, ORG_PARENT)
VALUES  (1, NULL)
,       (2, 1)
,       (3, 2)

INSERT @MANAGERS (ORG_ID, MANAGER)
VALUES (1, 'John Doe')
,       (2, 'Jane Doe')
,       (3, NULL)
;
WITH BOSS
AS
( 
    SELECT      m.MANAGER, m.ORG_ID AS ORI, m.ORG_ID, p.ORG_PARENT, 1 cnt
    FROM        @MANAGERS m 
    INNER JOIN  @ORG_PARENTS p 
                ON p.ORG_ID = m.ORG_ID
    UNION ALL

    SELECT      m1.MANAGER, b.ORI, m1.ORG_ID, OP.ORG_PARENT, cnt +1
    FROM        BOSS b
    INNER JOIN  @ORG_PARENTS AS OP
            ON  OP.ORG_ID = b.ORG_PARENT
    INNER JOIN  @MANAGERS m1 
            ON  m1.ORG_ID = OP.ORG_ID 
)

SELECT  * 
FROM    BOSS 
WHERE   ORI = 3

结果:

+----------+-----+--------+------------+-----+
| MANAGER  | ORI | ORG_ID | ORG_PARENT | cnt |
+----------+-----+--------+------------+-----+
| NULL     |   3 |      3 | 2          |   1 |
| Jane Doe |   3 |      2 | 1          |   2 |
| John Doe |   3 |      1 | NULL       |   3 |
+----------+-----+--------+------------+-----+

一般提示:

不要预定义 CTE 的列;这不是必需的,并且使维护变得烦人。

使用递归 CTE,始终保留一个计数器,这样您就可以限制递归性,并且可以跟踪您的深度。

编辑:

顺便说一句,如果你想要第一个非空管理器,你可以这样做(有很多方法):

SELECT  BOSS.* 
FROM    BOSS 
INNER JOIN (
                SELECT  BOSS.ORI
                ,       MIN(BOSS.cnt) cnt
                FROM    BOSS
                WHERE   BOSS.MANAGER IS NOT NULL
                GROUP BY BOSS.ORI 
            ) X
        ON  X.ORI = BOSS.ORI
        AND X.cnt = BOSS.cnt
WHERE   BOSS.ORI IN (3)

【讨论】:

  • 如果@MANAGERS 表完全缺少ORG_ID=3 以防它为NULL,我应该如何处理?
  • 我不确定我是否理解您描述的情况。如果表中缺少数据,那就是丢失了,你想发生什么?
  • 对不起,CTE 对我来说是新的,我想知道如果我们以我的示例 MANAGER 表为例,如果 org.unit 缺少经理,那么是否可以使用递归查询org.unit 在 MANAGER 表中根本没有一行: INSERT @MANAGERS (ORG_ID, MANAGER) VALUES (1, 'John Doe') , (2, 'Jane Doe') 我知道它改变了原来的问题有点因为我不能再在递归成员中使用 INNER JOIN 并且不允许使用 LEFT OUTER JOIN。
  • 我隐约记得曾经在这种情况下使用过outer apply,但你真的应该问一个新问题。
猜你喜欢
  • 2022-01-01
  • 2019-02-17
  • 2017-06-28
  • 1970-01-01
  • 2022-11-14
  • 2013-11-03
  • 2011-07-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多