24

Oracle SQLは、独自のCONNECT BY構文を使用して、v2以降の階層クエリを実行できます。最新の11gリリース2では、再帰的サブクエリファクタリング(再帰的with句とも呼ばれます)が追加されました。これはANSI規格であり、私が正しく理解していれば、これは他のRDBMSベンダーによっても実装されています。

connect-byとrecursivewithを比較すると、循環検出を使用した場合の結果セットに違いがあることに気付きました。結果による接続は私にとってより直感的であるため、Oracleの実装にバグが含まれているのか、それともこれが標準のANSIであり、予想される動作であるのか疑問に思います。したがって、私の質問は、MySQL、DB2、SQLServerなどの他のデータベースを使用してクエリで再帰をチェックできるかどうかです。もちろん、これらのデータベースが再帰的なwith句をサポートしている場合。

Oracle11.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構文を使用すると、もう1行が返されます。

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;
4

7 に答える 7

15

上のドキュメントからCONNECT_BY_ISCYCLE

現在の行にその祖先でもある子がある場合、CONNECT_BY_ISCYCLE疑似列が返されます1

そしてそれについてCYCLE

祖先の行の1つがサイクル列に対して同じ値を持っている場合、その行はサイクルを形成していると見なされます。

あなたの例では、行2にはその祖先でもある子がありますが、idまだ返されていません。

つまり、CONNECT_BY_ISCYCLEまだ返されていません)をCYCLEチェックし、現在の行(すでに返されている)をチェックします。

CONNECT BYは行ベースですが、再帰CTEはセットベースです。

OracleのドキュメントにCYCLEは「祖先の行」が記載されていることに注意してください。ただし、一般的に言えば、再帰には「祖先行」の概念はありませんCTE。これはセットベースの操作であり、ツリーから完全に結果を得ることができます。一般的に、アンカー部分と再帰部分は異なるテーブルを使用することもできます。

再帰CTE通常、階層ツリーの構築に使用されるためOracle、サイクルチェックを追加することにしました。ただし、再帰の動作がセットベースであるCTEため、「祖先行」のサイクル条件も明確に定義できないため、次のステップでサイクルが生成されるかどうかを判断することは一般に不可能です。

「次の」ステップを実行するには、「現在の」セット全体が使用可能である必要がありますが、現在のセットの各行(サイクル列を含む)を生成するには、「次の」操作の結果が必要です。

現在のセットが常に単一の行で構成されている場合(のようにCONNECT BY)は問題ありませんが、セット全体で再帰演算が定義されている場合は問題になります。

Oracle 11まだ調べていませんが、背後に隠すだけでSQL Server再帰を実装します。これには、多数の制限を設ける必要があります(これらはすべて、すべてのセットベースの操作を効果的に禁止します)。CTECONNECT BY

PostgreSQL一方、の実装は真にセットベースです。再帰部分のアンカー部分を使用して任意の操作を実行できます。ただし、サイクルはそもそも定義されていないため、サイクルを検出する手段はありません。

前に述べたように、 'sはまったくMySQL実装されていません( 'sまたはsCTEも実装されておらず、ネストされたループのみが実装されているため、それほど驚かないでください)。HASH JOINMERGE JOIN

皮肉なことに、私は今日、このテーマに関する手紙を受け取りました。これについてはブログで取り上げます。

アップデート:

CTEの再帰は、変装にすぎSQL Serverません。CONNECT BY衝撃的な詳細については、私のブログのこの記事を参照してください。

于 2009-11-18T18:02:20.733 に答える
6

PostgreSQLはWITHスタイルの階層クエリをサポートしていますが、自動サイクル検出はありません。つまり、独自に作成する必要があり、返される行数は、クエリの再帰部分で結合条件を指定する方法によって異なります。

どちらの例でも、ID(all_idsと呼ばれる)の場合は配列を使用してループを検出します。

WITH recursive tr (id, parent_id, all_ids, cycle) AS (
    SELECT id, parent_id, ARRAY[id], false
    FROM t
    WHERE id = 1
    UNION ALL
    SELECT t.id, t.parent_id, all_ids || t.id, t.id = ANY(all_ids)
    FROM t
    JOIN tr ON t.parent_id = tr.id AND NOT cycle)
