Shortest-Path-Algorithm-for-Material-Transportation / misc / k_shortest_path.sql
k_shortest_path.sql
Raw

DECLARE @SourceNode NVARCHAR(MAX); --set SOURCE, using nvarchar to keep unicode and avoid conversion costs
DECLARE @TargetNode NVARCHAR(MAX); -- set TARGET
DECLARE @K INT = 5; -- set number of shortest paths to find

-- table to store the K shortest paths
CREATE TABLE ShortestPaths (
    Path NVARCHAR(MAX),
    TotalWeight INT
);

-- table to store potential paths for yens invesitgation
CREATE TABLE #PotentialPaths ( --could be lots of potential paths, for performance using a temp table  
    Path NVARCHAR(MAX),
    TotalWeight INT
);



-- initialize with the shortest path from X to Y using Dijkstra
INSERT INTO #PotentialPaths (Path, TotalWeight)
SELECT 'SOURCE -> TARGET' AS Path, --DEBUG 
       CASE WHEN SourceNode = @SourceNode THEN 0 ELSE 1 END AS TotalWeight --testing here
FROM Graph
WHERE SourceNode = @SourceNode
AND TargetNode = @TargetNode;

-- YENS
DECLARE @CurrentPathCount INT = 0;

WHILE @CurrentPathCount < @K AND (SELECT COUNT(*) FROM #PotentialPaths) > 0
BEGIN
    -- get the candidate path with the smallest total weight
    DECLARE @CandidatePath NVARCHAR(MAX);
    DECLARE @CandidateWeight INT;

    SELECT TOP 1 @CandidatePath = Path, @CandidateWeight = TotalWeight
    FROM #PotentialPaths
    ORDER BY TotalWeight;

    -- remove the candidate path from the temporary table
    DELETE FROM #PotentialPaths WHERE Path = @CandidatePath;

    -- add the candidate path to the list of shortest paths
    INSERT INTO ShortestPaths (Path, TotalWeight)
    VALUES (@CandidatePath, @CandidateWeight);

    -- calculate the node where the candidate path deviates
    DECLARE @DeviationNode NVARCHAR(MAX);

    SET @DeviationNode = dbo.FindDeviationNode(@CandidatePath, @ShortestPaths);

    DECLARE @AlternatePaths TABLE (
        Path NVARCHAR(MAX),
        TotalWeight INT
    );

    -- generate potential alternate paths by bypassing the deviation node
    INSERT INTO @AlternatePaths (Path, TotalWeight)
    SELECT Path, TotalWeight
    FROM GenerateAlternatePaths(@CandidatePath, @DeviationNode, @SourceNode, @TargetNode);

    -- add the alternate paths to the temporary table
    INSERT INTO #PotentialPaths (Path, TotalWeight)
    SELECT Path, TotalWeight
    FROM @AlternatePaths;

    SET @CurrentPathCount = @CurrentPathCount + 1;
END;

-- get k paths from result, needs some improvement?
SELECT TOP (@K) * FROM ShortestPaths;



-- finding the point of difference between the current shortest paths and the potential next path
GO
CREATE FUNCTION FindDeviationNode(@CandidatePath NVARCHAR(MAX), @ShortestPaths NVARCHAR(MAX))
RETURNS NVARCHAR(MAX)
AS
BEGIN
    DECLARE @CandidatePathNodes TABLE (Node NVARCHAR(MAX));
    DECLARE @ShortestPathNodes TABLE (Node NVARCHAR(MAX));

    -- split the candidate path and shortest path into nodes
    INSERT INTO @CandidatePathNodes
    SELECT value
    FROM STRING_SPLIT(@CandidatePath, ' -> ');  --testing string here

    INSERT INTO @ShortestPathNodes
    SELECT value
    FROM STRING_SPLIT(@ShortestPaths, ' -> ');

    -- look for first node of deviation between candidate and shortest path
    DECLARE @DeviationNode NVARCHAR(MAX);

    SELECT TOP 1 @DeviationNode = C.Node
    FROM @CandidatePathNodes C
    LEFT JOIN @ShortestPathNodes S ON C.Node = S.Node
    WHERE S.Node IS NULL;

    RETURN @DeviationNode;
END;


GO
CREATE FUNCTION GenerateAlternatePaths(@CandidatePath NVARCHAR(MAX), @DeviationNode NVARCHAR(MAX), @SourceNode NVARCHAR(MAX), @TargetNode NVARCHAR(MAX))
RETURNS TABLE
AS
RETURN
(
    SELECT
        --combining NewPath as a bridge between source and target
        CONCAT(@SourceNode, ' -> ', NewPath, ' -> ', @TargetNode) AS Path, --testing the ammendment here
        TotalWeight
    FROM
    (
        SELECT
        --target of first
            TargetNode AS NewPath,
            CASE
                WHEN TargetNode = @DeviationNode THEN Weight1
                ELSE Weight1 + (SELECT TotalWeight FROM #PotentialPaths WHERE Path = @CandidatePath)
            END AS TotalWeight
        FROM
        (
            SELECT
                G1.TargetNode,
                G1.Weight AS Weight1
            FROM
                Graph G1 --using graph need all edges
            WHERE
                G1.SourceNode = @SourceNode
                AND G1.TargetNode <> @DeviationNode
                --checking the path isnt already in use, some additional investigation around converter/diverter?
                AND NOT EXISTS (SELECT 1 FROM #PotentialPaths P1 WHERE P1.Path = CONCAT(@SourceNode, ' -> ', G1.TargetNode, ' -> ', @TargetNode)) --checking p1
        ) AS T1
    ) AS T2
);