Thread: Ltree - how to sort nodes on parent node

Ltree - how to sort nodes on parent node

From
cojack
Date:
Hello,
Im here because Oleg Bartunov invite me to this mailing list. Im searching
for help with ltree module, in todo list (in this module) is Better
documentation, since 2003 year, no one make something more for this. Whats
is the problem? With example from manual about ltree, we have some data in
our table, now add some row to table for Science node, and we will have
something like this:

 id |                     path                      | sort
                                     
----+-----------------------------------------------+------
                                     
  1 | Top                                           |    1
                                     
  2 | Top.Science                                   |    1
                                     
  3 | Top.Science.Astronomy                         |    1
                                     
  4 | Top.Science.Astronomy.Astrophysics            |    1
                                     
  5 | Top.Science.Astronomy.Cosmology               |    2
                                     
  6 | Top.Hobbies                                   |    2
                                     
  7 | Top.Hobbies.Amateurs_Astronomy                |    2
                                     
  8 | Top.Collections                               |    3
  9 | Top.Collections.Pictures                      |    1
 10 | Top.Collections.Pictures.Astronomy            |    1
 11 | Top.Collections.Pictures.Astronomy.Stars      |    1
 12 | Top.Collections.Pictures.Astronomy.Galaxies   |    2
 13 | Top.Collections.Pictures.Astronomy.Astronauts |    3
 15 | Top.Science.Programing                        |    3

Sort column, is added by my self, because Im trying to sort those columns,
but I don't know how. depesz from irc show me example how to sort this data
with creating ordering column in the fly:

WITH RECURSIVE rec AS (
SELECT *, btrim(to_char( sort, '0000000' )) AS ordering FROM tree WHERE
nlevel(path) = 1
UNION ALL
SELECT t2.*, t1.ordering || '/' || btrim(to_char( t2.sort, '0000000' )) AS
ordering FROM rec t1, tree t2 WHERE t1.path @> t2.path AND nlevel(t1.path) +
1 = nlevel(t2.path)
)
SELECT id, subpath(path, -1) AS title, nlevel(path) AS depth, sort FROM rec
ORDER BY ordering;

But I'm not sure it's the best way to sort this columns, so Im wrinting here
for help and some examples, or improve the ltree manual with more example.

--
Regards,
cojack.

Re: Ltree - how to sort nodes on parent node

From
Alban Hertroys
Date:
On 19 Apr 2010, at 9:23, cojack wrote:

> Hello,

> id |                     path                      | sort
                                      
> ----+-----------------------------------------------+------
                                       
>  1 | Top                                           |    1
                                      
>  2 | Top.Science                                   |    1
                                      
>  3 | Top.Science.Astronomy                         |    1
                                      
>  4 | Top.Science.Astronomy.Astrophysics            |    1
                                      
>  5 | Top.Science.Astronomy.Cosmology               |    2
                                      
>  6 | Top.Hobbies                                   |    2
                                      
>  7 | Top.Hobbies.Amateurs_Astronomy                |    2
                                      
>  8 | Top.Collections                               |    3
>  9 | Top.Collections.Pictures                      |    1
> 10 | Top.Collections.Pictures.Astronomy            |    1
> 11 | Top.Collections.Pictures.Astronomy.Stars      |    1
> 12 | Top.Collections.Pictures.Astronomy.Galaxies   |    2
> 13 | Top.Collections.Pictures.Astronomy.Astronauts |    3
> 15 | Top.Science.Programing                        |    3


It would help if you'd show us what result you expect from ordering the above.

Most people would order this by path I think. However that doesn't match your sort column and I can't think of any
methodthat would give results in such an arbitrary order as you seem to be specifying - unless you set it by hand like
youdo. 

Alban Hertroys

--
Screwing up is an excellent way to attach something to the ceiling.


!DSPAM:737,4bcc9b1e10415135793547!



Re: Ltree - how to sort nodes on parent node

From
cojack
Date:
> Alban Hertroys wrote:
>
> It would help if you'd show us what result you expect from ordering the
> above.
>
> Most people would order this by path I think. However that doesn't match
> your sort column and I can't think of any method that would give results
> in such an arbitrary order as you seem to be specifying - unless you set
> it by hand like you do.
>
> Alban Hertroys
>
> --
> Screwing up is an excellent way to attach something to the ceiling.
>
>
> !DSPAM:737,4bcc9b1e10415135793547!
>
>
>
Yes, you have right, for example I create new idea of stored data in table:

here is a paste: http://pastebin.com/4pX5cM7j -- never expired link

