【问题标题】:How to find all IDs of children recursively?如何递归地找到孩子的所有ID?
【发布时间】:2010-11-02 16:41:46
【问题描述】:

我想仅使用 MySQL 从树中的孩子获取所有 ID。

我有一张这样的桌子:

ID parent_id name
1  0         cat1
2  1         subcat1
3  2         sub-subcat1
4  2         sub-subcat2
5  0         cat2

现在我正在尝试以递归方式获取 cat1 (2,3,4) 的所有子 ID。有什么方法可以实现吗?

【问题讨论】:

    标签: sql mysql database recursion nested


    【解决方案1】:

    有两种基本方法可以做到这一点:邻接列表和嵌套列表。看看Managing Hierarchical Data in MySQL

    您拥有的是邻接列表。不,没有办法用一条 SQL 语句递归地抓取所有后代。如果可能,只需将它们全部抓取并在代码中映射它们。

    嵌套集可以做你想做的事,但我倾向于避免它,因为插入记录的成本很高,而且容易出错。

    【讨论】:

    • 有一种方法可以在支持它的 RDBMS(如 PostgreSQL)中的普通 SQL 查询中进行递归
    • 此链接返回网页不可用
    【解决方案2】:

    如果您愿意的话,您可以使用存储过程来实现。

    否则,您无法使用单个 sql 语句来完成。

    理想情况下,您应该进行递归调用以从程序中遍历树

    【讨论】:

      【解决方案3】:

      您的问题似乎有点不准确。为什么要拥有它们,以及将它们“放在树上”是什么意思?

      你得到的表是树(表示的关系方式)。

      如果您希望它们“在一个表中”,其中包含包含对(ID 4,ParentID 0)的行,那么您需要您的 SQL 引擎的递归 SQL 版本来执行此操作,如果该引擎支持的话。

      我不会具体了解 MySQL,但我的理解是他们曾经计划使用与 Oracle 相同的语法来实现递归 SQL,即使用 CONNECT BY。

      如果您在手册的目录中查找“递归查询”或“CONNECT BY”等关键字,我想您应该能够找到答案。

      (很抱歉无法提供更易于使用的答案。)

      【讨论】:

        【解决方案4】:

        创建应该如下所示的表

        DROP TABLE IF EXISTS `parent_child`;
        CREATE TABLE `parent_child` (
          `id` int(11) NOT NULL AUTO_INCREMENT,
          `name` varchar(255) DEFAULT NULL,
          `parent_id` int(11) DEFAULT NULL,
          PRIMARY KEY (`id`)
        ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1;
        
        insert  into `parent_child`(`id`,`name`,`parent_id`)
        values (1,'cat1',0),(2,'subcat1',1),
        (3,'sub-subcat1',2),(4,'sub-subcat2',2),
        (5,'cat2',0);
        

        创建获取父子元素的函数

        DELIMITER $$
        
        USE `yourdatabase`$$
        
        DROP FUNCTION IF EXISTS `GetAllNode1`$$
        
        CREATE DEFINER=`root`@`localhost` FUNCTION `GetAllNode1`(GivenID INT) RETURNS TEXT CHARSET latin1
            DETERMINISTIC
        BEGIN
            DECLARE rv,q,queue,queue_children TEXT;
            DECLARE queue_length,front_id,pos INT;
            SET rv = '';
            SET queue = GivenID;
            SET queue_length = 1;
            WHILE queue_length > 0 DO
                SET front_id = queue;
                IF queue_length = 1 THEN
                    SET queue = '';
                ELSE
                    SET pos = LOCATE(',',queue) + 1;
                    SET q = SUBSTR(queue,pos);
                    SET queue = q;
                END IF;
                SET queue_length = queue_length - 1;
                SELECT IFNULL(qc,'') INTO queue_children
                FROM (SELECT GROUP_CONCAT(id) AS qc
                FROM `parent_child` WHERE `parent_id` = front_id) A ;
                IF LENGTH(queue_children) = 0 THEN
                    IF LENGTH(queue) = 0 THEN
                        SET queue_length = 0;
                    END IF;
                ELSE
                    IF LENGTH(rv) = 0 THEN
                        SET rv = queue_children;
                    ELSE
                        SET rv = CONCAT(rv,',',queue_children);
                    END IF;
                    IF LENGTH(queue) = 0 THEN
                        SET queue = queue_children;
                    ELSE
                        SET queue = CONCAT(queue,',',queue_children);
                    END IF;
                    SET queue_length = LENGTH(queue) - LENGTH(REPLACE(queue,',','')) + 1;
                END IF;
            END WHILE;
            RETURN rv;
        END$$
        
        DELIMITER ;
        

        为欲望输出编写查询

        SELECT GetAllNode1(id) FROM parent_child 
        or 
        SELECT GetAllNode1(id) FROM parent_child  where id =1 //for specific parent's child element 
        

        【讨论】:

          【解决方案5】:

          这是一个简单的单查询 MySql 解决方案:

          SELECT GROUP_CONCAT(Level SEPARATOR ',') FROM (
             SELECT @Ids := (
                 SELECT GROUP_CONCAT(`ID` SEPARATOR ',')
                 FROM `table_name`
                 WHERE FIND_IN_SET(`parent_id`, @Ids)
             ) Level
             FROM `table_name`
             JOIN (SELECT @Ids := <id>) temp1
          ) temp2
          

          只需将&lt;id&gt; 替换为父元素的ID

          这将返回一个字符串,其中包含ID = &lt;id&gt; 元素的所有后代的IDs,以, 分隔。如果您希望返回多行,每行有一个后代,则可以使用以下内容:

          SELECT *
          FROM `table_name`
          WHERE FIND_IN_SET(`ID`, (
             SELECT GROUP_CONCAT(Level SEPARATOR ',') FROM (
                SELECT @Ids := (
                    SELECT GROUP_CONCAT(`ID` SEPARATOR ',')
                    FROM `table_name`
                    WHERE FIND_IN_SET(`parent_id`, @Ids)
                ) Level
                FROM `table_name`
                JOIN (SELECT @Ids := <id>) temp1
             ) temp2
          ))
          

          包括根/父元素

          OP 要求元素的子元素,上面已回答。在某些情况下,在结果中包含根/父元素可能很有用。以下是我建议的解决方案:

          逗号分隔的 id 字符串:

          SELECT GROUP_CONCAT(Level SEPARATOR ',') FROM (
             SELECT <id> Level
             UNION
             SELECT @Ids := (
                 SELECT GROUP_CONCAT(`ID` SEPARATOR ',')
                 FROM `table_name`
                 WHERE FIND_IN_SET(`parent_id`, @Ids)
             ) Level
             FROM `table_name`
             JOIN (SELECT @Ids := <id>) temp1
          ) temp2
          

          多行:

          SELECT *
          FROM `table_name`
          WHERE `ID` = <id> OR FIND_IN_SET(`ID`, (
             SELECT GROUP_CONCAT(Level SEPARATOR ',') FROM (
                SELECT @Ids := (
                    SELECT GROUP_CONCAT(`ID` SEPARATOR ',')
                    FROM `table_name`
                    WHERE FIND_IN_SET(`parent_id`, @Ids)
                ) Level
                FROM `table_name`
                JOIN (SELECT @Ids := <id>) temp1
             ) temp2
          ))
          

          【讨论】:

          • 你能解释一下你的解决方案吗,它看起来很有希望,但我不确定我是否理解它。 @Ids 是递归用于加入的别名函数/var,您是否有更多关于哪些 mysql 版本支持这种查询的信息?
          • @Ids 是一个user-defined variable,它在 MySQL 中已经存在了很长时间。我认为查询应该在您想要使用的任何版本的 MySQL 中运行。该查询通过连接第一行上作为原始项目的子项的所有项目的 ID 来工作,然后连接第二行第一行中包含的任何项目的子项的所有项目的 ID,依此类推。最后,所有行都连接到一行。或者,可以拆分该行,以便每个项目都有自己的行(最后一个查询)。
          • 这很棒,很棒,我们使用了一段时间,但今天我意识到最后一个嵌套级别的子级被截断 - WHERE 子句中必须有 OR FIND_IN_SET(id, @Ids)。谢谢!
          • @Wirone 我没有体验到最后一个嵌套级别的孩子被截断。你能否提供一个数据集,你可以在其中得到这种行为,以便我看看它?在WHERE 子句中添加OR FIND_IN_SET(id, @Ids) 将对性能产生负面影响,因为结果中会重复ID。
          • @MagnarMyrtveit 这里是SQLFiddle。缺少的ID为688,开启上述OR子句时返回。
          猜你喜欢
          • 1970-01-01
          • 2011-02-12
          • 2018-11-03
          • 1970-01-01
          • 2022-01-27
          • 1970-01-01
          • 1970-01-01
          • 2021-02-25
          • 1970-01-01
          相关资源
          最近更新 更多