web-dev-qa-db-ja.com

循環参照を見つけるためのクエリ

2つのテーブルがあります

ID  Task
1   1
2   2
3   3
4   4

Col1  depend
2     3
2     4
3     1
4     2

IDおよびCol1は、FK制約によって関連付けられています。すべての循環参照を見つけたい。ここでIDCol1は、2つのテーブルの行を結合するためのものです。例:

Task 1 can start anytime.
Task 2 can start only after completion of 3, 4 etc

1 –
2 – 3, 4, 1, 2   -- for 2 there is circular dependency
3 – 1
4 – 2, 3, 4      -- also circular dependency

ケース2:

Col1  depend
2     3
2     4
3     1
4     5
5     2

ID  Task
1   1
2   2
3   3
4   4
5   5

1
2 – 3, 4, 1, 5, 2     -- circular reference
3 – 1
4 – 5, 2, 3, 4        -- circular reference
5 – 2, 3, 4, 5        -- circular reference

循環参照は、どの再帰レベルでも使用できます。そのような循環参照を見つける方法は?
再帰クエリを試しましたが、無限ループに入りました。これに対する再帰クエリを書く方法は?

6
GaNeSh GoRdE

http://www.postgresql.org/docs/8.4/static/queries-with.html の例をあなたのケースに適合させました:

WITH RECURSIVE search_graph(id, depends, depth, path, cycle) AS (
        SELECT g.Col1, g.depends, 1,
          ARRAY[g.Col1],
          false
        FROM deps g
      UNION ALL
        SELECT g.Col1, g.depends, sg.depth + 1,
          path || g.Col1,
          g.Col1 = ANY(path)
        FROM deps g, search_graph sg
        WHERE g.Col1 = sg.depends AND NOT cycle
)
SELECT distinct id FROM search_graph where cycle = true;

結果:

ID
 4
 2

最初の例では、

ID
 4
 2
 5

二番目に

SQLフィドルは http://sqlfiddle.com/#!15/87a96/2 にあります。

4
Leo

マニュアルの例 にも基づいています:

WITH RECURSIVE graph AS (
   SELECT col1 AS id
        , ARRAY[depend, col1] AS path
        , (depend = col1) AS cycle    -- first step could be circular
   FROM   dep

   UNION ALL
   SELECT d.col1, d.depend || path, d.depend = ANY(path)
   FROM   graph g
   JOIN   dep   d ON d.col1 = g.path[1]
   WHERE  NOT g.cycle
   )
SELECT DISTINCT id
FROM   graph
WHERE  cycle;

結果

id
 2
 4
 5

SQLフィドル。

これは少し単純で高速です。

  • 無関係な列はありません。
  • 先にチェックするため、反復が1つ少なくなります。
  • 短絡している行を即座に識別します。 (行がそれ自体にリンクできることは除外されていません。)

これはprependsそれぞれの新しいアイテムのパスなので、path[1]追加の列の代わりに。私の知る限り、appending要素と同じくらい高速です。
配列の最初の要素へのアクセスは、追加の列とほぼ同じくらい安くなるはずです。これにより、行が広くなります。パフォーマンスが重要かどうかをテストして比較します。

WITH RECURSIVE graph AS (
   SELECT col1 AS id, depend AS col1  -- simplify join condition
        , ARRAY[col1, depend] AS path
        , (col1 = depend) AS cycle    -- simplify if short-circuit impossible
   FROM   dep

   UNION ALL
   SELECT d.col1, d.depend
        , path || d.depend, d.depend = ANY(path)
   FROM   graph g
   JOIN   dep   d USING (col1)
   WHERE  NOT g.cycle
   )
SELECT DISTINCT id
FROM   graph
WHERE  cycle;
2