snippetsqlModerate
How do I sort the results of a recursive query in an expanded tree-like fashion?
Viewed 0 times
thequeryexpandedlikerecursivefashionresultshowsorttree
Problem
Let's assume you have a
It represents a standard node-like tree structure with root nodes at the top and several child nodes dangling from root nodes or other child nodes.
Let us insert a couple of example values:
Now I want to retrieve the first 10 root nodes and all their children up to a depth of 4:
This works great and gives me the following result:
As you might have noticed, there's no
How would I order the results as they would appear in an expanded tree view, as you can see from the example picture below?
I basically want the child nodes to be placed right after their corresponding parent node. If two or more child nodes have the same parent node, I want them to be sorted by their timestamp. Based on the example above, here's the desired output order that I'm trying to achieve:
```
depth | node | parent | ts
-------+------+--------+---------
1 | 1 | | 2014-01-01 00:00:00
2 |
nodes tables like this:CREATE TABLE nodes (
node serial PRIMARY KEY,
parent integer NULL REFERENCES nodes(node),
ts timestamp NOT NULL DEFAULT now()
);It represents a standard node-like tree structure with root nodes at the top and several child nodes dangling from root nodes or other child nodes.
Let us insert a couple of example values:
INSERT INTO nodes (parent) VALUES
(NULL), (NULL), (NULL), (NULL)
, (1), (1), (1), (1), (6), (1), (6)
, (9), (6), (6), (3), (3), (3), (15);Now I want to retrieve the first 10 root nodes and all their children up to a depth of 4:
WITH RECURSIVE node_rec AS (
(SELECT 1 AS depth, * FROM nodes WHERE parent IS NULL LIMIT 10)
UNION ALL
SELECT depth + 1, n.*
FROM nodes AS n JOIN node_rec ON (n.parent = node_rec.node)
WHERE depth < 4
)
SELECT * FROM node_rec;This works great and gives me the following result:
depth | node | parent
-------+------+--------
1 | 1 |
1 | 2 |
1 | 3 |
1 | 4 |
2 | 5 | 1
2 | 6 | 1
2 | 7 | 1
2 | 8 | 1
2 | 10 | 1
2 | 15 | 3
2 | 16 | 3
2 | 17 | 3
3 | 9 | 6
3 | 11 | 6
3 | 13 | 6
3 | 14 | 6
3 | 18 | 15
4 | 12 | 9As you might have noticed, there's no
ORDER BY clause, so the order is not defined. The order you see here is from root nodes to deeper nodes.How would I order the results as they would appear in an expanded tree view, as you can see from the example picture below?
I basically want the child nodes to be placed right after their corresponding parent node. If two or more child nodes have the same parent node, I want them to be sorted by their timestamp. Based on the example above, here's the desired output order that I'm trying to achieve:
```
depth | node | parent | ts
-------+------+--------+---------
1 | 1 | | 2014-01-01 00:00:00
2 |
Solution
You can order by the array representing the path from the root up to the leaf:
To keep it simple I left out
Basic query
The same can be simplified with the dedicated
Order by additional column
If two or more child nodes have the same parent node,
I want them to be sorted by their timestamp.
We really need to sort by the timestamp column
Equivalent, simpler query with the
AS you can see, the new syntax allows to specify multiple columns for the sort.
db<>fiddle here
To keep it simple I left out
LIMIT (so we also don't need extra parentheses) and WHERE of your original query. Those can be added freely.Basic query
WITH RECURSIVE node_rec AS (
SELECT 1 AS depth, *
, ARRAY[node] AS path
FROM nodes
WHERE parent IS NULL
UNION ALL
SELECT r.depth + 1, n.*
, r.path || n.node
FROM node_rec r
JOIN nodes n ON n.parent = r.node
)
SELECT *
FROM node_rec
ORDER BY path;The same can be simplified with the dedicated
SEARCH clause in Postgres 14 or later:WITH RECURSIVE node_rec AS (
SELECT 1 AS depth, *
FROM nodes
WHERE parent IS NULL
UNION ALL
SELECT r.depth + 1, n.*
FROM node_rec r
JOIN nodes n ON n.parent = r.node
) SEARCH DEPTH FIRST BY node SET path
SELECT *
FROM node_rec
ORDER BY path, ts;Order by additional column
If two or more child nodes have the same parent node,
I want them to be sorted by their timestamp.
We really need to sort by the timestamp column
ts first on each level, and node is just a tiebreaker (the timestamp may not be unique). The simplest solution is to take both as record type:WITH RECURSIVE node_rec AS (
SELECT 1 AS depth, *
, ARRAY[(ts, node)] AS path
FROM nodes
WHERE parent IS NULL
UNION ALL
SELECT r.depth + 1, n.*
, r.path || (n.ts, n.node)
FROM node_rec r
JOIN nodes n ON n.parent = r.node
)
SELECT *
FROM node_rec
ORDER BY path;Equivalent, simpler query with the
SEARCH clause in Postgres 14 or later:WITH RECURSIVE node_rec AS (
SELECT 1 AS depth, *
FROM nodes
WHERE parent IS NULL
UNION ALL
SELECT r.depth + 1, n.*
FROM node_rec r
JOIN nodes n ON n.parent = r.node
) SEARCH DEPTH FIRST BY ts, node SET path
SELECT *
FROM node_rec
ORDER BY path, ts;AS you can see, the new syntax allows to specify multiple columns for the sort.
db<>fiddle here
Code Snippets
WITH RECURSIVE node_rec AS (
SELECT 1 AS depth, *
, ARRAY[node] AS path
FROM nodes
WHERE parent IS NULL
UNION ALL
SELECT r.depth + 1, n.*
, r.path || n.node
FROM node_rec r
JOIN nodes n ON n.parent = r.node
)
SELECT *
FROM node_rec
ORDER BY path;WITH RECURSIVE node_rec AS (
SELECT 1 AS depth, *
FROM nodes
WHERE parent IS NULL
UNION ALL
SELECT r.depth + 1, n.*
FROM node_rec r
JOIN nodes n ON n.parent = r.node
) SEARCH DEPTH FIRST BY node SET path
SELECT *
FROM node_rec
ORDER BY path, ts;WITH RECURSIVE node_rec AS (
SELECT 1 AS depth, *
, ARRAY[(ts, node)] AS path
FROM nodes
WHERE parent IS NULL
UNION ALL
SELECT r.depth + 1, n.*
, r.path || (n.ts, n.node)
FROM node_rec r
JOIN nodes n ON n.parent = r.node
)
SELECT *
FROM node_rec
ORDER BY path;WITH RECURSIVE node_rec AS (
SELECT 1 AS depth, *
FROM nodes
WHERE parent IS NULL
UNION ALL
SELECT r.depth + 1, n.*
FROM node_rec r
JOIN nodes n ON n.parent = r.node
) SEARCH DEPTH FIRST BY ts, node SET path
SELECT *
FROM node_rec
ORDER BY path, ts;Context
StackExchange Database Administrators Q#63153, answer score: 18
Revisions (0)
No revisions yet.