TIL: WITH RECURSIVE
いくつか並列実行する部分区間をもつ E2E のレイテンシを分析する際、実行フローのクリティカルパスを特定したい。レイテンシのデータは field trace から取り出す。手元での計測ではなく、世の中(社内ベータテスト人口)の遅いケースではどんなクリティカルパスが支配的なのか。
・・・というような分析をするにあたり、データを Python に持っていくのではなく、できれば SQL だけで済ませたい。そこで SQL で shortest path を計算できないかチャットに聞いたら 「WITH RECURSIVE を使え」" という返事が来た。(実際は Claude でなく社内のチャットに聞いた。だいたい同じ返答だった。)
なんだそれ・・・ということで実際に試してみる。 (動かしてみたいひとはこの notebook をどうぞ):
WITH RECURSIVE paths AS (
SELECT
weight AS distance,
dst AS tail,
src || '.' || dst AS name
FROM edges
WHERE src = 'A' UNION ALL
SELECT
paths.distance + edges.weight AS distance,
edges.dst AS tail,
paths.name || '.' || edges.dst AS name
FROM paths
INNER JOIN edges ON edges.src = paths.tail
)
SELECT distance, name
FROM paths
WHERE tail = 'I'
ORDER BY distance
ここで "UNION ALL" を使うのは、わかるようなわからないようなかんじ。パーサは UNION ALL を使ったこの構造を特別に検知して再起(ループ)実行を plan するらしい。SQL なのに限りなく手続き的。
このアプローチはすべての経路を列挙するので効率的とはいえない。ダイクストラとかできないかな・・・とおもったが自分の SQL 力ではわからなかった。ただ自分の扱うフローは小さなグラフなので、このくらいの非効率は問題なさそう。
なおチャットはダイクストラもスルッと書いてよこしたが、正しいのかいまいちよくわからなかった・・・
-- Written by Claude.
-- Not sure whether this is correct or a slop.
WITH RECURSIVE Dijkstra AS (
-- Base case: starting node with cost 0
SELECT
source_node AS node,
0 AS cost,
ARRAY[source_node] AS path
FROM (SELECT 'A' AS source_node) s -- Replace with your start node
UNION ALL
-- For each iteration, select the lowest-cost unvisited node
SELECT
e.destination_node,
d.cost + e.edge_weight,
d.path || e.destination_node
FROM Dijkstra d
JOIN graph_edges e ON d.node = e.source_node
WHERE NOT e.destination_node = ANY(d.path)
-- This is critical - only process the lowest-cost node in each iteration
AND NOT EXISTS (
SELECT 1 FROM Dijkstra d2
WHERE d2.cost < d.cost AND NOT EXISTS (
SELECT 1 FROM Dijkstra d3
WHERE d3.node = d2.node
)
)
)
SELECT node, path, cost
FROM Dijkstra
WHERE node = 'Z' -- Replace with your destination node
ORDER BY cost
LIMIT 1;