views:

59

answers:

1

Hi,

I need help with a recursive query. Assuming the following table:

CREATE TEMPORARY TABLE tree (
    id        integer PRIMARY KEY,
    parent_id integer NOT NULL,
    name      varchar(50)
);    

INSERT INTO tree (id, parent_id, name) VALUES (3, 0, 'Peter'), (2,0, 'Thomas'), (5,2, 'David'), (1, 0, 'Rob'), (8, 0, 'Brian');

I can retrieve a list of all people and their children with the following query:

WITH RECURSIVE recursetree(id, parent_id) AS (
    SELECT id, parent_id FROM tree WHERE parent_id = 0
  UNION
    SELECT t.id, t.parent_id
    FROM tree t
    JOIN recursetree rt ON rt.id = t.parent_id
  )
SELECT * FROM recursetree;

How can I list them in order, and also sort the first level items by name? For example, the desired output would be:

id, parent_id, name    
8, 0, "Brian"
3, 0, "Peter"
1, 0; "Rob"
2, 0, "Thomas"
5, 2, "  David"

Thanks,

*EDIT. Please note that adding an ORDER BY won't work: *

WITH RECURSIVE recursetree(id, parent_id, path, name) AS (
    SELECT 
        id, 
        parent_id, 
        array[id] AS path, 
        name 
    FROM tree WHERE parent_id = 0
  UNION ALL
    SELECT t.id, t.parent_id, rt.path || t.id, t.name
    FROM tree t
    JOIN recursetree rt ON rt.id = t.parent_id
  )
SELECT * FROM recursetree ORDER BY path;

The above will retain the parent child relationship (children follow their parents), but applying any other ORDER BY clause (ie: name - like some have suggested) will cause the result to lose it's parent-child relationships.

+2  A: 

See also this (translated) article about CTE's in PostgreSQL: wiki.phpfreakz.nl

Edit: Try this one, using an array:

WITH RECURSIVE recursetree(id, parent_ids, firstname) AS (
    SELECT id, NULL::int[] || parent_id, name FROM tree WHERE parent_id = 0
  UNION ALL
    SELECT 
    t.id, 
    rt.parent_ids || t.parent_id, 
    name
    FROM tree t
    JOIN recursetree rt ON rt.id = t.parent_id
  )
SELECT * FROM recursetree ORDER BY parent_ids;
Frank Heikens
Thanks for the link. However, the crux of the problem remains - I have to sort by path to retain ordering, but I need to sort top-level items by a field (in this case name).
robdog
Just use an array in the CTE, a child will always have more elements in the array as it's parents, an ASCending order will do the trick.
Frank Heikens
probably "ORDER BY parent_ids, firstname" to order the top-level items by name as well (which will all have {0} as their parent_ids)
araqnid
Frank, I'm not sure what you're suggesting. Are you suggesting storing the path in an array? If so, I still need to sort on that field to retain relationships - which means I won't be able to sort on name. See my edit above.
robdog
Why wouldn't it be possible to sort on name? It works fine over here. Show us a case that doesn't work. (ps. 0 is an incorrect parent_id, it does not exist. You'd better use NULL when is there is no parent)
Frank Heikens
WITH RECURSIVE recursetree(id, parent_id, name) AS ( SELECT id, NULL::int[] || parent_id, name FROM tree WHERE parent_id = 0 UNION ALL SELECT t.id, rt.parent_id || t.parent_id, t.name FROM tree t JOIN recursetree rt ON rt.id = t.parent_id )SELECT * FROM recursetree ORDER BY name;
robdog
Apologies for the formatting above. The above query orders by name - as a result children no longer follow their parents in the result.
robdog
Ofcourse not! Why should it? You don't order by parent, why would the database order by something you don't tell it to do so? ORDER BY parent_ids, name;
Frank Heikens
Thanks Frank. I appreciate the help, but that still wont work. Because you're ordering on path first, you're going to be ordering by id - not name.
robdog
I think it may be easier (although less efficient) to do a last sort in the application logic to order the results correctly.
robdog