【问题标题】:Recursive Loop - Parent/Child Tree递归循环 - 父/子树
【发布时间】:2017-11-06 18:54:07
【问题描述】:

我正在尝试递归循环并返回所有具有9 根元素的child_id

结构:

 +-- 9
 |   +-- 8
 |       +-- 17
 |       +-- 33
 |       +-- 18
 |   +-- 22
 |       +-- 11
 |       +-- 4

父子链接表:(表名:elements_children)

+----+-----------+----------+
| id | parent_id | child_id |
+----+-----------+----------+
|  1 |         9 |        8 |
|  2 |         8 |       17 |
|  3 |         8 |       33 |
|  4 |         8 |       18 |
|  5 |         9 |       22 |
|  6 |        22 |       11 |
|  7 |        22 |        4 |
|  8 |         3 |        5 |
+----+-----------+----------+

所需的输出 - [8,17,33,18,22,11,4]

程序 1 (getChildren):

BEGIN

-- CREATE TEMP TABLE
DROP TABLE IF EXISTS total_children;
CREATE TEMPORARY TABLE total_children(
    id INT(11) NOT NULL auto_increment PRIMARY KEY, 
    child_id VARCHAR(255)
);

-- CALL TO PROCEDURE 2
CALL getNodes(rootNode);

-- SELECT child_id's FROM the temp table, then drop the table
SELECT child_id FROM total_children;
DROP TABLE total_children;

END

过程 2(getNodes):

BEGIN
-- VARIABLES
DECLARE done BOOLEAN DEFAULT FALSE;
DECLARE childNode VARCHAR(255);

-- CURSOR1
DECLARE cur1 CURSOR FOR SELECT child_id FROM elements_children WHERE parent_id = parentNode;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

OPEN cur1;

-- START LOOP
myloop:LOOP
    FETCH cur1 INTO childNode;
    -- CHECK IF DONE IS TRUE
    IF(done) THEN 
        LEAVE myloop; 
    END IF;
    -- APPEND TO TOTAL
    INSERT INTO total_children(child_id) SELECT childNode;
    -- RECURSIVE
    CALL getNodes(childNode);
END LOOP myloop;
-- END LOOP

-- END CURSOR1
CLOSE cur1;

END

我收到错误:递归限制超过 200

我将递归限制设置为 200,并且我知道该过程不应递归 200 次,因此我的代码中肯定有一个错误不会停止递归,我相信与 done 变量有关在myloop: LOOP.

问题:为什么我的程序会产生这个递归错误?

【问题讨论】:

  • procedures 调用程序 - 天哪!有时这是有道理的,但不确定这是其中之一。拥有一个构建子 ID 列表的循环可能会更简单,每次通过循环都附加新的 ID,直到循环内没有返回任何内容。我看看能不能写一篇……
  • @SloanThrasher 是的,我一直在查看 cursor 以迭代选择结果,但仍在尝试提出解决方案。
  • 如果你有一个列来存储“路径”,事情会更简单,例如9/22/11
  • @Bohemian 那是什么样的?
  • 只有两个(主)列; IDpath,其中 path 类似于文件路径或目录。所以“a/b”的路径意味着“我是b的孩子,这是a的孩子”。查找 x 的所有子节点只是 where path like '%x_%' 等。根据您的确切需要,它可能对您没有帮助,但它避免了递归问题

标签: mysql recursive-query b-tree


【解决方案1】:

我认为以下存储过程将产生您要求的结果。我设置了一个表格,并用您问题中的数据填充它:

DROP TABLE IF EXISTS `parent_child`;

CREATE TABLE `parent_child` (
    `id`            INT(10) UNSIGNED        NOT NULL AUTO_INCREMENT COMMENT 'Primary Key',
    `parent_id`     INT(10) UNSIGNED        NOT NULL,
    `child_id`      INT(10) UNSIGNED        NOT NULL,
    PRIMARY KEY (`id`),
    KEY `idx_parent_child_parent_id` (`parent_id`)
)
    ENGINE=MyISAM 
    AUTO_INCREMENT=1 
    DEFAULT CHARSET=utf8 
    COLLATE=utf8_unicode_ci
    COMMENT '';

INSERT INTO `parent_child`
(`id`,`parent_id`,`child_id`)
VALUES
('1','9','8'),
('2','8','17'),
('3','8','33'),
('4','8','18'),
('5','9','22'),
('6','22','11'),
('7','22','4'),
('8','3','5');

然后我创建了一个程序来逐步获取孩子,直到没有孩子为止。

DROP PROCEDURE GetChildren;

DELIMITER //

CREATE PROCEDURE GetChildren(root_id INT)
    BEGIN
        SET @list = root_id;
        SET @new_list = root_id;
        SET @maxDepth = 4;
        SET @depth = 0;

        WHILE (@new_list <> "" AND @depth < @maxDepth) DO
            SELECT @new_list as `new_list_before`,@list as `whole_list_before`;
            SET @depth = @depth + 1;
            SET @querystr = CONCAT("SELECT GROUP_CONCAT(`child_id`) as `children` INTO @c FROM `parent_child` WHERE `parent_id` in (?) AND (NOT (`child_id` IN (?)));");
            PREPARE stmt1 FROM @querystr;
            EXECUTE stmt1 USING @new_list,@list;
            IF @c <> "" THEN
                SET @list = CONCAT(@list,",",@c);
            END IF;
            SET @new_list = @c;
            SELECT @new_list as `new_list`,@list as `whole_list`;
            DEALLOCATE PREPARE stmt1;
        END WHILE;

        SELECT @list;
    END //
DELIMITER ;

最后,如果根 id 为 9,它的调用方式如下:

CALL GetChildren(9);

这会产生:

@list:
9,8,22,17,33,18

作为参考,这里是执行的选择之一:

SELECT GROUP_CONCAT(`child_id`) as `children` 
FROM `parent_child` 
WHERE `parent_id` in (9,8,22) AND (NOT `child_id` IN (9,8,22)) 
GROUP BY `parent_id`;

【讨论】:

  • 也可以使用临时表来完成,使用 select 为所有子项插入行,并在子 ID 上使用唯一键以防止重复。重复真实表和临时表之间的连接,直到它不增加大小。
  • 如果您有一个合理的最大深度级别数(,也可以通过单个查询来完成。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-05-25
  • 1970-01-01
  • 2013-10-24
  • 1970-01-01
  • 2018-06-27
相关资源
最近更新 更多