Finding depth in a list model

My production data is similar to the adjacency list model shown here ...

http://mysql.stu.edu.tw/tech-resources/articles/hierarchical-data.html

My questions are - how do I know how deep the nesting is? In this case it is 4 from the last "Flash" sheet to the first "Electronics" node. Is there a query that will return this maximum depth number?

+3


source to share


4 answers


MYSQL has no CTEs nor recursion functions. You can solve it with a "classic loop":

DELIMITER $$
DROP FUNCTION IF EXISTS figure_up_depth $$

--encapsulated as a function:
CREATE FUNCTION figure_up_depth(idToSearch INT) RETURNS INT BEGIN

 --var to count steps from node to root:
 DECLARE depth INT;
 SET depth = 1;

 --while we are not on root node:
 WHILE ( idToSearch  is not  null) DO

   SET idToSearch = ( select parent
     from category
     where category_id = idToSearch
                  );

   SET depth = depth + 1;

 END WHILE;

 RETURN depth;

END;
$$
DELIMITER ;

      

Check:

mysql> select figure_up_depth(7);
+--------------------+
| figure_up_depth(7) |
+--------------------+
|                  4 |
+--------------------+
1 row in set (0.00 sec)

      

EDITED July 8, 2015

I understand that the OP is asking for the max depth , not the node depth. Easy:

mysql> select max( figure_up_depth(category_id) ) as max_depth
       from category;

      



If performance matters , the correct solution is:

  • Place everything categories.parent

    in a temporary table table1

    .
  • Join table1 to categories with categories.id = table1.id

    , place the result in the combined field categories.parent

    in a temporary table table2

    .
  • Drop table1

    and rename table2

    as table1

    .
  • Go to step 2 if it table1

    has strings.

The number of iteration cycles will be the depth of the tree:

DELIMITER $$
DROP PROCEDURE IF EXISTS letsgo;
CREATE PROCEDURE letsgo() BEGIN
 DECLARE R int;
 DECLARE D int;
 SET D=0;
 DROP TEMPORARY TABLE IF EXISTS children;
 CREATE TEMPORARY TABLE children AS (SELECT category_id as id 
                                       FROM category 
                                      WHERE parent is NULL );
 DROP TEMPORARY TABLE IF EXISTS children_prev;
 CREATE TEMPORARY TABLE children_prev AS (SELECT * FROM children );
 SET R = ( SELECT count(*) FROM children ); 
 WHILE ( R > 0 ) DO
   DROP TEMPORARY TABLE IF EXISTS children_aux;
   CREATE TEMPORARY TABLE children_aux AS (
     SELECT category_id as id 
       FROM category R  
       INNER JOIN  children_prev C on C.id = R.parent
   );
   SET R = ( SELECT count(*) FROM children_aux );  
   INSERT INTO children 
      SELECT * FROM children_aux;
   TRUNCATE TABLE children_prev;
   INSERT INTO children_prev
      SELECT * FROM children_aux;   
   SET D=D+1;
 END WHILE;
 SELECT D;
END;
$$
DELIMITER ;

      

Testing:

mysql> call letsgo();
+------+
| D    |
+------+
|    4 |
+------+
1 row in set (0.14 sec)

      

Note: sorry for the dirty solution, this is mysql: no CTE, no recursion, no selection in function recursion, not DO-While, ...

+5


source


For a solution that works for the ultimate "maximum" depth, you can use a query like this:

  SELECT CASE 
         WHEN MAX(l8.category_id IS NOT NULL) THEN 8
         WHEN MAX(l7.category_id IS NOT NULL) THEN 7
         WHEN MAX(l6.category_id IS NOT NULL) THEN 6
         WHEN MAX(l5.category_id IS NOT NULL) THEN 5
         WHEN MAX(l4.category_id IS NOT NULL) THEN 4
         WHEN MAX(l3.category_id IS NOT NULL) THEN 3
         WHEN MAX(l2.category_id IS NOT NULL) THEN 2
         WHEN MAX(l1.category_id IS NOT NULL) THEN 1
         ELSE 0
         END AS max_depth
    FROM category l1
    LEFT JOIN category l2 ON l2.parent = l1.category_id
    LEFT JOIN category l3 ON l3.parent = l2.category_id
    LEFT JOIN category l4 ON l4.parent = l3.category_id
    LEFT JOIN category l5 ON l5.parent = l4.category_id
    LEFT JOIN category l6 ON l6.parent = l5.category_id
    LEFT JOIN category l7 ON l7.parent = l6.category_id
    LEFT JOIN category l8 ON l8.parent = l7.category_id
   WHERE l1.parent IS NULL

      

