How to Create a Mysql Hierarchical Recursive Query?
I Have a Mysql Table Which Is as Follows: Id Name Parent_Id 19 Category1 0 20 Category2 19 21 Category3 20 22 Category4 21. .. .. .. .. Now, I Want to Have a...
I have a MySQL table which is as follows:
| id | name | parent_id |
|---|---|---|
| 19 | category1 | 0 |
| 20 | category2 | 19 |
| 21 | category3 | 20 |
| 22 | category4 | 21 |
| ... | ... | ... |
Now, I want to have a single MySQL query to which I simply supply the id [for instance say id=19] then I should get all its child ids [i.e. result should have ids '20,21,22']....
The hierarchy of the children is not known; it can vary....
I know how to do it using a for loop... but how to achieve the same using a single MySQL query?
16 Answers
For MySQL 8+: use the recursive with syntax.
For MySQL 5.x: use inline variables, path IDs, or self-joins.
MySQL 8+
with recursive cte (id, name, parent_id) as (
select id,
name,
parent_id
from products
where parent_id = 19
union all
select p.id,
p.name,
p.parent_id
from products p
inner join cte
on p.parent_id = cte.id
)
select * from cte;
The value specified in parent_id = 19 should be set to the id of the parent you want to select all the descendants of.
MySQL 5.x
For MySQL versions that do not support Common Table Expressions (up to version 5.7), you would achieve this with the following query:
select id,
name,
parent_id
from (select * from products
order by parent_id, id) products_sorted,
(select @pv := '19') initialisation
where find_in_set(parent_id, @pv)
and length(@pv := concat(@pv, ',', id))
Here is a fiddle.
Here, the value specified in @pv := '19' should be set to the id of the parent you want to select all the descendants of.
This will work also if a parent has multiple children. However, it is required that each record fulfills the condition parent_id < id, otherwise the results will not be complete.
Variable assignments inside a query
This query uses specific MySQL syntax: variables are assigned and modified during its execution. Some assumptions are made about the order of execution:
- The
fromclause is evaluated first. So that is where@pvgets initialised. - The
whereclause is evaluated for each record in the order of retrieval from thefromaliases. So this is where a condition is put to only include records for which the parent was already identified as being in the descendant tree (all descendants of the primary parent are progressively added to@pv). - The conditions in this
whereclause are evaluated in order, and the evaluation is interrupted once the total outcome is certain. Therefore the second condition must be in second place, as it adds theidto the parent list, and this should only happen if theidpasses the first condition. Thelengthfunction is only called to make sure this condition is always true, even if thepvstring would for some reason yield a falsy value.
All in all, one may find these assumptions too risky to rely on. The documentation warns:
you might get the results you expect, but this is not guaranteed [...] the order of evaluation for expressions involving user variables is undefined.
So even though it works consistently with the above query, the evaluation order may still change, for instance when you add conditions or use this query as a view or sub-query in a larger query. It is a "feature" that will be removed in a future MySQL release:
Previous releases of MySQL made it possible to assign a value to a user variable in statements other than
SET. This functionality is supported in MySQL 8.0 for backward compatibility but is subject to removal in a future release of MySQL.
As stated above, from MySQL 8.0 onward you should use the recursive with syntax.
Efficiency
For very large data sets this solution might get slow, as the find_in_set operation is not the most ideal way to find a number in a list, certainly not in a list that reaches a size in the same order of magnitude as the number of records returned.
Must Read
Alternative 1: with recursive, connect by
More and more databases implement the SQL:1999 ISO standard WITH [RECURSIVE] syntax for recursive queries (e.g. Postgres 8.4+, SQL Server 2005+, DB2, Oracle 11gR2+, SQLite 3.8.4+, Firebird 2.1+, H2, HyperSQL 2.1.0+, Teradata, MariaDB 10.2.2+). And as of version 8.0, also MySQL supports it. See the top of this answer for the syntax to use.
Some databases have an alternative, non-standard syntax for hierarchical look-ups, such as the CONNECT BY clause available on Oracle, DB2, Informix, CUBRID and other databases.
MySQL version 5.7 does not offer such a feature. When your database engine provides this syntax or you can migrate to one that does, then that is certainly the best option to go for. If not, then also consider the following alternatives.