SELECT id, parent_id, cycle
FROM tr;

 id | parent_id | cycle
----+-----------+-------
  1 |         2 | f
  2 |         1 | f
  1 |         2 | t


WITH recursive tr (id, parent_id, all_ids, cycle) AS (
    SELECT id, parent_id, ARRAY[id], false
    FROM t
    WHERE id = 1
    UNION ALL
    SELECT t.id, t.parent_id, all_ids || t.id, (EXISTS(SELECT 1 FROM t AS x WHERE x.id = t.parent_id))
    FROM t
    JOIN tr ON t.parent_id = tr.id
    WHERE NOT t.id = ANY(all_ids))
SELECT id, parent_id, cycle
FROM tr;

 id | parent_id | cycle
----+-----------+-------
  1 |         2 | f
  2 |         1 | t
于 2009-11-14T01:06:56.557 に答える
3

私の知る限り:

  • MySQLは再帰CTEをサポートしていません
  • SQL Severは、再帰CTEでのサイクル検出をサポートしていません
于 2009-11-13T23:03:43.997 に答える
1

MySQLサーバーバージョン5.0.45は気に入らなかったwith

エラー1064(42000):SQL構文にエラーがあります。MySQLサーバーのバージョンに対応するマニュアルで、near'with tr(id、parent_id)as(select id、parent_id from t where id = 1 union alls'を1行目で使用する正しい構文を確認してください。

于 2009-11-13T21:08:50.603 に答える
1

接続の結果は、必ずしも直感的であるとは限りません。

id = 3以下のクエリは、画像のグラフから始まるサイクルを検出するためのさまざまなアプローチを示しています。

create table graph (id, id_parent) as
(select 2, 1 from dual
union all select 3, 1 from dual
union all select 4, 3 from dual
union all select 5, 4 from dual
union all select 3, 5 from dual)

ここに画像の説明を入力してください

SQL> select level lvl, graph.*, connect_by_iscycle cycle
  2    from graph
  3   start with id = 3
  4  connect by nocycle prior id = id_parent;

       LVL         ID  ID_PARENT      CYCLE
---------- ---------- ---------- ----------
         1          3          1          0
         2          4          3          0
         3          5          4          1
         1          3          5          0
         2          4          3          0
         3          5          4          1

6 rows selected.

SQL> select level lvl, graph.*, connect_by_iscycle cycle
  2    from graph
  3   start with id = 3
  4  connect by nocycle prior id = id_parent
  5         and prior id_parent is not null;

       LVL         ID  ID_PARENT      CYCLE
---------- ---------- ---------- ----------
         1          3          1          0
         2          4          3          0
         3          5          4          0
         4          3          5          1
         1          3          5          0
         2          4          3          0
         3          5          4          1

7 rows selected.

SQL> with t(id, id_parent) as
  2   (select *
  3      from graph
  4     where id = 3
  5    union all
  6    select g.id, g.id_parent
  7      from t
  8      join graph g
  9        on t.id = g.id_parent)
 10  search depth first by id set ord
 11  cycle id set cycle to 1 default 0
 12  select * from t;

        ID  ID_PARENT        ORD C
---------- ---------- ---------- -
         3          1          1 0
         4          3          2 0
         5          4          3 0
         3          5          4 1
         3          5          5 0
         4          3          6 0
         5          4          7 0
         3          5          8 1

8 rows selected.

ノードにid = 3は2つの親があるため、この例ではOracleが2サイクルをトラバースします。

(1, 3) -> (3, 4) -> (4, 5) -> (5, 3)

(5, 3) -> (3, 4) -> (4, 5)

最初のクエリと最初のサイクルの結果からエッジ(5、3)が欠落しています。同時に、エッジ(5、3)が3番目のクエリと2番目のサイクルの結果に2回表示されます。

なぜそうなのか?Quassnoiが提供する回答で、循環検出ロジックの説明を確認できます。平易な英語でそれはそれを意味します

(1)現在の行の子IDがこれまでにアクセスしたIDの一部である場合、接続はサイクルを検出します

(2)rec withは、現在の行のIDがこれまでにアクセスしたIDの一部である場合にサイクルを検出します

追加の述語がありますが、2番目のクエリの結果は最も自然に見えますand prior id_parent is not null。この場合

