Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does one print depth-level in a Postgres query that uses RECURSIVE to select descendants?

I have a table persons that contains a column for parent_id, which refers to another row in the same table. Assume this is the logical hierarchy:

          P1
  P2      P3      P4
P5  P6  P7  P8  P9  P10

I have written a query that prints all parents of a given node, along with the height above the node, and it seems to work fine:

WITH
RECURSIVE ancestors AS (
  SELECT id, parent_id
    FROM persons
    WHERE id = 8
  UNION
    SELECT p.id, p.parent_id
      FROM persons p
      INNER JOIN ancestors
        ON
          p.id = ancestors.parent_id
  )
SELECT persons.id, persons.name,
      ROW_NUMBER() over () as height
  FROM ancestors
  INNER JOIN persons
  ON
    ancestors.id = persons.id
  WHERE
    persons.id <> 8

Result:

  id   |    name     | height 
-------+-------------+---------
    3  | P3          |      1
    1  | P1          |      2
(2 rows)

I now want to write a query that similarly prints all descendants, along with depth. Here's the query so far (same as above with id and parent_id swapped in the UNION join):

WITH
RECURSIVE descendants AS (
  SELECT id, parent_id
    FROM persons
    WHERE id = 1
  UNION
    SELECT p.id, p.parent_id
      FROM persons p
      INNER JOIN descendants
        ON
          p.parent_id = descendants.id
  )
SELECT persons.id, persons.name,
      ROW_NUMBER() over () as depth
  FROM descendants
  INNER JOIN persons
  ON
    descendants.id = persons.id
  WHERE
    persons.id <> 1

This gives the following result:

  id   |    name     | depth
-------+-------------+---------
    2  | P2          |      1
    3  | P3          |      2
    4  | P4          |      3
    5  | P5          |      4
    6  | P6          |      5
    7  | P7          |      6
    8  | P8          |      7
    9  | P9          |      8
    10 | P10         |      9
(9 rows)

Clearly, the depth is all wrong. ROW_NUMBER() isn't doing what I want. How do I go about this?

I've thought about using a counter within the recursive part of the query itself, which increments every time it is run, but I'm not sure if there's a way to achieve that.

like image 671
thatharmansingh Avatar asked Nov 27 '15 12:11

thatharmansingh


People also ask

How recursive query works in PostgreSQL?

PostgreSQL internally uses a working table to process recursive CTEs. This processing is not really recursive, but rather iterative: First, the working table is initialized by executing the non-recursive branch of the CTE. The result of the CTE is also initialized with this result set.

What is recursive function in PostgreSQL?

PostgreSQL provides the WITH statement that supports the designing of auxiliary queries also known as CTEs (Common Table Expressions). A recursive query is a query that refers to a recursive CTE.


Video Answer


1 Answers

Use an additional integer column with values incremented at each recursive step.

WITH RECURSIVE descendants AS (
    SELECT id, parent_id, 0 AS depth
    FROM persons
    WHERE id = 1
UNION
    SELECT p.id, p.parent_id, d.depth+ 1
    FROM persons p
    INNER JOIN descendants d
    ON p.parent_id = d.id
)
SELECT p.id, p.name, depth
FROM descendants d
INNER JOIN persons p
ON d.id = p.id
WHERE p.id <> 1;

 id | name | depth 
----+------+-------
  2 | P2   |     1
  3 | P3   |     1
  4 | P4   |     1
  5 | P5   |     2
  6 | P6   |     2
  7 | P7   |     2
  8 | P8   |     2
  9 | P9   |     2
 10 | P10  |     2
(9 rows)

Db<>fiddle.

like image 69
klin Avatar answered Oct 21 '22 13:10

klin