【问题标题】:Recursive CTE in presence of circular references存在循环引用时的递归 CTE
【发布时间】:2014-03-17 10:57:13
【问题描述】:

我在 SQL Server 数据库中有一个层次结构。我正在尝试编写一个查询来获取给定元素下结构中的所有元素。

所以,给定一个包含 id 和 parent_id 列的 DB 表,这就是我要做的:

WITH recursive_cte (root_id, id) AS (
  SELECT parent_id, id
  FROM test_cte
  UNION ALL
  SELECT t.parent_id, r.id
  FROM test_cte t
  INNER JOIN recursive_cte r
  ON (r.root_id=t.id)
)
SELECT *
FROM recursive_cte
WHERE root_id=0

现在,如果在 id=0 元素下的结构中存在循环引用,我会从 DBMS 收到一个错误(在语句完成之前,最大递归 100 已用尽)。这很好,循环引用的存在已经是一个错误。

但是如果我在另一个元素下的结构中有循环引用,查询总是会报错。即使我指定了将记录集限制为非循环的条件(例如WHERE root_id=0)。

例如在:

id|parent_id|name           |
--+---------+---------------+
0 |NULL     |TEST A         |
1 |4        |TEST CIRCULAR  |
2 |0        |TEST B         |
3 |2        |TEST C         |
4 |1        |TEST CIRCULAR B|

我希望我的查询在条件root_id=0 下正常工作。有没有办法做到这一点?

【问题讨论】:

    标签: sql sql-server recursion recursive-query


    【解决方案1】:

    这可能不是一个优雅的解决方案,但它似乎有效。将访问过的 id 列表存储在列表中,并从进一步搜索中排除它们。我认为这是您查询的正确比较:

    WITH recursive_cte(root_id, id, ids) AS (
      SELECT parent_id, id, ',' + cast(id as varchar(8000)) + ',' as ids
      FROM test_cte
      UNION ALL
      SELECT t.parent_id, r.id, ids + cast(id as varchar(8000)) + ','
      FROM test_cte t INNER JOIN 
           recursive_cte r
           ON r.root_id = t.id
      WHERE ',' + r.ids + ',' not like '%,' + cast(t.id as varchar(8000)) + ',%'
    )
    SELECT *
    FROM recursive_cte
    WHERE root_id = 0;
    

    【讨论】:

    • 在 CTE 的递归部分,我认为在 WHERE 子句中将逗号添加到 r.ids 是没有必要的。 ids 已经有一个开始和结束的逗号 AFAICT。
    【解决方案2】:

    您需要将 WHERE 过滤器放在查询的 CTE 部分,如下所示:

    WITH recursive_cte (root_id, id) AS (
      SELECT parent_id, id
      FROM test_cte
      WHERE id=0       -- Restrict your recursion to start from the item with id = 0, instead of considdering all items.
      UNION ALL
      SELECT t.parent_id, r.id
      FROM test_cte t
      INNER JOIN recursive_cte r
      ON (r.root_id=t.id)
    )
    SELECT *
    FROM recursive_cte
    

    【讨论】:

    • 可以像第一次重复而不是进行 100 次迭代那样进行限制吗?
    • 限制迭代次数的一种方法是在 UNION ALL 上方的 SELECT 语句中包含一个固定值为 1 的新列。在 UNION ALL 正下方的 SELECT 语句中,将该列加上 1。然后,在最后的 SELECT 语句中,您可以在该列上放置一个过滤器。
    猜你喜欢
    • 2016-09-28
    • 1970-01-01
    • 2021-11-04
    • 1970-01-01
    • 2020-02-08
    • 1970-01-01
    • 2018-06-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多