【问题标题】:Infinite loop with recursive SQL query带有递归 SQL 查询的无限循环
【发布时间】:2020-04-01 19:34:56
【问题描述】:

我似乎无法在此查询中找到无限循环背后的原因,也不知道如何纠正它。

这里是上下文: 我有一个名为 mergesWith 的表,其描述如下: mergesWith:有关邻近海域的信息。请注意,在这种关系中,对于每一对 相邻的海洋(A,B),只给出一个元组——因此,关系不是对称的。 sea1:一片海 sea2:大海。

我想通过导航了解从地中海可到达的每一个海洋。我选择了使用“with”的递归查询:

With 
   acces(p,d) as (
       select sea1 as p, sea2 as d
       from MERGESWITH
       UNION ALL
       select a.p, case when mw.sea1=a.d 
                       then mw.sea2 
                       else mw.sea1 
                   end as d
       from acces a, MERGESWITH mw
       where a.d=mw.sea1 or a.d=mw.sea2)
select d
from acces
where p= 'Mediterranean Sea';

我认为原因是 case whena.d=mw.sea1 or a.d=mw.sea2 限制不够,但我似乎无法确定原因。

我收到此错误消息: 32044。 00000 - “执行递归 WITH 查询时检测到循环” *原因:递归 WITH 子句查询产生了一个循环并被停止 为了避免无限循环。 *Action:重写递归 WITH 查询以停止递归或使用 CYCLE 子句。

【问题讨论】:

  • 几乎不可能在没有看到示例数据的情况下为您提供解决方案。

标签: sql oracle recursion with-statement hierarchical-query


【解决方案1】:

显然,由于数据中的循环,这将取决于数据。在开发递归 CTE 时,我通常包含一个 lev 值。这使得调试它们变得更简单。

所以,试试这样的:

with acces(p, d, lev) as (
      select sea1 as p, sea2 as d, 1 as lev
      from MERGESWITH
      union all
      select a.p,
             (case when mw.sea1 = a.d then mw.sea2 else mw.sea1 end) as d,
             lev + 1
       from acces a join
            MERGESWITH mw
            on a.d in (mw.sea1, mw.sea2)
       where lev < 5)
select d
from acces
where p = 'Mediterranean Sea';

如果您找到原因但无法修复代码,请提出 问题,并提供示例数据和所需结果。某种 DB fiddle 也很有帮助。

【讨论】:

  • 我已经尝试过您编写的代码,但我仍然得到完全相同的错误:32044.00000 -“执行递归 WITH 查询时检测到循环” *原因:递归 WITH 子句查询产生了一个循环并被停止以避免无限循环。 *操作:重写递归 WITH 查询以停止递归或使用 CYCLE 子句。
  • 显然,即使这确实有效,查询也不等同于原始查询。它没有找到与原始海洋相连的海洋,但这需要通过其他海洋进行太多的跳跃。但是,问题仍然存在 - 为什么 Oracle 仍然在您的查询中找到一个循环。答案是循环自动检测的实现有一个bug;我在这里写过:community.oracle.com/thread/4197768
  • 具体来说:自动循环检测基于递归成员的where子句中引用的列。但是,当使用 ANSI 连接时,只检查“引用列”的连接条件,而忽略仅出现在 where 子句中的其他列。同样,这是一个错误。因为我不是 Oracle 的付费客户,所以我没有报告这个错误(实际上我不知道它是否已经被报告)。当然,“解决方法”是检测周期的正确方法 - cycle 子句。
【解决方案2】:

循环是由查询的结构引起的,而不是由数据中的循环引起的。您询问骑自行车的原因。这应该很明显:在第一次迭代中,一行输出有d = 'Aegean Sea'。在第二次迭代中,您会发现有d = 'Mediterranean Sea' 的行,对吗?您现在可以看到这将如何导致循环吗?

递归查询有一个cycle 子句正好用于此类问题。出于某种原因,即使是许多学习了递归 with 子句并一直使用它的用户,似乎也没有意识到 cycle 子句(以及不相关但同样有用的 search 子句 - 用于排序输出)。

在您的代码中,您需要进行两项更改。添加 cycle 子句,并在外部查询过滤器中仅针对非循环行。在cycle 子句中,您可以决定将什么称为“循环”列,以及赋予它什么值。为了使其看起来尽可能类似于connect by 查询,我喜欢将新列称为IS_CYCLE 并为其指定值0(表示无循环)和1(表示循环)。在下面的外部查询中,将 is_cycle 添加到 select 列表中,以查看它添加到递归查询中的内容。

注意cycle 子句的位置:它位于递归with 子句之后(特别是结尾的右括号之后递归因子子查询)。

with 
   acces(p,d) as (
       select sea1 as p, sea2 as d
       from   MERGESWITH
       UNION ALL
       select  a.p, case when mw.sea1=a.d 
                         then mw.sea2 
                         else mw.sea1 
                    end  as d
       from   acces a, MERGESWITH mw
       where  a.d=mw.sea1 or a.d=mw.sea2)
       cycle  d set is_cycle to 1 default 0           -- add this line
select d
from   acces
where  p= 'Mediterranean Sea'
  and  is_cycle = 0                                   -- and this line
;

【讨论】:

    猜你喜欢
    • 2023-03-29
    • 2020-02-08
    • 2017-03-15
    • 2016-10-06
    • 1970-01-01
    • 1970-01-01
    • 2014-10-13
    • 2016-09-28
    • 2010-10-21
    相关资源
    最近更新 更多