#sql #postgresql
#sql #postgresql
Вопрос:
Введение в проблему
У меня есть список ребер графа с fromNodes, toNodes и свойствами ребра (EdgeType и edgeLength) в таблице SQL.
Моя цель — найти все желтые ребра, а затем построить пути по графу, состоящему из всех соединенных синих ребер (начиная с каждого желтого ребра).
Я реализовал следующее для этого обсуждения:
ШАГ 1 Итак, сначала я хочу найти все желтые края, это легко:
WITH
YellowEdges AS
(SELECT EdgeId, FromNodeID, ToNodeID,EdgeType,EdgeLength,array_append(GraphNetwork.EDGES,GraphNetwork.EdgeId) AS EdgePathArray
FROM GraphNetwork
WHERE EdgeType = 3
)
SELECT *
FROM YellowEdge
Теперь у нас есть все желтые ребра 1,4,6,9,15
ШАГ 2 Найдите все синие края, хорошо, никаких проблем
WITH
YellowEdges AS
(SELECT EdgeId, FromNodeID, ToNodeID,EdgeType,EdgeLength,array_append(GraphNetwork.EDGES,GraphNetwork.EdgeId) AS EdgePathArray
FROM GraphNetwork
WHERE EdgeType = 3
),
BlueEdges AS
(SELECT *
FROM GraphNetwork
WHERE EdgeType = 2
)
SELECT *
FROM BlueEdges
ШАГ 3 Найдите все синие ребра, которые ведут к желтым ребрам, и выполните соединение по левому краю, добавив как желтые, так и синие ребра в столбец массивов, называемый edge path array
WITH
YellowEdges AS
(SELECT EdgeId, FromNodeID, ToNodeID,EdgeType,EdgeLength,array_append(GraphNetwork.EDGES,GraphNetwork.EdgeId) AS EdgePathArray
FROM GraphNetwork
WHERE EdgeType = 3
),
BlueEdges AS
(SELECT *
FROM GraphNetwork
WHERE EdgeType = 2
),
EdgePathTable AS
(SELECT *
FROM (
SELECT BlueEdges.EdgeId, BlueEdges.FromNodeID, BlueEdges.ToNodeID,BlueEdges.EdgeType,BlueEdges.EdgeLength,array_append(YellowEdges.EdgePathArray,BlueEdges.EdgeId) AS EdgePathArray
FROM YellowEdges
LEFT JOIN BlueEdges
ON YellowEdges.EdgeId <> BlueEdges.EdgeId
AND YellowEdges.FromNodeID = BlueEdges.ToNodeID
) AS unnamedTable
)
SELECT *
FROM EdgePathTable
Шаг 4 Теперь найдите все синие ребра, которые соединяются с синими ребрами, которые мы определили ранее, и добавьте их к контуру (убедитесь, что новые ребра ранее не были пройдены как часть этого пути (чтобы избежать круговых путей)
WITH
YellowEdges AS
(SELECT EdgeId, FromNodeID, ToNodeID,EdgeType,EdgeLength,array_append(GraphNetwork.EDGES,GraphNetwork.EdgeId) AS EdgePathArray
FROM GraphNetwork
WHERE EdgeType = 3
),
BlueEdges AS
(SELECT *
FROM GraphNetwork
WHERE EdgeType = 2
),
EdgePathTable AS
(SELECT *
FROM (
SELECT BlueEdges.EdgeId, BlueEdges.FromNodeID, BlueEdges.ToNodeID,BlueEdges.EdgeType,BlueEdges.EdgeLength,array_append(YellowEdges.EdgePathArray,BlueEdges.EdgeId) AS EdgePathArray
FROM YellowEdges
LEFT JOIN BlueEdges
ON YellowEdges.EdgeId <> BlueEdges.EdgeId
AND YellowEdges.FromNodeID = BlueEdges.ToNodeID
) AS unnamedTable
)
SELECT *
FROM (SELECT EdgePathTable_iteration2.EdgeId, EdgePathTable_iteration2.FromNodeID, EdgePathTable_iteration2.ToNodeID,EdgePathTable_iteration2.EdgeType,EdgePathTable_iteration2.EdgeLength,array_append(EdgePathTable.EdgePathArray,EdgePathTable_iteration2.EdgeId) AS EdgePathArray
FROM EdgePathTable
LEFT JOIN BlueEdges AS EdgePathTable_iteration2
ON EdgePathTable_iteration2.EdgeId <> any(EdgePathTable.EdgePathArray)
AND EdgePathTable.FromNodeID = EdgePathTable_iteration2.ToNodeID) AS EdgePathTable
ШАГ 5 Повторяйте шаг 4, пока не будет выполнено какое-либо условие i (e выполните x итераций, или вы достигли конца каждого пути).
Хорошо, вот где я застрял. Я рассмотрел несколько потенциальных вариантов: рекурсивный CTE, рекурсивный вызов функции, вызов функции в цикле while или, может быть, просто повторение этого запроса в цикле while.
Я не уверен, какой из них, если таковые имеются, позволит мне делать то, что я хочу:
- Рекурсивный CTE, в котором я не уверен, будет работать, поскольку он основан на объединении, и, похоже, его невозможно реализовать с помощью left join. Я нашел несколько примеров обхода дерева, но в каждом случае он был ограничен началом с одного ребра, я предполагаю, что это ограничение этого метода, поскольку объединение увеличивает количество строк, а не количество столбцов. Мне нужен метод, который позволяет мне пересекать несколько ребер, как то, что я начал делать здесь. Когда я в конечном итоге заработаю, я перенесу это в нашу фактическую базу данных, которая, вероятно, имеет 100 000-1 000 000 таких ребер
- Функциональные параметры вызова кажутся сложными, потому что кажется, что вы не можете передать таблицу функции (но вы можете передать табличную вещь или что-то в этом роде, но я все еще не смог этого понять)
- Мне нравится идея просто повторить этот запрос в цикле while, но я не смог найти понятную документацию о том, как выполнить цикл while в запросе PostgreSQL. Я не смог реализовать даже базовый цикл while в запросе на SQL fiddle.
Вот ссылка на скрипку к примеру до шага 4:
http://sqlfiddle.com /#!17/2a341d/1
Или вы можете найти пример кода ниже. Я немного застрял на этом и был бы очень признателен за помощь.
CREATE TABLE GraphNetwork
(
EdgeId INT primary key,
FromNodeID INT,
ToNodeID INT,
EdgeType INT,
EdgeLength INT,
EDGES INT[]
);
INSERT INTO GraphNetwork (EdgeId, FromNodeID, ToNodeID, EdgeType, EdgeLength)
VALUES
(1,2,1,3,10),
(2,3,2,2,50),
(3,4,3,2,40),
(4,5,4,3,15),
(5,5,16,2,60),
(6,4,5,3,20),
(7,3,4,2,80),
(8,2,3,2,25),
(9,7,6,3,5),
(10,8,7,2,20),
(11,9,8,2,35),
(12,7,9,2,10),
(13,10,9,2,10),
(14,11,10,1,15),
(15,13,12,3,25),
(16,14,13,2,25),
(17,15,14,1,30)
;
WITH YellowEdges AS
(
SELECT
EdgeId, FromNodeID, ToNodeID, EdgeType, EdgeLength,
array_append(GraphNetwork.EDGES, GraphNetwork.EdgeId) AS EdgePathArray
FROM
GraphNetwork
WHERE
EdgeType = 3
),
BlueEdges AS
(
SELECT *
FROM GraphNetwork
WHERE EdgeType = 2
),
EdgePathTable AS
(
SELECT *
FROM
(SELECT
BlueEdges.EdgeId, BlueEdges.FromNodeID, BlueEdges.ToNodeID,
BlueEdges.EdgeType, BlueEdges.EdgeLength,
array_append(YellowEdges.EdgePathArray, BlueEdges.EdgeId) AS EdgePathArray
FROM
YellowEdges
LEFT JOIN
BlueEdges ON YellowEdges.EdgeId <> BlueEdges.EdgeId
AND YellowEdges.FromNodeID = BlueEdges.ToNodeID
) AS unnamedTable
)
SELECT *
FROM
(SELECT
EdgePathTable_iteration2.EdgeId,
EdgePathTable_iteration2.FromNodeID,
EdgePathTable_iteration2.ToNodeID,
EdgePathTable_iteration2.EdgeType,
EdgePathTable_iteration2.EdgeLength,
array_append(EdgePathTable.EdgePathArray, EdgePathTable_iteration2.EdgeId) AS EdgePathArray
FROM
EdgePathTable
LEFT JOIN
BlueEdges AS EdgePathTable_iteration2 ON EdgePathTable_iteration2.EdgeId <> ANY(EdgePathTable.EdgePathArray)
AND EdgePathTable.FromNodeID = EdgePathTable_iteration2.ToNodeID) AS EdgePathTable
Ответ №1:
A recursive cte
должен предоставить вам ожидаемый результат.
- Нерекурсивный термин выделяет все желтые ребра.
- Рекурсивный термин итеративно добавляет синие края к желтым краям.
LEFT JOIN
не требуется в рекурсивном термине,INNER JOIN
это нормально, потому что рекурсивный термин добавляет несколько новых строк к существующим, исходящим из нерекурсивного термина и предыдущих итераций рекурсивного термина.- В
WHERE
предложении рекурсивного терминаNOT l.EdgePathArray @> array[g.EdgeID]
является обязательным, чтобы избежать бесконечных циклов.
recursive cte
Предоставляет слишком много строк из-за новых строк, создаваемых на каждой итерации. Самосоединение в рекурсивном результате cte позволяет выбирать только соответствующие строки, то есть строки, которые EdgePathArray
не являются составной частью EdgePathArray другой строки.
Вот код sql :
WITH RECURSIVE list (EdgeId, FromNodeID, ToNodeID, EdgeType, EdgeLength, EdgePathArray) AS
(
SELECT EdgeId, FromNodeID, ToNodeID, EdgeType, EdgeLength, array[EdgeId] AS EdgePathArray
FROM GraphNetwork
WHERE EdgeType = 3
UNION ALL
SELECT g.EdgeId, g.FromNodeID, g.ToNodeID, g.EdgeType, g.EdgeLength, l.EdgePathArray || g.EdgeId
FROM list AS l
INNER JOIN GraphNetwork AS g
ON g.ToNodeID = l.FromNodeID
WHERE g.EdgeType = 2
AND NOT l.EdgePathArray @> array[g.EdgeID]
)
SELECT c.*
FROM list AS c
LEFT JOIN list AS f
ON f.EdgePathArray @> c.EdgePathArray
AND f.EdgePathArray <> c.EdgePathArray
WHERE f.EdgePathArray IS NULL
ORDER BY c.EdgePathArray
Вот результат теста.
Все подробности о рекурсивном cte см. в руководстве.