EXPLAIN EXTENDED

How to create fast database queries

Hierarchical queries in MySQL: adding ancestry chains.

with 4 comments

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.

Written by Quassnoi

March 19th, 2009 at 11:00 pm

Posted in MySQL

4 Responses to 'Hierarchical queries in MySQL: adding ancestry chains.'

Subscribe to comments with RSS

  1. 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

  2. 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

  3. 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

  4. Can we do the same in oracle?

    AAG

    9 Aug 23 at 16:53

Leave a Reply