Hierarchical queries in MySQL: adding ancestry chains.
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:
Today, we will improve the query a little more.
If we develop the catalog with categories, subcategories etc, it's useful to have breadcrumb links somewhere in the header of the page.
In Oracle, there is a special function called SYS_CONNECT_BY_PATH(row_expression, delimiter)
, that returns the ancestry chain of a given node.
For any given row in a hierarchical query, it will return the delimiter
separated list of row_expressions
calculated for each of the ancestor rows, parents first.
We can emulate this function in MySQL too:
CREATE FUNCTION hierarchy_sys_connect_by_path(delimiter TEXT, node INT) RETURNS TEXT NOT DETERMINISTIC READS SQL DATA BEGIN DECLARE _path TEXT; DECLARE _cpath TEXT; DECLARE _id INT; DECLARE EXIT HANDLER FOR NOT FOUND RETURN _path; SET _id = COALESCE(node, @id); SET _path = _id; LOOP SELECT parent INTO _id FROM t_hierarchy WHERE id = _id AND COALESCE(id <> @start_with, TRUE); SET _path = CONCAT(_id, delimiter, _path); END LOOP; END
This function will work even without initializing session variables. In this case it will return the full ancestry chain path up to the first orphan node.
If @start_with
is initialized, the function will return the ancestry chain up to @start_with
row.
Let's see how it works:
SELECT CONCAT(REPEAT(' ', level - 1), hi.id) AS treeitem, hierarchy_sys_connect_by_path('/', hi.id) AS path, parent, level FROM ( SELECT hierarchy_connect_by_parent_eq_prior_id_with_level(id, 2) AS id, CAST(@level AS SIGNED) AS level FROM ( SELECT @start_with := 1, @id := @start_with, @level := 0 ) vars, t_hierarchy WHERE @id IS NOT NULL ) ho JOIN t_hierarchy hi ON hi.id = ho.id
treeitem | path | parent | level | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2 | 1/2 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
7 | 1/2/7 | 2 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
8 | 1/2/8 | 2 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
9 | 1/2/9 | 2 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
10 | 1/2/10 | 2 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
11 | 1/2/11 | 2 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
3 | 1/3 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
12 | 1/3/12 | 3 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
13 | 1/3/13 | 3 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
14 | 1/3/14 | 3 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
15 | 1/3/15 | 3 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
16 | 1/3/16 | 3 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
4 | 1/4 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
17 | 1/4/17 | 4 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
18 | 1/4/18 | 4 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
19 | 1/4/19 | 4 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
20 | 1/4/20 | 4 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
21 | 1/4/21 | 4 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
5 | 1/5 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
22 | 1/5/22 | 5 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
23 | 1/5/23 | 5 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
24 | 1/5/24 | 5 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
25 | 1/5/25 | 5 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
26 | 1/5/26 | 5 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
6 | 1/6 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
27 | 1/6/27 | 6 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
28 | 1/6/28 | 6 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
29 | 1/6/29 | 6 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
30 | 1/6/30 | 6 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
31 | 1/6/31 | 6 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
30 rows fetched in 0.0017s (0.0637s) |
The function result differs a little from what Oracle returns, because the latter appends the delimiter to the beginning of the expression returned, while our function uses delimiter only as a separator.
I made it this way because it's almost always undesirable to have the delimiter in the beginning of the result. And it's much easier to append the delimiter when we need it than to get rid of when we don't.
To be continued.
Hi,
in the 2nd request of this page, I had to change
SELECT @start_with := 1 SELECT @start_with := 0
To make it works
GISSINGER
10 Oct 13 at 20:51
And in the 1st I had to change ‘delimiter’ to ‘delim’ because DELIMITER is a special word in MySQL
GISSINGER
10 Oct 13 at 20:53
With order by path it works for me: http://sqlfiddle.com/#!9/a3ed2/2/1
Thanks for the greate articles.
Joachim
24 Dec 16 at 13:23
Can we do the same in oracle?
AAG
9 Aug 23 at 16:53