As you can see, I have noodes with numeric type, those nodes present a sort
position by self. And If I type ORDER BY path; I will have data like I want
to have: http://pastebin.com/R4z01LC5 -- never expired link

Again, you can see now grouped data in his nodes, this is the outputed data
I wanted. If you know better way to make this WITHOUT recursive queries,
give me a hint.

--
Regards,
cojack.

Re: Ltree - how to sort nodes on parent node

From
Alban Hertroys
Date:
On 19 Apr 2010, at 20:26, cojack wrote:

>> Alban Hertroys wrote:
>>
>> It would help if you'd show us what result you expect from ordering the
>> above.
>>
>> Most people would order this by path I think. However that doesn't match
>> your sort column and I can't think of any method that would give results
>> in such an arbitrary order as you seem to be specifying - unless you set
>> it by hand like you do.

> Yes, you have right, for example I create new idea of stored data in table:
>
> here is a paste: http://pastebin.com/4pX5cM7j -- never expired link
>
> As you can see, I have noodes with numeric type, those nodes present a sort
> position by self. And If I type ORDER BY path; I will have data like I want
> to have: http://pastebin.com/R4z01LC5 -- never expired link
>
> Again, you can see now grouped data in his nodes, this is the outputed data
> I wanted. If you know better way to make this WITHOUT recursive queries,
> give me a hint.


Aha, looks like you want to sort each tree level by some user-specified order.

You should realise that ltree was contributed before Postgres supported (recursive) CTE's. If you're using ltree in
combinationwith recursive CTE's you're doing twice the work that you need to do - ltree was created as a means to make
recursivequeries possible in the first place. 

I think you have basically two ways to go about this:

1). The way you're doing this in your new examples should work, although I'd probably make the ordering numbers part of
thecategory names and split those off when I read them. For example: 
         27 | 1|Top
         28 | 1|Top.1|Science
         29 | 1|Top.2|Hobby
         30 | 1|Top.3|Colors
         31 | 1|Top.1|Science.1|Physics
         32 | 1|Top.1|Science.2|Chemistry
         33 | 1|Top.1|Science.3|Biology
         34 | 1|Top.1|Science.4|History
         35 | 1|Top.2|Hobby.1|Fishing
         36 | 1|Top.2|Hobby.2|Football
         37 | 1|Top.3|Colors.1|Black
         38 | 1|Top.3|Colors.2|Red
         39 | 1|Top.3|Colors.3|Blue
         40 | 1|Top.1|Science.5|Archeology
         41 | 1|Top.2|Hobby.3|Swimming
         42 | 1|Top.3|Colors.4|Gray
         43 | 1|Top.3|Colors.5|Purple
         44 | 1|Top.3|Colors.6|Brown
         45 | 1|Top.2|Hobby.4|Climbing

2). Drop the ltree column and go with a truly recursive approach, something like this:

CREATE TABLE node (
    category    text    NOT NULL PRIMARY KEY,
    sort_order    int    NOT NULL,
    parent        text    REFERENCES tree (category)
                    ON UPDATE CASCADE
                    ON DELETE CASCADE
);

WITH RECURSIVE tree AS (
    SELECT *
      FROM node
     WHERE parent IS NULL

    UNION ALL

    SELECT node.*
      FROM tree, node
     WHERE node.parent = tree.category
     ORDER BY sort_order
)
SELECT * FROM tree;

I haven't actually used recursive CTE's before so there may be some errors in the above, but you get the general idea.

Alban Hertroys

--
Screwing up is an excellent way to attach something to the ceiling.


!DSPAM:737,4bcd773910411833268189!



Re: Ltree - how to sort nodes on parent node

