Home > Software design >  Using recursive function to find all childrens children IDS of a certain parent ID
Using recursive function to find all childrens children IDS of a certain parent ID

Time:01-24

I have a table named concept_has_parent_concepts with concept_ids and their parent_ids, the concept_ids are actually the child_ids pointing to their parent.

I want to find all the children of a parent, but the children can also have children and so on.

So the main focus is to find all the children, but also their children of a certain parent with id = 300264091. I want to achieve this with MySQL 8 recursive function, and I came up with this:

WITH RECURSIVE runner (concept_id, parent_id) AS (
    SELECT concept_id AS child_id FROM concept_has_parent_concepts WHERE parent_id = 300264091
    UNION ALL
    SELECT concept_id, parent_id FROM runner WHERE concept_id = child_id
)
SELECT concept_id, parent_id FROM runner

But it doesn't work, it gives an error can't find child_id (SQL Fout (1054): Unknown column 'concept_id' in 'field list'). I'm new to the recursive function, and I'm confused. What approach should I follow to find all the childrens(children) of a parent?

Sample data

Some sample data with more simple ID's, where parentid with NULL conceptid is already top parent:

concept_id parent_id
1 NULL
2 NULL
3 1
4 1
5 2
6 2
7 3
8 7
9 8
10 9
INSERT INTO concept_has_parent_concepts (concept_id,parent_id) VALUES ('1',NULL);
INSERT INTO concept_has_parent_concepts (concept_id,parent_id) VALUES ('2',NULL);
INSERT INTO concept_has_parent_concepts (concept_id,parent_id) VALUES ('3','1');
INSERT INTO concept_has_parent_concepts (concept_id,parent_id) VALUES ('4','1');
INSERT INTO concept_has_parent_concepts (concept_id,parent_id) VALUES ('5','2');
INSERT INTO concept_has_parent_concepts (concept_id,parent_id) VALUES ('6','2');
INSERT INTO concept_has_parent_concepts (concept_id,parent_id) VALUES ('7','3');
INSERT INTO concept_has_parent_concepts (concept_id,parent_id) VALUES ('8','7');
INSERT INTO concept_has_parent_concepts (concept_id,parent_id) VALUES ('9','8');
INSERT INTO concept_has_parent_concepts (concept_id,parent_id) VALUES ('10','9');

Expected result

Expected result with finding all childrens children of conceptid = 1, is finding concepts 3,4,7,8,9 and 10. For finding all childrens-children of conceptid = 2 I expect finding concepts 5, 6.

CodePudding user response:

A couple of problems:

  • In any UNION query (not only CTEs), all queries must have the same columns. In the case of a CTE, the queries must have the columns as those you define after the CTE name.

  • The columns you define after the name of the CTE are the columns of the CTE. Any <column> AS <alias> you define in the CTE body are ignored. You define the columns of the CTE as (concept_id, parent_id), so those are the only names you can reference.

  • The latter query inside a recursive CTE is typically a JOIN between the CTE and the base table.

Here's what I tested:

WITH RECURSIVE runner (concept_id, parent_id) AS (
    SELECT concept_id, parent_id FROM concept_has_parent_concepts 
    WHERE concept_id = 1
    UNION ALL
    SELECT c.concept_id, c.parent_id FROM runner AS r 
    JOIN concept_has_parent_concepts AS c ON r.concept_id = c.parent_id
)
SELECT concept_id, parent_id FROM runner;

Output:

 ------------ ----------- 
| concept_id | parent_id |
 ------------ ----------- 
|          1 |      NULL |
|          3 |         1 |
|          4 |         1 |
|          7 |         3 |
|          8 |         7 |
|          9 |         8 |
|         10 |         9 |
 ------------ ----------- 

CodePudding user response:

When dealing with recursion, it's important to define the two steps:

  • base step: getting the first child for parents that have no parents
  • recursive step: matching cte's child with tab's parent, then keeping cte's parent and tab's child (example: if A->B and B->C and C->D and D->E, we want to keep A->C at step1, A->D at step2, A->E at step3, etc...).

Conditions on the inner join will ensure these steps are correctly executed. A final aggregation with GROUP_CONCAT and partition on "parent_id" will make you see the ancestors and children for each ancestor.

WITH RECURSIVE cte AS (
    SELECT t1.concept_id AS parent_id, 
           t2.concept_id AS child_id
    FROM       tab t1
    INNER JOIN tab t2
            ON t1.concept_id = t2.parent_id
    WHERE t1.parent_id IS NULL
  
    UNION ALL
  
    SELECT cte.parent_id, tab.concept_id
    FROM       cte 
    INNER JOIN tab 
            ON cte.child_id = tab.parent_id
)
SELECT parent_id, GROUP_CONCAT(child_id) AS children
FROM cte
GROUP BY parent_id

Check the demo here.

  • Related