1

Let's say I've got the following table structure:

| ID | ParentID | Name |

I'd like to write a recursive PostgreSQL function for getting all child nodes of a node ID passed to it as a parameter.

Here's my code so far (I only have one part of the function which gets all the children of the passed ID, and now I need the recursive part):

CREATE OR REPLACE FUNCTION GetAllChildren(IN NodeID INTEGER) RETURNS INTEGER AS $$
DECLARE
    Crs CURSOR FOR SELECT ID, ParentID, Name FROM Tree WHERE ParentID=NodeID;
    VarRow Tree%ROWTYPE;
BEGIN
    OPEN Crs;

    CREATE TEMPORARY TABLE TBL(
        ID SERIAL,
        ParentID INTEGER,
        Name CHARACTER(100)
    );

    LOOP
        FETCH Crs INTO VarRow;
        IF VarRow IS NULL THEN
            EXIT;
        END IF;
        INSERT INTO TBL(ID, ParentID, Name) VALUES(VarRow.ID, VarRow.ParentID, VarRow.Name);
    END LOOP;

    CLOSE Crs;

    RETURN 0;
END;
$$ LANGUAGE plpgsql;

Perhaps the biggest problem is that I don't know where to save the output between the calls of the recursion.

If you haven't figured out so far, it's about the adjacency list, getting all the children of a node and printing them out to a table.

Does anyone have a solution?

Luka
  • 1,718
  • 3
  • 19
  • 29

2 Answers2

4

For information, there are common table expressions in Postgres, which will probably help here:

http://www.postgresql.org/docs/current/static/queries-with.html

Adapting the example from the docs:

WITH RECURSIVE rec_tree(parent_id, node_id, data, depth) AS (
        SELECT t.parent_id, t.node_id, t.data, 1
        FROM tree t
      UNION ALL
        SELECT t.parent_id, t.node_id, t.data, rt.depth + 1
        FROM tree t, rec_tree rt
        WHERE t.parent_id = rt.node_id
)
SELECT * FROM rec_tree;

(See the docs for an example that prevents cycles for a graph.)

Denis de Bernardy
  • 75,850
  • 13
  • 131
  • 154
4
  • PostgreSQL doesn't know local (procedure) limited temporary tables - your temp table is visible in all instances of called function, and it will be visible outside your function too - it has session visibility.

  • But PostgreSQL functions (PostgreSQL has no procedures) can returns table directly - so you don't need use auxiliary table for storing data

CREATE OR REPLACE FUNCTION children_by_parent(_parent_id int)
RETURNS SETOF children AS $$ -- children is table name
DECLARE r children;
BEGIN
  FOR r IN 
    SELECT * FROM children
       WHERE parent_id = _parent_id
  LOOP
    RETURN NEXT r; -- return node
    RETURN QUERY SELECT * FROM children_by_parent(r.id); -- return children
  END LOOP;
  RETURN;
END;
$$ LANGUAGE plpgsql STRICT;

This form is faster, because you don't fill any table (although temp table is usually in RAM only).

You don't need use a explicit cursor in PostgreSQL - statement FOR does all, it is shorter and more user friendly.

  • the best solution is Denis idea - use CTE - recursive SQL.
Pavel Stehule
  • 42,331
  • 5
  • 91
  • 94