Last active
October 14, 2025 15:43
-
-
Save JerryNixon/9de683bffb210ff7bd61ec4e1eeda41e to your computer and use it in GitHub Desktop.
This is a variant of Dijkstra's algorithm for SQL Graph, written in T-SQL.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| This script implements a two-hop variant of Dijkstra’s algorithm using SQL Server graph features. | |
| It creates a graph with 1500 nodes and edges, then finds the shortest two-edge paths | |
| between 5 source and 5 destination nodes. Execution time is measured using a stopwatch. | |
| */ | |
| SET NOCOUNT ON | |
| WHILE (@@TRANCOUNT > 0) ROLLBACK TRANSACTION | |
| BEGIN TRANSACTION | |
| EXEC STOPWATCH_RESET; | |
| DROP TABLE IF EXISTS test_location; | |
| CREATE TABLE test_location (CODE VARCHAR(50)) AS NODE; | |
| DROP TABLE IF EXISTS test_shipsto; | |
| CREATE TABLE test_shipsto | |
| ( | |
| COST MONEY | |
| , INDEX IDX_test_shipsto_FROMTO | |
| UNIQUE CLUSTERED ($from_id, $to_id) | |
| WITH (DATA_COMPRESSION = PAGE) | |
| ) AS EDGE; | |
| -- create the node | |
| WITH generator (ID, CODE) AS | |
| ( | |
| SELECT 1, NEWID() | |
| UNION ALL | |
| SELECT ID + 1, NEWID() | |
| FROM generator | |
| WHERE ID < 1500 | |
| ) | |
| INSERT INTO test_location | |
| SELECT CODE | |
| FROM generator | |
| OPTION (MAXRECURSION 0); | |
| -- create the edge | |
| WITH pairs (FROM_NODE, TO_NODE) AS | |
| ( | |
| SELECT | |
| FROMLOC.$node_id | |
| , TOLOC.$node_id | |
| FROM test_location AS FROMLOC | |
| CROSS JOIN test_location AS TOLOC | |
| WHERE FROMLOC.CODE != TOLOC.CODE | |
| ) | |
| INSERT INTO test_shipsto | |
| ($from_id, $to_id, COST) | |
| SELECT | |
| FROM_NODE | |
| , TO_NODE | |
| , ABS(CHECKSUM(NEWID())) % 14 | |
| FROM pairs; | |
| -- take the first 5 | |
| DECLARE @sources TABLE (CODE VARCHAR(1000)); | |
| INSERT INTO @sources | |
| SELECT CODE | |
| FROM test_location | |
| ORDER BY CODE OFFSET 0 ROWS FETCH FIRST 5 ROWS ONLY; | |
| -- take the second 5 | |
| DECLARE @destinations TABLE (CODE VARCHAR(1000)); | |
| INSERT INTO @destinations | |
| SELECT CODE | |
| FROM test_location | |
| ORDER BY CODE OFFSET 5 ROWS FETCH NEXT 5 ROWS ONLY; | |
| EXEC STOPWATCH_READ @message = 'After DDL & insert'; | |
| WITH two_edges (STARTNODE, ENDNODE, PATH, COST, RANK) AS | |
| ( | |
| SELECT L1.CODE, L3.CODE | |
| , CONCAT(L1.CODE, '->', L2.CODE, '->', L3.CODE) | |
| , S1.COST + S2.COST | |
| , ROW_NUMBER() OVER (PARTITION BY L1.CODE, L3.CODE ORDER BY S1.COST + S2.COST) | |
| FROM test_location AS L1 | |
| , test_shipsto AS S1 | |
| , test_location AS L2 | |
| , test_shipsto AS S2 | |
| , test_location AS L3 | |
| WHERE MATCH(L1-(S1)->L2-(S2)->L3) | |
| AND L1.CODE != L2.CODE | |
| AND L1.CODE != L3.CODE | |
| AND L2.CODE != L3.CODE | |
| AND L1.CODE IN (SELECT S.CODE FROM @sources AS S) | |
| AND L3.CODE IN (SELECT D.CODE FROM @destinations AS D) | |
| ) | |
| SELECT * | |
| FROM two_edges | |
| WHERE RANK = 1 | |
| ------------------------------------------------- | |
| EXEC STOPWATCH_READ @message = 'After Version 1'; | |
| GO | |
| /* | |
| CREATE OR ALTER PROC STOPWATCH_RESET | |
| @print BIT = 1 | |
| , @message NVARCHAR(1000) = NULL | |
| AS | |
| DECLARE @starttime VARCHAR(50) = SYSDATETIME(); | |
| EXEC sp_set_session_context N'start_time', @starttime; | |
| IF (@print = 1) | |
| BEGIN | |
| SET @message = CONCAT('Start: ', @starttime, ' ', @message); | |
| RAISERROR (@message, 0, 1) WITH NOWAIT; | |
| END | |
| GO | |
| CREATE OR ALTER PROC STOPWATCH_READ | |
| @reset BIT = 1 | |
| , @message NVARCHAR(1000) = NULL | |
| AS | |
| DECLARE @starttime DATETIME2(7) = CONVERT(DATETIME2(7), SESSION_CONTEXT(N'start_time')) | |
| DECLARE @emptydate DATETIME2 = CAST('1900-01-01 00:00:00.0000000' as datetime2); | |
| SET @message = CONCAT('Delta: ', CONVERT(time, DATEADD(ms, DATEDIFF(ms, @starttime, SYSDATETIME()), @emptydate)), ' ', @message) | |
| RAISERROR (@message, 0, 1) WITH NOWAIT; | |
| IF (@reset = 1) | |
| EXEC STOPWATCH_RESET 0; | |
| GO | |
| CREATE OR ALTER PROCEDURE GENERATEDATA | |
| @count INT = 10 | |
| AS | |
| WITH data (ID, CODE) AS | |
| ( | |
| SELECT 1, NEWID() | |
| UNION ALL | |
| SELECT ID + 1, NEWID() | |
| FROM DATA | |
| WHERE ID < @count | |
| ) | |
| SELECT CODE FROM data | |
| OPTION (MAXRECURSION 0); | |
| */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| Finds the shortest two-hop paths between 5 source and 5 destination nodes in a graph. | |
| Uses SQL Server graph features with MATCH clause. | |
| */ | |
| SET NOCOUNT ON; | |
| BEGIN TRY | |
| WHILE (@@TRANCOUNT > 0) ROLLBACK TRANSACTION; | |
| BEGIN TRANSACTION; | |
| EXEC STOPWATCH_RESET; | |
| -- Create node table | |
| DROP TABLE IF EXISTS Locations; | |
| CREATE TABLE Locations (LocationID VARCHAR(50)) AS NODE; | |
| -- Create edge table with clustered index | |
| DROP TABLE IF EXISTS ShippingRoutes; | |
| CREATE TABLE ShippingRoutes | |
| ( | |
| COST MONEY, | |
| INDEX IDX_ShippingRoutes_FromTo UNIQUE CLUSTERED ($from_id, $to_id) | |
| WITH (DATA_COMPRESSION = PAGE) | |
| ) AS EDGE; | |
| -- Insert 1500 nodes | |
| WITH generator (ID, LocationID) AS | |
| ( | |
| SELECT 1, NEWID() | |
| UNION ALL | |
| SELECT ID + 1, NEWID() | |
| FROM generator | |
| WHERE ID < 1500 | |
| ) | |
| INSERT INTO Locations (LocationID) | |
| SELECT LocationID | |
| FROM generator | |
| OPTION (MAXRECURSION 0); | |
| -- Insert edges (sparse: 10% of possible pairs) | |
| WITH pairs (FromNode, ToNode) AS | |
| ( | |
| SELECT | |
| L1.$node_id, | |
| L2.$node_id | |
| FROM Locations AS L1 | |
| CROSS JOIN Locations AS L2 | |
| WHERE L1.LocationID != L2.LocationID | |
| AND ABS(CHECKSUM(NEWID())) % 100 < 10 -- 10% of edges | |
| ) | |
| INSERT INTO ShippingRoutes ($from_id, $to_id, COST) | |
| SELECT | |
| FromNode, | |
| ToNode, | |
| RAND() * 100 -- More realistic cost range | |
| FROM pairs; | |
| -- Select 5 source nodes | |
| DECLARE @Sources TABLE (LocationID VARCHAR(50)); | |
| INSERT INTO @Sources | |
| SELECT LocationID | |
| FROM Locations | |
| ORDER BY LocationID | |
| OFFSET 0 ROWS FETCH FIRST 5 ROWS ONLY; | |
| -- Select 5 destination nodes | |
| DECLARE @Destinations TABLE (LocationID VARCHAR(50)); | |
| INSERT INTO @Destinations | |
| SELECT LocationID | |
| FROM Locations | |
| ORDER BY LocationID | |
| OFFSET 5 ROWS FETCH NEXT 5 ROWS ONLY; | |
| EXEC STOPWATCH_READ @message = 'After DDL and data insertion'; | |
| -- Find shortest two-hop paths | |
| WITH TwoHopPaths (StartNode, EndNode, Path, TotalCost, PathRank) AS | |
| ( | |
| SELECT | |
| L1.LocationID, | |
| L3.LocationID, | |
| CONCAT(L1.LocationID, '->', L2.LocationID, '->', L3.LocationID), | |
| S1.COST + S2.COST, | |
| ROW_NUMBER() OVER ( | |
| PARTITION BY L1.LocationID, L3.LocationID | |
| ORDER BY S1.COST + S2.COST | |
| ) | |
| FROM Locations AS L1 | |
| JOIN ShippingRoutes AS S1 ON MATCH(L1-(S1)->L2) | |
| JOIN Locations AS L2 ON MATCH(L2-(S2)->L3) | |
| JOIN ShippingRoutes AS S2 ON MATCH(L2-(S2)->L3) | |
| JOIN Locations AS L3 ON MATCH(L1-(S1)->L2-(S2)->L3) | |
| WHERE L1.LocationID != L2.LocationID | |
| AND L1.LocationID != L3.LocationID | |
| AND L2.LocationID != L3.LocationID | |
| AND EXISTS (SELECT 1 FROM @Sources S WHERE S.LocationID = L1.LocationID) | |
| AND EXISTS (SELECT 1 FROM @Destinations D WHERE D.LocationID = L3.LocationID) | |
| ) | |
| SELECT StartNode, EndNode, Path, TotalCost | |
| FROM TwoHopPaths | |
| WHERE PathRank = 1; | |
| EXEC STOPWATCH_READ @message = 'After shortest path query'; | |
| COMMIT TRANSACTION; | |
| END TRY | |
| BEGIN CATCH | |
| ROLLBACK TRANSACTION; | |
| THROW; | |
| END CATCH; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment