SQL - postgres - shortest path in graph - recursion SQL - postgres - shortest path in graph - recursion postgresql postgresql

SQL - postgres - shortest path in graph - recursion


This works for me, but it's kinda ugly:

WITH RECURSIVE paths (n1, n2, distance) AS (    SELECT        nodes.n1,        nodes.n2,        1    FROM        nodes    WHERE        nodes.n1 <> nodes.n2    UNION ALL    SELECT        paths.n1,        nodes.n2,        paths.distance + 1    FROM        paths        JOIN nodes        ON            paths.n2 = nodes.n1    WHERE        nodes.n1 <> nodes.n2)SELECT   paths.n1,   paths.n2,   min(distance)FROM    pathsGROUP BY    1, 2UNIONSELECT    nodes.n1,    nodes.n2,    0FROM    nodesWHERE    nodes.n1 = nodes.n2

Also, I am not sure how good it will perform against larger datasets. As suggested by Mark Mann, you may want to use a graph library instead, e.g. pygraph.

EDIT: here's a sample with pygraph

from pygraph.algorithms.minmax import shortest_pathfrom pygraph.classes.digraph import digraphg = digraph()g.add_node('a')g.add_node('b')g.add_node('c')g.add_node('d')g.add_node('e')g.add_edge(('a', 'a'))g.add_edge(('a', 'b'))g.add_edge(('a', 'c'))g.add_edge(('b', 'b'))g.add_edge(('b', 'd'))g.add_edge(('b', 'c'))g.add_edge(('d', 'e'))for source in g.nodes():    tree, distances = shortest_path(g, source)    for target, distance in distances.iteritems():        if distance == 0 and not g.has_edge((source, target)):            continue        print source, target, distance

Excluding the graph building time, this takes 0.3ms while the SQL version takes 0.5ms.


Expanding on Mark's answer, there are some very reasonable approaches to explore a graph in SQL as well. In fact, they'll be faster than the dedicated libraries in perl or python, in that DB indexes will spare you the need to explore the graph.

The most efficient of index (if the graph is not constantly changing) is a nested-tree variation called the GRIPP index. (The linked paper mentions other approaches.)

If your graph is constantly changing, you might want to adapt the nested intervals approach to graphs, in a similar manner that GRIPP extends nested sets, or to simply use floats instead of integers (don't forget to normalize them by casting to numeric and back to float if you do).


Rather than computing these values on the fly, why not create a real table with all interesting pairs along with the shortest path value. Then whenever data is inserted, deleted or updated in your data table, you can recalculate all of the shortest path information. (Perl's Graph module is particularly well-suited to this task, and Perl's DBI interface makes the code straightforward.)

By using an external process, you can also limit the number of recalculations. Using PostgreSQL triggers would cause recalculations to occur on every insert, update and delete, but if you knew you were going to be adding twenty pairs of points, you could wait until your inserts were completed before doing the calculations.