在列表模型中查找深度

时间:2015-07-03 09:01:12

标签: mysql sql

我的生产数据与此处解释的邻接列表模型类似......

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

我的问题是如何知道嵌套有多深?在这种情况下,从最后一个叶子“Flash”到第一个节点“Electronics”是4。是否有返回此最大深度数的查询?

4 个答案:

答案 0 :(得分:6)

MYSQL没有CTE也没有递归功能。您可以使用'经典循环'

来解决它
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 ;

<强>检查:

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

已编辑 2015年7月8日

我意识到OP要求 max 深度,而不是节点深度。一个简单的:

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

如果表现重要,正确的解决方案是:

  1. 将所有categories.parent放入临时表table1
  2. 使用categories.id = table1.id将table1与类别相关联,将结果加入字段categories.parent放在临时表table2上。
  3. 删除table1并将table2重命名为table1
  4. 如果table1有行,请转到第2步。
  5. 循环次数迭代将是树深度:

    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 ;
    

    测试:

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

    注意:抱歉脏解决方案,这是mysql:没有CTE,没有递归,没有选择函数递归,没有DO-While,...

答案 1 :(得分:2)

对于适用于有限“最大”深度的解决方案,您可以使用如下查询:

  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

在这种情况下,如果“树”的深度超过八级,查询将返回8,即它检查的最大深度。

这可以扩展,但这种方法需要一些有限的最大值。 (MySQL对查询中的表引用数量有限制。)

树的“根”节点将具有父值的NULL值。所以,这就是级别1的节点。(可能有多个行具有NULL值,多个根。(我选择识别最大深度为1的一级树,如果没有,则选择返回0) root“node(s)。

表达式ln.category_id IS NOT NULL如果为真则返回1,如果为假则返回0。如果有任何行是非空值,我们知道树至少有很多层。

CASE表达式基本上是检查:树是否至少有8级深度?如果是的话,我们已经完成了。如果没有,这棵树至少有7层深吗?如果没有,它至少是6级吗?等

如果删除CASE包装表达式和MAX聚合,则返回的是树中的每个节点,并且它返回根节点。您可能希望从上到下排序......级别1,级别2,级别3等。

为了检查树的最大深度,我们想从下往上检查。

要检查某些无限深度,您需要递归,并且MySQL不支持SQL语句上下文中的任何内容。您将进入MySQL存储程序(过程或函数)的领域以获得递归解决方案。

(其他数据库为我们提供了方便的CONNECT BY语法。但是,唉,MySQL没有。)

答案 2 :(得分:1)

这是关于它的好方法:)

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

答案 3 :(得分:1)

您可能需要考虑添加深度列。在层次结构中插入节点时,如果没有父节点,则深度为0,否则将1添加到父节点的深度。然后你可以直接选择它。这种方法非常有效,适用于任何深度