From
cojack
Date:
> Alban Hertroys wrote:
>
>
> Aha, looks like you want to sort each tree level by some user-specified
> order.
>
> You should realise that ltree was contributed before Postgres supported
> (recursive) CTE's. If you're using ltree in combination with recursive
> CTE's you're doing twice the work that you need to do - ltree was created
> as a means to make recursive queries possible in the first place.
>
> I think you have basically two ways to go about this:
>
> 1). The way you're doing this in your new examples should work, although
> I'd probably make the ordering numbers part of the category names and
> split those off when I read them. For example:
>          27 | 1|Top
>          28 | 1|Top.1|Science
>          29 | 1|Top.2|Hobby
>          30 | 1|Top.3|Colors
>          31 | 1|Top.1|Science.1|Physics
>          32 | 1|Top.1|Science.2|Chemistry
>          33 | 1|Top.1|Science.3|Biology
>          34 | 1|Top.1|Science.4|History
>          35 | 1|Top.2|Hobby.1|Fishing
>          36 | 1|Top.2|Hobby.2|Football
>          37 | 1|Top.3|Colors.1|Black
>          38 | 1|Top.3|Colors.2|Red
>          39 | 1|Top.3|Colors.3|Blue
>          40 | 1|Top.1|Science.5|Archeology
>          41 | 1|Top.2|Hobby.3|Swimming
>          42 | 1|Top.3|Colors.4|Gray
>          43 | 1|Top.3|Colors.5|Purple
>          44 | 1|Top.3|Colors.6|Brown
>          45 | 1|Top.2|Hobby.4|Climbing
>
>
> Alban Hertroys
>
> --
> Screwing up is an excellent way to attach something to the ceiling.
>
>
> !DSPAM:737,4bcd773910411833268189!
>
>
>
My and your first example doesn't work fine at all, why? Becouse when we add
more thank 10 sub nodes in some node, the 10 node will not be after 9, but
after 1 before 2, and this is not good idea to set sort in path. I think the
best idea for this will be create other column, with also ltree data type
and stored inside a sort/ordering data. Like:

1
1.1
1.1.1
1.1.2
1.1.3

And while selected it from table, just cast it to int. I'll check this and
his performance after I return from work.

I am not interested about recursive queries, i think this kill ltree idea.

--
Regards,
cojack.

Re: Ltree - how to sort nodes on parent node

From
Harald Fuchs
Date:
In article <59670B22-30CB-4E6E-83C8-C1D1036C9B2A@solfertje.student.utwente.nl>,
Alban Hertroys <dalroi@solfertje.student.utwente.nl> writes:

> 2). Drop the ltree column and go with a truly recursive approach, something like this:

> CREATE TABLE node (
>     category    text    NOT NULL PRIMARY KEY,
>     sort_order    int    NOT NULL,
>     parent        text    REFERENCES tree (category)
>                     ON UPDATE CASCADE
>                     ON DELETE CASCADE
> );

> WITH RECURSIVE tree AS (
>     SELECT *
>       FROM node
>      WHERE parent IS NULL

>     UNION ALL

>     SELECT node.*
>       FROM tree, node
>      WHERE node.parent = tree.category
>      ORDER BY sort_order
> )
> SELECT * FROM tree;

Here's a working version:

  WITH RECURSIVE tree (path, category, sort_order, parent) AS (
    SELECT category, category, sort_order::text, parent
    FROM node
    WHERE parent IS NULL
  UNION ALL
    SELECT t.path || '.' || n.category,
           n.category,
           t.sort_order || '.' || n.sort_order,
           n.parent
    FROM tree t
    JOIN node n ON n.parent = t.category
  )
  SELECT path
  FROM tree
  ORDER BY sort_order

Re: Ltree - how to sort nodes on parent node

From
Alban Hertroys
Date:
On 20 Apr 2010, at 18:05, Harald Fuchs wrote:

> Here's a working version:
>
>  WITH RECURSIVE tree (path, category, sort_order, parent) AS (
>    SELECT category, category, sort_order::text, parent
>    FROM node
>    WHERE parent IS NULL
>  UNION ALL
>    SELECT t.path || '.' || n.category,
>           n.category,
>           t.sort_order || '.' || n.sort_order,
>           n.parent
>    FROM tree t
>    JOIN node n ON n.parent = t.category
>  )
>  SELECT path
>  FROM tree
>  ORDER BY sort_order


May be, but then you're just re-inventing ltree again. I'm pretty sure this must be possible without adding convoluted
thingslike casting sort orders to text (which can for example cause issues like '10' ending up between '1' and '2'). 

Since this is 8.4 anyway (CTE's after all), can't the sorting be done using a windowing function or something? We have
recursionnow, there's got to be a proper solution, I just can't get my mind around it right now. 

Alban Hertroys

--
If you can't see the forest for the trees,
cut the trees and you'll see there is no forest.


!DSPAM:737,4bcdf5a610412270627163!



Re: Ltree - how to sort nodes on parent node

From
Alban Hertroys
Date:
On 20 Apr 2010, at 11:59, cojack wrote:

>> 1). The way you're doing this in your new examples should work, although
>> I'd probably make the ordering numbers part of the category names and
>> split those off when I read them. For example:
>>         27 | 1|Top
>>         28 | 1|Top.1|Science
>>         29 | 1|Top.2|Hobby
>>         30 | 1|Top.3|Colors
>>         31 | 1|Top.1|Science.1|Physics
>>         32 | 1|Top.1|Science.2|Chemistry
>>         33 | 1|Top.1|Science.3|Biology
>>         34 | 1|Top.1|Science.4|History
>>         35 | 1|Top.2|Hobby.1|Fishing
>>         36 | 1|Top.2|Hobby.2|Football
>>         37 | 1|Top.3|Colors.1|Black
>>         38 | 1|Top.3|Colors.2|Red
>>         39 | 1|Top.3|Colors.3|Blue
>>         40 | 1|Top.1|Science.5|Archeology
>>         41 | 1|Top.2|Hobby.3|Swimming
>>         42 | 1|Top.3|Colors.4|Gray
>>         43 | 1|Top.3|Colors.5|Purple
>>         44 | 1|Top.3|Colors.6|Brown
>>         45 | 1|Top.2|Hobby.4|Climbing