(3)現在の行のID がこれまでにアクセスした親IDの一部である場合、サイクルを検出します

このすべての条件は、以下のクエリの列cnt1、cnt2、cnt3に実装されています。

SQL> with t(id, id_parent, path_id, path_id_parent, cnt1, cnt2, cnt3) as
  2   (select g.*,
  3           cast('->' || g.id as varchar2(4000)),
  4           cast('->' || g.id_parent as varchar2(4000)),
  5           0,
  6           0,
  7           0
  8      from graph g
  9     where id = 3
 10    union all
 11    select g.id,
 12           g.id_parent,
 13           t.path_id || '->' || g.id,
 14           t.path_id_parent || '->' || g.id_parent,
 15           regexp_count(t.path_id || '->', '->' ||
 16            (select id from graph c where c.id_parent = g.id) || '->'),
 17           regexp_count(t.path_id || '->', '->' || g.id || '->'),
 18           regexp_count(t.path_id_parent || '->', '->' || g.id || '->')
 19      from t
 20      join graph g
 21        on t.id = g.id_parent
 22    -- and t.cnt1 = 0
 23    -- and t.cnt2 = 0
 24    -- and t.cnt3 = 0
 25    )
 26  search depth first by id set ord
 27  cycle id set cycle to 1 default 0
 28  select * from t;

        ID  ID_PARENT PATH_ID         PATH_ID_PARENT  CNT1 CNT2 CNT3        ORD C
---------- ---------- --------------- --------------- ---- ---- ---- ---------- -
         3          1 ->3             ->1                0    0    0          1 0
         4          3 ->3->4          ->1->3             0    0    0          2 0
         5          4 ->3->4->5       ->1->3->4          1    0    0          3 0
         3          5 ->3->4->5->3    ->1->3->4->5       1    1    1          4 1
         3          5 ->3             ->5                0    0    0          5 0
         4          3 ->3->4          ->5->3             0    0    0          6 0
         5          4 ->3->4->5       ->5->3->4          1    0    1          7 0
         3          5 ->3->4->5->3    ->5->3->4->5       1    1    1          8 1

8 rows selected.

cnt1 / cnt2 / cnt3でフィルターのコメントを解除し、「cycle id set cycle to 1 default 0」を削除すると、クエリは上記の対応するクエリとして結果を返します。言い換えれば、より直感的なサイクル検出ロジックを回避して実装できcycle clauseます。

階層のトラバースとサイクル検出の詳細については、 『Oracle SQL Revealed 』を参照してください。

于 2018-11-20T22:25:39.550 に答える
0
WITH RECURSIVE s (master, slave, all_ids, cycle) AS
(
    SELECT master, slave, ARRAY[master], false FROM binding WHERE master=3477

    UNION ALL

    SELECT d.master, d.slave, all_ids || d.master, d.slave = ANY(all_ids)
    FROM
        binding AS d
    JOIN
        s
    ON (d.master = s.slave)
    WHERE NOT d.master = ANY(all_ids)
)
SELECT *
FROM s;

この状態の方がいいと思いますd.slave = ANY(all_ids)

于 2014-01-26T14:50:25.037 に答える
0

「したがって、私の質問は、MySQL、DB2、SQLServerなどの他のデータベースを使用してクエリで再帰をチェックできるかどうかです。」

MariaDB 10.5.2以降のサポートサイクル検出:

CYCLE句は、CTEサイクル検出を有効にし、過剰または無限ループを回避します。MariaDBは、緩和された非標準の文法をサポートします。

WITH RECURSIVE ... (
 ...
)
CYCLE <cycle column list> RESTRICT

例:

CREATE TABLE t(id INT, parent_id INT);
INSERT INTO t(id, parent_id) VALUES (1, NULL),(2,1),(3,2),(1,3);

WITH RECURSIVE cte AS (
  SELECT id, parent_id, 0 lvl 
  FROM t WHERE parent_id IS NULL
  UNION ALL
  SELECT t.id, t.parent_id, lvl + 1 AS lvl
  FROM cte c1
  JOIN t ON c1.id = t.parent_id
)
CYCLE id, parent_id RESTRICT 
SELECT * FROM cte ORDER BY lvl;

db<>フィドルデモ

于 2020-06-28T12:20:13.853 に答える