一尘不染

使用递归子查询分解的周期检测

sql

从v2开始,Oracle SQL可以使用其专有的CONNECT
BY语法进行分层查询。在最新的11g版本2中,他们添加了递归子查询因子,也称为递归with子句。这是ANSI标准,如果我理解正确,那么其他RDBMS供应商也已经实现了这一标准。

在比较connect-
by和递归方式时,我发现使用周期检测时结果集有所不同。按结果进行连接对我来说更直观,因此我想知道Oracle的实现中是否包含错误,或者这是否是标准ANSI和预期的行为。因此,我的问题是,是否可以使用其他数据库(例如MySQL,DB2,SQL
Server和其他数据库)通过查询检查递归。前提是那些数据库当然支持递归with子句。

这是它在Oracle 11.2.0.1.0上的工作方式

SQL> select *
  2    from t
  3  /

        ID  PARENT_ID
---------- ----------
         1          2
         2          1

2 rows selected.

使用CONNECT BY语法的查询:

SQL>  select id
  2        , parent_id
  3        , connect_by_iscycle
  4     from t
  5  connect by nocycle parent_id = prior id
  6    start with id = 1
  7  /

        ID  PARENT_ID CONNECT_BY_ISCYCLE
---------- ---------- ------------------
         1          2                  0
         2          1                  1

2 rows selected.

在我看来,这很直观。但是,使用新的ANSI语法,它将返回多一行:

SQL> with tr (id,parent_id) as
  2  ( select id
  3         , parent_id
  4      from t
  5     where id = 1
  6     union all
  7    select t.id
  8         , t.parent_id
  9      from t
 10           join tr on t.parent_id = tr.id
 11  ) cycle id set is_cycle to '1' default '0'
 12  select id
 13       , parent_id
 14       , is_cycle
 15    from tr
 16  /

        ID  PARENT_ID I
---------- ---------- -
         1          2 0
         2          1 0
         1          2 1

3 rows selected.

这是您可以用来检查的脚本:

create table t
( id        number
, parent_id number
);
insert into t values (1, 2);
insert into t values (2, 1);
commit;
with tr (id,parent_id) as
( select id
       , parent_id
    from t
   where id = 1
   union all
  select t.id
       , t.parent_id
    from t
         join tr on t.parent_id = tr.id
) cycle id set is_cycle to '1' default '0'
select id
     , parent_id
     , is_cycle
  from tr;

阅读 137

收藏
2021-03-17

共1个答案

一尘不染

从以下文档CONNECT_BY_ISCYCLE

CONNECT_BY_ISCYCLE虚列返回1如果当前行有一个孩子,这也是它的祖先

以及CYCLE

如果某一行的祖先行的某个周期列的值相同,则认为该行构成了一个周期。

在您的示例中,row2确实有一个孩子,它也是其祖先,但id尚未返回。

换句话说,CONNECT_BY_ISCYCLE检查 子项 (尚未返回),同时CYCLE检查 当前行 (已返回)。

CONNECT BY基于行,而递归CTE基于集。

请注意,Oracle的文档中CYCLE提到了“祖先行”。但是,一般来讲,递归中没有“祖先行”的概念CTE。这是一个基于集合的操作,可以完全从树中产生结果。一般来说,锚部分和递归部分甚至可以使用不同的表。

由于递归CTE的是 通常
用于建立层次结构树,Oracle决定增加一个周期的检查。但是由于递归CTE操作的基于集合的方式,通常无法判断下一步是否会生成一个循环,因为如果没有对“祖先行”循环条件的明确定义,也将无法定义。

要执行“下一步”步骤,需要整个“当前”集可用,但是要生成当前集的每一行(包括循环列),我们只需要具有“下一步”操作的结果即可。

如果当前集合始终仅由一行组成(例如中的CONNECT BY),这不是问题,但是如果对集合进行整体定义的递归操作则是一个问题。

没考虑Oracle 11还,但SQL Server工具递归CTE的只是隐藏的CONNECT BY在他们身后,这需要将大量的限制(所有有效禁止所有基于集合的操作)。

PostgreSQL另一方面,的实现是真正基于集合的:您可以对递归部分中的锚点部分执行任何操作。但是,它没有任何检测循环的方法,因为循环不是首先定义的。

如前所述,MySQL根本不实现CTEs(也没有实现HASH JOINs或MERGE JOINs,仅实现嵌套循环,因此不要感到惊讶)。

具有讽刺意味的是,我今天收到了关于这个主题的一封信,我将在我的博客中进行介绍。

更新:

递归CTESQL Server只不过CONNECT BY是变相而已。有关令人震惊的详细信息,请参阅我的博客中的这篇文章:

2021-03-17