> My and your first example doesn't work fine at all, why? Becouse when we add
> more thank 10 sub nodes in some node, the 10 node will not be after 9, but

That's just a matter of reserving enough padding for the numbers to fit. It does mean you bake in an upper limit to the
numberof items people can sort, but there is a practical limit your users are very unlikely to ever pass. I think
anythingpast 4 digits is unlikely to happen. It's not a very clean solution, but it certainly does work. 

> after 1 before 2, and this is not good idea to set sort in path. I think the
> best idea for this will be create other column, with also ltree data type
> and stored inside a sort/ordering data. Like:
>
> 1
> 1.1
> 1.1.1
> 1.1.2
> 1.1.3
>
> And while selected it from table, just cast it to int. I'll check this and
> his performance after I return from work.

This has the same problem as the previous one, 10 will end up between 1 and 2. It is cleaner than combining both into
onetree though, so with sufficient padding it should work. 

> I am not interested about recursive queries, i think this kill ltree idea.


And IMHO it should. ltree is from a time when we didn't have any other means to describe data organised as a tree in
Postgres.Navigating a tree is inherently recursive, so recursion is most likely the proper way to go about it. 

A solution omitting recursion (like ltree) can be faster, but you will run into limitations like the one you're
currentlystruggling with. 

A solution with recursive queries will probably be more flexible and allows for referential integrity without having to
writeyour own triggers and stuff - for example, what happens if you decide that Archeology isn't a Science but a
Colour?What makes sure it's child-nodes get moved into Colors as well? 

Alban Hertroys

--
If you can't see the forest for the trees,
cut the trees and you'll see there is no forest.


!DSPAM:737,4bcdf97810413554942613!



Re: Ltree - how to sort nodes on parent node

From
Peter Hunsberger
Date:
On Tue, Apr 20, 2010 at 1:58 PM, Alban Hertroys
<dalroi@solfertje.student.utwente.nl> wrote:
> On 20 Apr 2010, at 11:59, cojack wrote:
>
>
>> I am not interested about recursive queries, i think this kill ltree idea.
>
>
> And IMHO it should. ltree is from a time when we didn't have any other means to describe data organised as a tree in
Postgres.Navigating a tree is inherently recursive, so recursion is most likely the proper way to go about it. 
>
> A solution omitting recursion (like ltree) can be faster, but you will run into limitations like the one you're
currentlystruggling with. 
>
> A solution with recursive queries will probably be more flexible and allows for referential integrity without having
towrite your own triggers and stuff - for example, what happens if you decide that Archeology isn't a Science but a
Colour?What makes sure it's child-nodes get moved into Colors as well? 
>

I've only been peripherally following this thread, so the following
may be overkill for the requirements, but the non-recursive / flat
query, solution is usually the set / subset pattern.  It's been
popularized by Joe Celko and he has gone as far as writing a book on
the topic "Trees and hierarchies in SQL for smarties".  If you don't
have many requirements for reordering the tree  this solution works
well.   It can be more of a pain if you need a GUI for tree management
(but can be done).  We use this type of solution to manage trees up to
about 100,000 nodes in size with good performance.  Other
non-recursive solutions include Vadim Tropashko's (now with Oracle)
Nested Interval Tree Encoding methods, which map directly to the
dotted path (1.1.3) type tree notations in the examples in this thread
and are a variation on the set / subset models.

--
Peter Hunsberger

Re: Ltree - how to sort nodes on parent node

From
Harald Fuchs
Date:
In article <1F96E061-713C-4929-A7D9-278E5B608EE1@solfertje.student.utwente.nl>,
Alban Hertroys <dalroi@solfertje.student.utwente.nl> writes:

