Bigbob556677
Bigbob556677

Reputation: 2158

Get All Parents/ Children Of Record In Table

I have an SQL (Postgres) table named “Items” with the following schema.

id | parent_id | name

Where the parent_id column is a ForeignKey to the same table. (E.g items have parent items and those items can have parent items)

This makes a simple branching hierarchy.

Currently I query for the specific record and use a for loop to get each of its parents/ children. This results in numerous queries being executed.

What I’m wanting to do is query this table using the id of one of the items and get all of its parents/ children returned using a single query.

I can accomplish this by using .select_related(“parent__parent”) or .prefetch_related(“item_set”)for the children, but both of these approaches would require knowing the depth of the hierarchy before hand.

Is there a way in Django to get all of the related rows in a single query? Raw SQL is fine too.

Upvotes: 1

Views: 738

Answers (2)

Nicola Lepetit
Nicola Lepetit

Reputation: 795

This is a recursive solution for getting the parent and children hierarchy of a record.

select * from 
(WITH RECURSIVE cte_items (id, name, parent_id) AS (
    SELECT id, name, parent_id        
    FROM items
    WHERE id = 1
    UNION ALL
    SELECT e.id, e.name, e.parent_id
    FROM items e
      INNER JOIN cte_items o ON o.id = e.parent_id)
SELECT id, name, parent_id FROM cte_items) child
union
select * from 
(WITH RECURSIVE cte_items (id, name, parent_id) AS (
    SELECT id, name, parent_id        
    FROM items
    WHERE id = 1
    UNION ALL
    SELECT e.id, e.name, e.parent_id
    FROM items e
      INNER JOIN cte_items o ON o.parent_id = e.id)
SELECT id, name, parent_id FROM cte_items) parent
order by parent_id

Upvotes: 1

Bigbob556677
Bigbob556677

Reputation: 2158

OP Note

I have modified the great code provided by Nicola with the following.

The code is a little clearer and performs marginally fewer actions on the table.

The where id = 1 in each of the SELECTS is the actual id of the record you are wanting to query for.

WITH parents  AS (
    SELECT id, name, parent_id        
    FROM family
    WHERE id = 1 
    UNION ALL
    SELECT e.id, e.name, e.parent_id
    FROM family e
    INNER JOIN parents o ON e.id = o.parent_id)
, children  as (
    SELECT id, name, parent_id        
    FROM family
    WHERE id = 1  
    UNION ALL
    SELECT e.id, e.name, e.parent_id
    FROM family e
    INNER JOIN children o ON o.id = e.parent_id 
)
SELECT id, name, parent_id FROM parents
UNION
SELECT id, name, parent_id FROM children;

Upvotes: 0

Related Questions