Hierarchical queries in MySQL: finding leaves
Comments enabled. I *really* need your comment
This is a series of articles on hierarchical queries in MySQL:
- Hierarchical queries in MySQL
- Hierarchical queries in MySQL: adding level
- Hierarchical queries in MySQL: adding ancestry chains.
- Hierarchical queries in MySQL: finding leaves
- Hierarchical queries in MySQL: finding loops
See also:
In this article, we will find the leaves of the hierarchy tree.
A row is a leaf in a hierarchy tree if it has no children.
In Oracle, there is a speical pseudocolumn that tests if a given row is a leaf in a CONNECT BY
query. Surprisingly, it's called CONNECT_BY_ISLEAF
.
This pseudocolumn is quite straightforward: it returns 1 when a row is a leaf, 0 otherwise.
This pseudocolumn is quite easy to emulate in MySQL
, and we can do it even without using user-defined functions:
SELECT CONCAT(REPEAT(' ', lvl - 1), hi.id) AS treeitem, hierarchy_sys_connect_by_path('/', hi.id) AS path, parent, lvl, CASE WHEN lvl >= @maxlevel THEN 1 ELSE COALESCE( ( SELECT 0 FROM t_hierarchy hl WHERE hl.parent = ho.id LIMIT 1 ), 1) END AS is_leaf FROM ( SELECT hierarchy_connect_by_parent_eq_prior_id_with_level(id, @maxlevel) AS id, CAST(@level AS SIGNED) AS lvl FROM ( SELECT @start_with := 1, @id := @start_with, @level := 0, @maxlevel := 2 ) vars, t_hierarchy WHERE @id IS NOT NULL ) ho JOIN t_hierarchy hi ON hi.id = ho.id
treeitem | path | parent | lvl | is_leaf | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2 | 1/2 | 1 | 1 | 0 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
7 | 1/2/7 | 2 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
8 | 1/2/8 | 2 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
9 | 1/2/9 | 2 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
10 | 1/2/10 | 2 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
11 | 1/2/11 | 2 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
3 | 1/3 | 1 | 1 | 0 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
12 | 1/3/12 | 3 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
13 | 1/3/13 | 3 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
14 | 1/3/14 | 3 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
15 | 1/3/15 | 3 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
16 | 1/3/16 | 3 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
4 | 1/4 | 1 | 1 | 0 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
17 | 1/4/17 | 4 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
18 | 1/4/18 | 4 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
19 | 1/4/19 | 4 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
20 | 1/4/20 | 4 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
21 | 1/4/21 | 4 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
5 | 1/5 | 1 | 1 | 0 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
22 | 1/5/22 | 5 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
23 | 1/5/23 | 5 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
24 | 1/5/24 | 5 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
25 | 1/5/25 | 5 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
26 | 1/5/26 | 5 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
6 | 1/6 | 1 | 1 | 0 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
27 | 1/6/27 | 6 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
28 | 1/6/28 | 6 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
29 | 1/6/29 | 6 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
30 | 1/6/30 | 6 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
31 | 1/6/31 | 6 | 2 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
30 rows fetched in 0.0020s (0.0628s) |
This expression works as follows:
- It tests if the current row level hits the maximum level constraint. If it does, the expression returns 1 immediately, as even if there are children in the table itself, their level would exceed the maximum level value and, therefore, they would not be returned by
hierarchy_connect_by_parent_eq_prior_id_with_level
- If the level is OK, the expression selects 0 for the first child of the current row. If there is no child, then the row is a leaf, and the expression returns 1
This is an efficient way to do the check, as the subquery performs just a quick one row index scan without costly function calls.
To be continued.