> On 20 Apr 2010, at 18:05, Harald Fuchs wrote:
>> Here's a working version:
>>
>> WITH RECURSIVE tree (path, category, sort_order, parent) AS (
>> SELECT category, category, sort_order::text, parent
>> FROM node
>> WHERE parent IS NULL
>> UNION ALL
>> SELECT t.path || '.' || n.category,
>> n.category,
>> t.sort_order || '.' || n.sort_order,
>> n.parent
>> FROM tree t
>> JOIN node n ON n.parent = t.category
>> )
>> SELECT path
>> FROM tree
>> ORDER BY sort_order

> May be, but then you're just re-inventing ltree again.

Not quite - with proper normalization you're storing the path elements
only once and create the ltree-style paths on the fly.

> I'm pretty sure this must be possible without adding convoluted
> things like casting sort orders to text (which can for example cause
> issues like '10' ending up between '1' and '2').

Ah, you're right.  I think _some_ convolution is still needed because
we must remember the sort order for each path element.

> Since this is 8.4 anyway (CTE's after all), can't the sorting be
> done using a windowing function or something? We have recursion now,
> there's got to be a proper solution, I just can't get my mind around
> it right now.

I don't think windowing functions will help here.  Anyway, here's a
complete example which also deals with the 1/10/2 issue you mentioned
above:

CREATE TABLE node (
  id serial NOT NULL,
  category text NOT NULL,
  sort_order int NOT NULL,
  parent int NULL REFERENCES node (id),
  PRIMARY KEY (id)
);

CREATE UNIQUE INDEX node_pc_uq ON node (parent, category);

-- Enforce unambiguous sorting
CREATE UNIQUE INDEX node_ps_uq ON node (parent, sort_order);

COPY node (id, category, sort_order, parent) FROM stdin;
1    Top    1    \N
2    Science    1    1
3    Physics    1    2
4    Chemistry    2    2
5    Biology    3    2
6    History    4    2
7    Archeology    5    2
8    Hobby    2    1
9    Fishing    1    8
10    Football    2    8
11    Swimming    3    8
12    Climbing    4    8
13    Colors    3    1
14    Black    1    13
15    Red    2    13
16    Blue    3    13
17    Gray    4    13
18    Purple    5    13
19    Brown    6    13
\.

WITH RECURSIVE tree (path, id, sort_order, parent) AS (
  SELECT category, id, ARRAY[sort_order], parent
  FROM node
  WHERE parent IS NULL
UNION ALL
  SELECT t.path || '.' || n.category, n.id,
         t.sort_order || n.sort_order,
         n.parent
  FROM tree t
  JOIN node n ON n.parent = t.id
)
SELECT path, id, sort_order, parent
FROM tree
ORDER BY sort_order;

Re: Ltree - how to sort nodes on parent node

From
Saccilotto Ramon
Date:
Hi everonye,

I don’t know if this is still a topic for anyone. But here is a query that I came up with to do the sorting. It will currently probably not make use of the ltree indexing, so it might be worth to further adapt the query.

The table (example_table) would be something like

path|ordinal
----+--------------
Top | 1
Top.Science | 1
Top.Hobbies | 2
Top.Collections | 3

The selection would work as follows:

/* create a intermediate table with an id column */
WITH ltreeTable AS (
  SELECT
  -- select the last part of the path as id
  subpath(path, -1) as "id",
  "path",
  "ordinal"
  FROM example_table
),

/* split the ltree path into separate parts */
treeParts AS (
  SELECT
  "id",
  -- split the path into separate parts
  unnest(regexp_split_to_array(path::text, '\.'))::ltree as "part",
  -- generate an ordinal for each array to preserve the order of the path
  generate_subscripts(regexp_split_to_array(path::text, '\.'), 1) as "idx"
  FROM ltreeTable
),

/* prefix each part with its respective zero-padded ordinal for sorting */
treePartsSorted AS (
  SELECT
  a.*,
  -- prefix each part with the ordinal
  lpad(b.ordinal::text, 4, '0') || '.' || a.part::text as "prefixed"
  FROM treeParts as a

  LEFT JOIN ltreeTable as b
  ON a.part = b.id
),

/* combine the paths back again */
treeSorting AS (
  SELECT
  "id",
  -- aggregate all parts and combine it back to an ltree path
  array_to_string(array_agg(prefixed ORDER BY idx),'.') AS "sorting"
  FROM treePartsSorted
  GROUP BY "id"
),

/* add the sorting column to the tree */
tree AS (
  SELECT
  a.*, text2ltree(b.sorting) as "sorting"
  FROM ltreeTable as a
  LEFT JOIN treeSorting as b
  ON a.id = b.id
)

SELECT * FROM tree
ORDER BY sorting asc;