Given a table of source-to-destination paths, each of whose nodes references a row in a nodes table, how do we find the shortest path from one node to another?
One answer is Dijkstra's algorithm (http://en.wikipedia.org/wiki/Dijkstra's_algorithm). Peter Larsson has posted a SQL Server implementation of it on the SQL Team Forum. Here is a MySQL implementation. The DDL: DROP TABLE IF EXISTS dijnodes,dijpaths; CREATE TABLE dijnodes ( nodeID int PRIMARY KEY AUTO_INCREMENT NOT NULL, nodename varchar (20) NOT NULL, cost int NULL, pathID int NULL, calculated tinyint NOT NULL ); CREATE TABLE dijpaths ( pathID int PRIMARY KEY AUTO_INCREMENT, fromNodeID int NOT NULL , toNodeID int NOT NULL , cost int NOT NULL );Here is a stored procedure to populate valid nodes and paths: DROP PROCEDURE IF EXISTS dijAddPath; DELIMITER | CREATE PROCEDURE dijAddPath( pFromNodeName VARCHAR(20), pToNodeName VARCHAR(20), pCost INT ) BEGIN DECLARE vFromNodeID, vToNodeID, vPathID INT; SET vFromNodeID = ( SELECT NodeID FROM dijnodes WHERE NodeName = pFromNodeName ); IF vFromNodeID IS NULL THEN BEGIN INSERT INTO dijnodes (NodeName,Calculated) VALUES (pFromNodeName,0); SET vFromNodeID = LAST_INSERT_ID(); END; END IF; SET vToNodeID = ( SELECT NodeID FROM dijnodes WHERE NodeName = pToNodeName ); IF vToNodeID IS NULL THEN BEGIN INSERT INTO dijnodes(NodeName, Calculated) VALUES(pToNodeName,0); SET vToNodeID = LAST_INSERT_ID(); END; END IF; SET vPathID = ( SELECT PathID FROM dijpaths WHERE FromNodeID = vFromNodeID AND ToNodeID = vToNodeID ); IF vPathID IS NULL THEN INSERT INTO dijpaths(FromNodeID,ToNodeID,Cost) VALUES(vFromNodeID,vToNodeID,pCost); ELSE UPDATE dijpaths SET Cost = pCost WHERE FromNodeID = vFromNodeID AND ToNodeID = vToNodeID; END IF; END; | DELIMITER ;Use dijAddpath() to populate the tables: call dijaddpath( 'a', 'b', 4 ); call dijaddpath( 'a', 'd', 1 ); call dijaddpath( 'b', 'a', 74 ); call dijaddpath( 'b', 'c', 2 ); call dijaddpath( 'b', 'e', 12 ); call dijaddpath( 'c', 'b', 12 ); call dijaddpath( 'c', 'f', 74 ); call dijaddpath( 'c', 'j', 12 ); call dijaddpath( 'd', 'e', 32 ); call dijaddpath( 'd', 'g', 22 ); call dijaddpath( 'e', 'd', 66 ); call dijaddpath( 'e', 'f', 76 ); call dijaddpath( 'e', 'h', 33 ); call dijaddpath( 'f', 'i', 11 ); call dijaddpath( 'f', 'j', 21 ); call dijaddpath( 'g', 'd', 12 ); call dijaddpath( 'g', 'h', 10 ); call dijaddpath( 'h', 'g', 2 ); call dijaddpath( 'h', 'i', 72 ); call dijaddpath( 'i', 'f', 31 ); call dijaddpath( 'i', 'j', 7 ); call dijaddpath( 'i', 'h', 18 ); call dijaddpath( 'j', 'f', 8 ); SELECT * FROM dijnodes; +--------+----------+------+--------+------------+ | nodeID | nodename | cost | pathID | calculated | +--------+----------+------+--------+------------+ | 1 | a | NULL | NULL | 0 | | 2 | b | NULL | NULL | 0 | | 3 | d | NULL | NULL | 0 | | 4 | c | NULL | NULL | 0 | | 5 | e | NULL | NULL | 0 | | 6 | f | NULL | NULL | 0 | | 7 | j | NULL | NULL | 0 | | 8 | g | NULL | NULL | 0 | | 9 | h | NULL | NULL | 0 | | 10 | i | NULL | NULL | 0 | +--------+----------+------+--------+------------+ SELECT * FROM dijpaths; +--------+------------+----------+------+ | pathID | fromNodeID | toNodeID | cost | +--------+------------+----------+------+ | 1 | 1 | 2 | 4 | | 2 | 1 | 3 | 1 | | 3 | 2 | 1 | 74 | | 4 | 2 | 4 | 2 | | 5 | 2 | 5 | 12 | | 6 | 4 | 2 | 12 | | 7 | 4 | 6 | 74 | | 8 | 4 | 7 | 12 | | 9 | 3 | 5 | 32 | | 10 | 3 | 8 | 22 | | 11 | 5 | 3 | 66 | | 12 | 5 | 6 | 76 | | 13 | 5 | 9 | 33 | | 14 | 6 | 10 | 11 | | 15 | 6 | 7 | 21 | | 16 | 8 | 3 | 12 | | 17 | 8 | 9 | 10 | | 18 | 9 | 8 | 2 | | 19 | 9 | 10 | 72 | | 20 | 10 | 6 | 31 | | 21 | 10 | 7 | 7 | | 22 | 10 | 9 | 18 | | 23 | 7 | 6 | 8 | +--------+------------+----------+------+The stored procedure is a 6-step:
DROP PROCEDURE IF EXISTS dijResolve; DELIMITER | CREATE PROCEDURE dijResolve( pFromNodeName VARCHAR(20), pToNodeName VARCHAR(20) ) BEGIN DECLARE vFromNodeID, vToNodeID, vNodeID, vCost, vPathID INT; DECLARE vFromNodeName, vToNodeName VARCHAR(20); -- null out path info in the nodes table UPDATE dijnodes SET PathID = NULL,Cost = NULL,Calculated = 0; -- find nodeIDs referenced by input params SET vFromNodeID = ( SELECT NodeID FROM dijnodes WHERE NodeName = pFromNodeName ); IF vFromNodeID IS NULL THEN SELECT CONCAT('From node name ', pFromNodeName, ' not found.' ); ELSE BEGIN -- start at src node SET vNodeID = vFromNodeID; SET vToNodeID = ( SELECT NodeID FROM dijnodes WHERE NodeName = pToNodeName ); IF vToNodeID IS NULL THEN SELECT CONCAT('From node name ', pToNodeName, ' not found.' ); ELSE BEGIN -- calculate path costs till all are done UPDATE dijnodes SET Cost=0 WHERE NodeID = vFromNodeID; WHILE vNodeID IS NOT NULL DO BEGIN UPDATE dijnodes AS src JOIN dijpaths AS paths ON paths.FromNodeID = src.NodeID JOIN dijnodes AS dest ON dest.NodeID = Paths.ToNodeID SET dest.Cost = CASE WHEN dest.Cost IS NULL THEN src.Cost + Paths.Cost WHEN src.Cost + Paths.Cost < dest.Cost THEN src.Cost + Paths.Cost ELSE dest.Cost END, dest.PathID = Paths.PathID WHERE src.NodeID = vNodeID AND (dest.Cost IS NULL OR src.Cost + Paths.Cost < dest.Cost) AND dest.Calculated = 0; UPDATE dijnodes SET Calculated = 1 WHERE NodeID = vNodeID; SET vNodeID = ( SELECT nodeID FROM dijnodes WHERE Calculated = 0 AND Cost IS NOT NULL ORDER BY Cost LIMIT 1 ); END; END WHILE; END; END IF; END; END IF; IF EXISTS( SELECT 1 FROM dijnodes WHERE NodeID = vToNodeID AND Cost IS NULL ) THEN -- problem, cannot proceed SELECT CONCAT( 'Node ',vNodeID, ' missed.' ); ELSE BEGIN -- write itinerary to map table DROP TEMPORARY TABLE IF EXISTS map; CREATE TEMPORARY TABLE map ( RowID INT PRIMARY KEY AUTO_INCREMENT, FromNodeName VARCHAR(20), ToNodeName VARCHAR(20), Cost INT ) ENGINE=MEMORY; WHILE vFromNodeID <> vToNodeID DO BEGIN SELECT src.NodeName,dest.NodeName,dest.Cost,dest.PathID INTO vFromNodeName, vToNodeName, vCost, vPathID FROM dijnodes AS dest JOIN dijpaths AS Paths ON Paths.PathID = dest.PathID JOIN dijnodes AS src ON src.NodeID = Paths.FromNodeID WHERE dest.NodeID = vToNodeID; INSERT INTO Map(FromNodeName,ToNodeName,Cost) VALUES(vFromNodeName,vToNodeName,vCost); SET vToNodeID = (SELECT FromNodeID FROM dijPaths WHERE PathID = vPathID); END; END WHILE; SELECT FromNodeName,ToNodeName,Cost FROM Map ORDER BY RowID DESC; DROP TEMPORARY TABLE Map; END; END IF; END; | DELIMITER ; CALL dijResolve( 'a','i'); +--------------+------------+------+ | FromNodeName | ToNodeName | Cost | +--------------+------------+------+ | a | b | 4 | | b | c | 6 | | c | j | 18 | | j | f | 26 | | f | i | 37 | +--------------+------------+------+ Last updated 26 Dec 2024 |
![]() |