In this case, if the "tree" contains more than eight levels, the query will return 8, the maximum depth it checks to.

This can be expanded, but this approach requires some ultimate maximum. (MySQL has a limit on the number of table references in a query).

The "root" node of the tree will be NULL for the parent. So the node is at level 1. (There might be multiple rows with NULL, multiple roots. ("I want to select a sibling tree with a maximum depth of 1 and choose to return 0 if there is no" root "node (s).

The expression ln.category_id IS NOT NULL

will return 1 if it is true and 0 if it is false. If there is any line where it is not empty, we know that the tree is at least at that level, that there are many levels.



The CASE expression basically checks: is there a tree at least 8 levels? If so, we're done. If not, then the tree is at least 7 levels? If not, is it at least six levels? and etc.

If you remove that CASE wrapper expression and aggregates MAX

, what you get is every node in the tree, and it goes back to the root node. You can order them from top to bottom ... level 1, level 2, level 3, etc.

To check the maximum depth of the tree, we want to check from the bottom up.


To check for unlimited depth, you need recursion, and MySQL does not support anything like that in the context of a SQL statement. You have entered the scope of a MySQL stored program (procedure or function) to get a recursive solution.

(Other databases give us a convenient syntax CONNECT BY

. But alas, MySQL doesn't.)

+2


source


Well that's a good long way :)

begin transaction

CREATE TABLE #category(
category_id INT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
parent INT DEFAULT NULL);

INSERT INTO #category
VALUES(1,'ELECTRONICS',NULL),(2,'TELEVISIONS',1),(3,'TUBE',2),
(4,'LCD',2),(5,'PLASMA',2),(6,'PORTABLE ELECTRONICS',1),
(7,'MP3 PLAYERS',6),(8,'FLASH',7),
(9,'CD PLAYERS',6),(10,'2 WAY RADIOS',6);

DECLARE @SQL NVARCHAR(MAX)
DECLARE @SQLF NVARCHAR(MAX)
DECLARE @SQLFINAL NVARCHAR(MAX)
DECLARE @CT as VARCHAR(10)
DECLARE @CTP as VARCHAR(10)
DECLARE @SQLFINALP as NVARCHAR(MAX)

SET @CT = 1
SET @CTP = 1
SET @SQL = 'Select t1.name ''lev1'' '
SET @SQLF = 'FROM #category t1 '
SET @SQLFINAL = @SQL + CHAR(13) + @SQLF +  'where t1.name = electronics and t1.name is not null'

While @@ROWCOUNT > 0 
    Begin
    SET @SQLFINALP = @SQL + CHAR(13) + @SQLF + 'WHERE t1.name =''electronics'''
    SET @CTP = @CT
    Set @CT = @CT + 1
    SET @SQL = @SQL +' , t' +@CT+'.name ''lev' + @CT + ''''
    SET @SQLF = @SQLF + ' left join #category t' + @CT + ' on t' + @CTP + '.category_id = t' + @CT + '.parent '
    SET @SQLFINAL = @SQL + 'into #temp' + CHAR(13) + @SQLF +  'where t' + @Ct + '.name is not null and t1.name = ''electronics'''
    exec SP_EXECUTESQL @SQLFINAL
    end
print @@ROWCOUNT
Select @CTP

print @SQLFINALp
exec SP_EXECUTESQL @SQLFINALp

rollback

      

+1


source


You might want to add a depth column. When you insert a node into the hierarchy, the depth is 0 if it has no parent, otherwise add 1 to the depth of the parent. Then you can just select it directly. This approach will be very effective and works for any depth.

+1


source







All Articles