次の問題があります: ソース ノード ( node_s ) からターゲット ノード ( node_t ) へのすべての可能なパスを検出しようとしています。
グラフ エッジを含む元のテーブルの形式は単純です。ノード_x | ノード_y | 強度 | ここで、"node_x" -> "node_y" は、エッジの強度が "重み" である直接エッジです。
パスの探索中の任意の時点で、その子の中のノードがターゲットnode_tを持っていることを発見した場合、このパスを記録し、このノードからのパスの探索を停止します。それ以外の場合は、探索を続行します。
簡単な解決策は、グラフの推移閉包を構築する PostgreSQL の再帰 CTE を使用することでした。
WITH RECURSIVE transitive_closure (source, target, weight, path_string) AS
(
--non-recurive term
SELECT b.node_x, b.node_y, b.strength, b.node_x || '.' || b.node_y || '.' AS path_string
FROM best_path b
WHERE b.node_x = node_s --source_node
UNION
--recurive term
SELECT tc.source, b.node_y, least(tc.weight,b.strength), tc.path_string || b.node_y || '.' AS path_string
FROM best_path AS b JOIN transitive_closure AS tc ON b.node_x = tc.target
WHERE tc.path_string NOT LIKE '%' || b.node_y || '.%'
)
SELECT *
FROM transitive_closure tc
WHERE tc.target = node_t --target_node
上記のコードは、ソース ノードnode_sから可能なすべてのパスを検出します。推移閉包の構築後にのみ、ソース ノードからターゲット ノードへの必要なパスの行を選択できます (最後の SELECT ステートメントを参照)。
例:
best_path テーブルには次のデータがあります。
node_x | node_y | strength
--------+--------+----------
1 | 2 | 1
1 | 3 | 1
2 | 4 | 1
2 | 5 | 1
3 | 6 | 1
3 | 7 | 1
4 | 8 | 1
4 | 9 | 1
5 | 10 | 1
5 | 11 | 1
クエリ:
ソース ノード = 1 からターゲット ノード = 4 へのパスを見つける
結果:
source | target | strength | path_string
--------+--------+----------+------------
1 | 2 | 1 | 1.2.
1 | 3 | 1 | 1.3.
1 | 4 | 1 | 1.2.4.
1 | 5 | 1 | 1.2.5.
1 | 6 | 1 | 1.3.6.
1 | 7 | 1 | 1.3.7.
1 | 8 | 1 | 1.2.4.8.
1 | 9 | 1 | 1.2.4.9.
1 | 10 | 1 | 1.2.5.10.
1 | 11 | 1 | 1.2.5.11.
これは私が必要とするものではありません。ノード 2 からノード 4 (ターゲット) への直接エッジが既にあるため、パス 1.2.5.、1.2.4.8.、1.2.4.9.、1.2.5.10.、1.2.5.11. は必要ありません。ノード 2 の場合、2 から 4 へのパスが検出された時点で停止する必要があります。
要約すると、ノードが既にターゲット ノードへの直接エッジを持っている場合、ノードのパスを発見したくありません。これは、CTE の再帰的な用語で、次のような条件が必要であることを意味します。疑似コードは次のとおりです。
WITH RECURSIVE transitive_closure (source, target, weight, path_string) AS
(
--non-recurive term (as before)
SELECT b.node_x, b.node_y, b.strength, b.node_x || '.' || b.node_y || '.' AS path_string
FROM best_path b
WHERE b.node_x = node_s --source_node
UNION
--recurive term
SELECT tc.source, b.node_y, least(tc.weight,b.strength), tc.path_string || b.node_y || '.' AS path_string
FROM best_path AS b JOIN transitive_closure AS tc ON b.node_x = tc.target
WHERE tc.path_string NOT LIKE '%' || b.node_y || '.%'
AND b.node_y = node_t
--will select only rows with direct edge to target
UNION (second union is not allowed in CTE)
SELECT those rows which do not have direct edge to target
AND which parents did not contribute to constructing the query above.
i.e. b.node_x = tc.target where there is no direct edge between b.node_x to node_t
)
ソース ノード = 1 からターゲット ノード = 4 へのパスを検索するクエリの結果として、次のようにしたいと考えています。
source | target | strength | path_string
--------+--------+----------+------------
1 | 2 | 1 | 1.2.
1 | 3 | 1 | 1.3.
1 | 4 | 1 | 1.2.4.
1 | 6 | 1 | 1.3.6.
1 | 7 | 1 | 1.3.7.
よろしくお願いします。
私はすでに多くの方法を試しました。たとえば、FROM/WHERE 句の条件、CTE を関数に渡そうとしましたが、成功しませんでした。
任意の提案をいただければ幸いです。
私は自分が望むものを達成する独自の再帰関数を持っていますが、膨大な量のデータでは非常に遅くなります。PostgreSQL の CTE は最適化されているようですので、もう少し掘り下げてみたいと思います。