sqlpostgresqlsql-order-bymaterialized-path-pattern

Sorting a tree while maintaining structure


I have a tree using materialized path.

The table is similar to:

+-------------+--------+----------+-------+
| path        | depth  | children | score |
+-------------+--------+----------+-------+
| 0001        | 1      | 3        | 5     |
| 00010001    | 2      | 0        | -3    |
| 00010002    | 2      | 0        | 27    |
| 00010003    | 2      | 0        | 10    |
| 0002        | 1      | 2        | 12    |
| 00020001    | 2      | 0        | 0     |
| 00020002    | 2      | 1        | 3     |
| 00020002001 | 3      | 0        | 1     |
+-------------+--------+----------+-------+

I want to sort by the score column while maintaining the tree structure.
What matters is children are beneath their parents.

+-------------+--------+----------+-------+
| path        | depth  | children | score |
+-------------+--------+----------+-------+
| 0002        | 1      | 2        | 12    |
| 00020002    | 2      | 1        | 3     |
| 00020002001 | 3      | 0        | 1     |
| 00020001    | 2      | 0        | 0     |
| 0001        | 1      | 3        | 5     |
| 00010002    | 2      | 0        | 27    |
| 00010003    | 2      | 0        | 10    |
| 00010001    | 2      | 0        | -3    |
+-------------+--------+----------+-------+

The path column is only used in the database, so it doesn't have to be sequential.

The SQL I currently use to sort the tree so I can build it:

SELECT path, depth, children, score FROM mytable ORDER BY path ASC

Solution

  • You're going to need a recursive query and a window function. It'll look similar to this:

    with recursive
    ordered_tree as (
    select tree.*,
           array[row_number() over w] as score_path
    from   tree
    where  depth = 1
    window w as (order by tree.score desc)
    union all
    select tree.*,
           parent.score_path || array[row_number() over w] as score_path
    from   tree
    join   ordered_tree as parent on parent.id = tree.parent_id
    window w as (partition by tree.parent_id order by tree.score desc)
    )
    select *
    from   ordered_tree
    order by score_path
    

    Note: the above will be rather slow if your set is large...