EXPLAIN EXTENDED

How to create fast database queries

Hierarchial queries in MySQL: identifying trees

Comments enabled. I *really* need your comment

Continuing on the theme of hierarchical queries in MySQL:

Assume we have a table with hierarchical structure like this:

treeitem
1
3
9
10
11
4
12
13
14
5
15
16
17
2
6
18
7
8
18 rows fetched in 0.0003s (0.0137s)

We have two trees here: one starting from 1, another one starting from 2.

The problem is: given any item, we should identify the whole tree this item belongs to, and return the whole tree in the hierarchical order.

This also can be easily done using hierarchical queries in MySQL.

In this article: Hierarchical queries in MySQL I shown how to implement a function that returnes tree items in correct order, being called sequentially.

This function is reentrable and keeps its state in session variables, one of which, @start_with, defines the parent element for the tree we want to build.

We have two problems here:

  1. Given an item, define a root of the tree it belongs to
  2. Build a whole tree, starting from the root

The first problem can be solved by iterating the linked list backwards, starting from the variable given as an input.

This article:

describes how to do it in great detail, that's why I'll just put a query here.

Let's create the table described above:


Table creation details

To find the root, for, say, 10, we need to rewind the tree, treating is a a linked list (id to parent)

SELECT  CONCAT(REPEAT('    ', level  - 1), CAST(_id AS CHAR)) AS treeitem,
parent,
level
FROM    (
SELECT  @r AS _id,
(
SELECT  @r := parent
FROM    t_hierarchy
WHERE   id = _id
) AS parent,
@l := @l + 1 AS level
FROM    (
SELECT  @r := 10,
@l := 0
) vars,
t_hierarchy h
WHERE   @r <> 0
) q
treeitem parent level
10 3 1
3 1 2
1 0 3
3 rows fetched in 0.0002s (0.0053s)

We traced the ancestry chain up to the very first item in the tree, which has no parents. This is, we identified the tree.

To get the whole tree. we just need to assign id retrieved in the last row of the query above, to the session variable @start_with, and call the tree building function repeatedly in a rowsource.

This can be easily done by applying ORDER BY level DESC LIMIT 1 to the query above.

The only problem we still have is that this function does not return @start_with itself, only its children and deeper. This of course can be easily solved by prepending the rowset with the @start_with itself.

Here's the final query:

SELECT  CONCAT(REPEAT('    ', level  - 1), CAST(hi.id AS CHAR)) AS treeitem, parent, level
FROM    (
SELECT  @start_with :=
(
SELECT  _id
FROM    (
SELECT  @r AS _id,
(
SELECT  @r := parent
FROM    t_hierarchy
WHERE   id = _id
) AS parent,
@l := @l + 1 AS level
FROM    (
SELECT  @r := 10,
@l := 0
) vars,
t_hierarchy h
WHERE   @r <> 0
) a
ORDER BY
level DESC
LIMIT 1
),
@id := @start_with,
@level := 1
) vars
JOIN    (
SELECT  @start_with AS id, 1 AS level
UNION ALL
SELECT  hierarchy_connect_by_parent_eq_prior_id(id) AS id, @level AS level
FROM    t_hierarchy
WHERE   @id IS NOT NULL
) ho
JOIN    t_hierarchy hi
ON      hi.id = ho.id

treeitem parent level
1 0 1
3 1 2
9 3 3
10 3 3
11 3 3
4 1 2
12 4 3
13 4 3
14 4 3
5 1 2
15 5 3
16 5 3
17 5 3
13 rows fetched in 0.0004s (0.0217s)

, which returns the whole tree of 1, given 10 as an input, since 1 is the ultimate ancestor of 10.

If we provide, say, 18 as a input, we will get the following:

SELECT  CONCAT(REPEAT('    ', level  - 1), CAST(hi.id AS CHAR)) AS treeitem, parent, level
FROM    (
SELECT  @start_with :=
(
SELECT  _id
FROM    (
SELECT  @r AS _id,
(
SELECT  @r := parent
FROM    t_hierarchy
WHERE   id = _id
) AS parent,
@l := @l + 1 AS level
FROM    (
SELECT  @r := 18,
@l := 0
) vars,
t_hierarchy h
WHERE   @r <> 0
) a
ORDER BY
level DESC
LIMIT 1
),
@id := @start_with,
@level := 1
) vars
JOIN    (
SELECT  @start_with AS id, 1 AS level
UNION ALL
SELECT  hierarchy_connect_by_parent_eq_prior_id(id) AS id, @level AS level
FROM    t_hierarchy
WHERE   @id IS NOT NULL
) ho
JOIN    t_hierarchy hi
ON      hi.id = ho.id

treeitem parent level
2 0 1
6 2 2
18 6 3
7 2 2
8 2 2
5 rows fetched in 0.0002s (0.0200s)

, since 18 is the member of other tree, having 2 as a root.

Written by Quassnoi

July 22nd, 2009 at 11:00 pm

Posted in MySQL

Leave a Reply