MySQL 从 parent 递归获取所有 child
MySQL Recursive get all child from parent
我有这种情况,在 Mysql 上使用递归查询在一个 table...
上找到 lv 2 和 lv3 child
我正在使用的数据库结构:
id name parent
1 A 0
2 B 0
3 C 0
4 D 1
5 E 1
6 F 2
7 G 2
8 H 3
9 I 3
10 J 4
11 K 4
我期待的结果,在过滤数据时,id=1,它会生成我期待的结果。
id name parent
4 D 1
5 E 1
10 J 4
11 K 4
或者这是插图。
我一直在到处寻找,并阅读这篇文章http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/,但我没有找到我正在寻找的结果..
任何帮助将不胜感激,谢谢
SELECT *
FROM TABLENAME
WHERE PARENT = 1
UNION
SELECT *
FROM TABLENAME
WHERE PARENT IN
(SELECT ID FROM TABLENAME WHERE PARENT = 1)
试试这个,更快
SELECT *
FROM table AS T1
INNER JOIN (SELECT id FROM table WHERE parent = 1) AS T2
ON T2.id = T1.parent OR T1.parent = 1
GROUP BY T1.id
如果你想获得特定 parent 的所有等级 child 那么你应该试试这个
select id,
name,
parent
from (select * from tablename
order by parent, id) tablename,
(select @pv := '1') initialisation
where find_in_set(parent, @pv) > 0
and @pv := concat(@pv, ',', id)
我试过这个
select id from (select * from roles order by parent_role, id) roles,(select @pv := '1') initialisation
where find_in_set(parent_role, @pv) > 0
and @pv := concat(@pv, ',', id)
但它只适用于深度 2,我需要让它在更深的深度下工作,因为我有 8 个级别
谢谢@Manoj Rana
真是帮了我大忙。
但是我想在 Hibernate createNativeQuery(); 函数中使用这个解决方案。
由于 := 运算符我无法使用。因此,我使用您的解决方案准备了新的存储过程,并将其用于我的代码中。
您可以找到我在 this link
中创建的存储过程
你要找的答案可以是这个;
https://github.com/ersengultepe/mysql_hierarchy_recursive_procedure/
DROP PROCEDURE IF EXISTS store_procedure_name;
CREATE PROCEDURE `store_procedure_name`(IN cat_id INT)
BEGIN
declare loopId Int;
SET max_sp_recursion_depth = 255;
-- If the value of the category that comes as a parameter is not in the table as parent_id, no further action is required
IF(select count(id) from category_table where parent_id=cat_id) > 0 THEN
-- create temporary table
CREATE TEMPORARY TABLE IF NOT EXISTS temp_category_table (
`id` smallint(5) unsigned,
`status` tinyint(3)
) ENGINE=InnoDB ;
-- First, save the corresponding value in the temporary table.
INSERT INTO temp_category_table
(id, status)
VALUES (cat_id, 0);
-- continue loop as long as the appropriate record exists in the temporary table
WHILE (select count(id) from temp_category_table where status=0) > 0 DO
-- in this section, a record with a status of 0 will be extracted from the temporary table and assigned to the variable loopId
set loopId = (select id from temp_category_table where status=0 limit 1);
INSERT INTO temp_category_table
(id, status)
(select id, 0 from category_table where parent_id=loopId);
update temp_category_table set status=1 where id=loopId;
CALL store_procedure_name((select id from temp_category_table where status=0 limit 1));
END WHILE;
(select DISTINCT(id) from temp_category_table order by id );
END IF;
END;
试试这个,简单易懂。
(但只支持一个层级)
SET @pv = 1;
select * from tablename
where FIND_IN_SET(parentrecordID,@pv) and !isnull(@pv:= concat(@pv, ',', id));
在我看来,在分层 table 结构中找到所有 children 的关键是首先找到 parent 的路径,然后使用 FIND_IN_SET
查看请求的节点是否在路径中。向上搜索比向下搜索更容易和更有效,因为 link 到 parent 已经在 table.
中
所以让我们从这样的层次结构开始:
1 Pets
├─ 2 Dogs
│ ├─ 3 Katie
├─ 4 Cats
│ ├─ 5 George
│ ├─ 6 Pete
│ ├─ 7 Alice
├─ 8 Other
│ ├─ 9 Rabbits
│ │ ├─ 10 Noah
│ │ ├─ 11 Teddy
│ │ ├─ 12 Bella
│ ├─ 13 Rats
│ │ ├─ 14 Henry
现在你想找到类别 Other
下的所有 children(包括类别)那么预期结果将是:
8,9,10,11,12,13,14
现在让我们来看看Henry的层级路径。 Henry (14) 的 parent 是 Rats (13),其中有 parent Other (8) 最后 Pets (1)。如果我们使用 ID 为 Henry 创建路径,它将如下所示:
1,8,13,14
这就是 MySQL 函数 FIND_IN_SET
发挥作用的地方。使用 FIND_IN_SET
您可以过滤结果,其中可以在逗号分隔列表中找到变量。在此示例中,我们正在寻找类别 Other (8) 中的所有 children,我们可以简单地使用 FIND_IN_SET(8, path)
.
要获取分层 table 的路径,我想在 post 此处 MySql: ORDER BY parent and child 参考我的回答。我们只需将破折号更改为逗号,这样我们就可以使用 FIND_IN_SET
函数。
上面的例子在层次结构中看起来像这样 table:
+----+--------+---------+
| id | parent | name |
+----+--------+---------+
| 1 | NULL | Pets |
| 2 | 1 | Dogs |
| 3 | 2 | Katie |
| 4 | 1 | Cats |
| 5 | 4 | George |
| 6 | 4 | Pete |
| 7 | 4 | Alice |
| 8 | 1 | Other |
| 9 | 8 | Rabbits |
| 10 | 9 | Noah |
| 11 | 9 | Teddy |
| 12 | 9 | Bella |
| 13 | 8 | Rats |
| 14 | 13 | Henry |
+----+--------+---------+
在我的方法中,我将使用一个过程,该过程将递归调用自身并继续在路径前面加上请求的 id
的 parent,直到它到达 NULL
parent.
DELIMITER $$
CREATE DEFINER=`root`@`localhost` PROCEDURE `PATH`(IN `input` INT, OUT `output` VARCHAR(128))
BEGIN
DECLARE _id INT;
DECLARE _parent INT;
DECLARE _path VARCHAR(128);
SET `max_sp_recursion_depth` = 50;
SELECT `id`, `parent`
INTO _id, _parent
FROM `database`.`table`
WHERE `table`.`id` = `input`;
IF _parent IS NULL THEN
SET _path = _id;
ELSE
CALL `PATH`(_parent, _path);
SELECT CONCAT(_path, ',', _id) INTO _path;
END IF;
SELECT _path INTO `output`;
END $$
DELIMITER ;
我们在 SELECT
查询中需要这些结果,因此我们也需要一个 FUNCTION
来包装 PROCEDURE
.
的结果
DELIMITER $$
CREATE DEFINER=`root`@`localhost` FUNCTION `GETPATH`(`input` INT) RETURNS VARCHAR(128)
BEGIN
CALL `PATH`(`input`, @path);
RETURN @path;
END $$
DELIMITER ;
现在我们可以在查询中使用路径了。在具有 10000 行的 table 上,在我的工作站上只需要一秒多一点。
SELECT `id`, `parent`, `name`, GETPATH(`id`) `path` FROM `database`.`table`;
示例输出:
+----+--------+---------+-----------+
| id | parent | name | path |
+----+--------+---------+-----------+
| 1 | NULL | Pets | 1 |
| 2 | 1 | Dogs | 1,2 |
| 3 | 2 | Katie | 1,2,3 |
| 4 | 1 | Cats | 1,4 |
| 5 | 4 | George | 1,4,5 |
| 6 | 4 | Pete | 1,4,6 |
| 7 | 4 | Alice | 1,4,7 |
| 8 | 1 | Other | 1,8 |
| 9 | 8 | Rabbits | 1,8,9 |
| 10 | 9 | Noah | 1,8,9,10 |
| 11 | 9 | Teddy | 1,8,9,11 |
| 12 | 9 | Bella | 1,8,9,12 |
| 13 | 8 | Rats | 1,8,13 |
| 14 | 13 | Henry | 1,8,13,14 |
+----+--------+---------+-----------+
并且要查找 Other (8) 的所有 children 以及 Other 本身,我们可以使用与 FIND_IN_SET
:
相同的查询和过滤器
SELECT `id`, `parent`, `name`, GETPATH(`id`) `path` FROM `database`.`table` WHERE FIND_IN_SET(8, GETPATH(`id`));
最后是结果。我们在程序中设置了 50 级的递归限制,但除此之外我们在深度上没有限制。
+----+--------+---------+-----------+
| id | parent | name | path |
+----+--------+---------+-----------+
| 8 | 1 | Other | 1,8 |
| 9 | 8 | Rabbits | 1,8,9 |
| 10 | 9 | Noah | 1,8,9,10 |
| 11 | 9 | Teddy | 1,8,9,11 |
| 12 | 9 | Bella | 1,8,9,12 |
| 13 | 8 | Rats | 1,8,13 |
| 14 | 13 | Henry | 1,8,13,14 |
+----+--------+---------+-----------+
7 rows in set (0,01 sec)
如果您想要单个值而不是行,那么您可能需要像这样使用 GROUP_CONCAT
:
SELECT GROUP_CONCAT(`id`) `children` FROM `database`.`table` WHERE FIND_IN_SET(8, GETPATH(`id`));
给出以下结果:
+--------------------+
| children |
+--------------------+
| 8,9,10,11,12,13,14 |
+--------------------+
1 row in set (0,00 sec)
我有这种情况,在 Mysql 上使用递归查询在一个 table...
上找到 lv 2 和 lv3 child
我正在使用的数据库结构:
id name parent
1 A 0
2 B 0
3 C 0
4 D 1
5 E 1
6 F 2
7 G 2
8 H 3
9 I 3
10 J 4
11 K 4
我期待的结果,在过滤数据时,id=1,它会生成我期待的结果。
id name parent
4 D 1
5 E 1
10 J 4
11 K 4
或者这是插图。
我一直在到处寻找,并阅读这篇文章http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/,但我没有找到我正在寻找的结果..
任何帮助将不胜感激,谢谢
SELECT *
FROM TABLENAME
WHERE PARENT = 1
UNION
SELECT *
FROM TABLENAME
WHERE PARENT IN
(SELECT ID FROM TABLENAME WHERE PARENT = 1)
试试这个,更快
SELECT *
FROM table AS T1
INNER JOIN (SELECT id FROM table WHERE parent = 1) AS T2
ON T2.id = T1.parent OR T1.parent = 1
GROUP BY T1.id
如果你想获得特定 parent 的所有等级 child 那么你应该试试这个
select id,
name,
parent
from (select * from tablename
order by parent, id) tablename,
(select @pv := '1') initialisation
where find_in_set(parent, @pv) > 0
and @pv := concat(@pv, ',', id)
我试过这个
select id from (select * from roles order by parent_role, id) roles,(select @pv := '1') initialisation
where find_in_set(parent_role, @pv) > 0
and @pv := concat(@pv, ',', id)
但它只适用于深度 2,我需要让它在更深的深度下工作,因为我有 8 个级别
谢谢@Manoj Rana
您可以找到我在 this link
中创建的存储过程你要找的答案可以是这个; https://github.com/ersengultepe/mysql_hierarchy_recursive_procedure/
DROP PROCEDURE IF EXISTS store_procedure_name;
CREATE PROCEDURE `store_procedure_name`(IN cat_id INT)
BEGIN
declare loopId Int;
SET max_sp_recursion_depth = 255;
-- If the value of the category that comes as a parameter is not in the table as parent_id, no further action is required
IF(select count(id) from category_table where parent_id=cat_id) > 0 THEN
-- create temporary table
CREATE TEMPORARY TABLE IF NOT EXISTS temp_category_table (
`id` smallint(5) unsigned,
`status` tinyint(3)
) ENGINE=InnoDB ;
-- First, save the corresponding value in the temporary table.
INSERT INTO temp_category_table
(id, status)
VALUES (cat_id, 0);
-- continue loop as long as the appropriate record exists in the temporary table
WHILE (select count(id) from temp_category_table where status=0) > 0 DO
-- in this section, a record with a status of 0 will be extracted from the temporary table and assigned to the variable loopId
set loopId = (select id from temp_category_table where status=0 limit 1);
INSERT INTO temp_category_table
(id, status)
(select id, 0 from category_table where parent_id=loopId);
update temp_category_table set status=1 where id=loopId;
CALL store_procedure_name((select id from temp_category_table where status=0 limit 1));
END WHILE;
(select DISTINCT(id) from temp_category_table order by id );
END IF;
END;
试试这个,简单易懂。
(但只支持一个层级)
SET @pv = 1;
select * from tablename
where FIND_IN_SET(parentrecordID,@pv) and !isnull(@pv:= concat(@pv, ',', id));
在我看来,在分层 table 结构中找到所有 children 的关键是首先找到 parent 的路径,然后使用 FIND_IN_SET
查看请求的节点是否在路径中。向上搜索比向下搜索更容易和更有效,因为 link 到 parent 已经在 table.
所以让我们从这样的层次结构开始:
1 Pets
├─ 2 Dogs
│ ├─ 3 Katie
├─ 4 Cats
│ ├─ 5 George
│ ├─ 6 Pete
│ ├─ 7 Alice
├─ 8 Other
│ ├─ 9 Rabbits
│ │ ├─ 10 Noah
│ │ ├─ 11 Teddy
│ │ ├─ 12 Bella
│ ├─ 13 Rats
│ │ ├─ 14 Henry
现在你想找到类别 Other
下的所有 children(包括类别)那么预期结果将是:
8,9,10,11,12,13,14
现在让我们来看看Henry的层级路径。 Henry (14) 的 parent 是 Rats (13),其中有 parent Other (8) 最后 Pets (1)。如果我们使用 ID 为 Henry 创建路径,它将如下所示:
1,8,13,14
这就是 MySQL 函数 FIND_IN_SET
发挥作用的地方。使用 FIND_IN_SET
您可以过滤结果,其中可以在逗号分隔列表中找到变量。在此示例中,我们正在寻找类别 Other (8) 中的所有 children,我们可以简单地使用 FIND_IN_SET(8, path)
.
要获取分层 table 的路径,我想在 post 此处 MySql: ORDER BY parent and child 参考我的回答。我们只需将破折号更改为逗号,这样我们就可以使用 FIND_IN_SET
函数。
上面的例子在层次结构中看起来像这样 table:
+----+--------+---------+
| id | parent | name |
+----+--------+---------+
| 1 | NULL | Pets |
| 2 | 1 | Dogs |
| 3 | 2 | Katie |
| 4 | 1 | Cats |
| 5 | 4 | George |
| 6 | 4 | Pete |
| 7 | 4 | Alice |
| 8 | 1 | Other |
| 9 | 8 | Rabbits |
| 10 | 9 | Noah |
| 11 | 9 | Teddy |
| 12 | 9 | Bella |
| 13 | 8 | Rats |
| 14 | 13 | Henry |
+----+--------+---------+
在我的方法中,我将使用一个过程,该过程将递归调用自身并继续在路径前面加上请求的 id
的 parent,直到它到达 NULL
parent.
DELIMITER $$
CREATE DEFINER=`root`@`localhost` PROCEDURE `PATH`(IN `input` INT, OUT `output` VARCHAR(128))
BEGIN
DECLARE _id INT;
DECLARE _parent INT;
DECLARE _path VARCHAR(128);
SET `max_sp_recursion_depth` = 50;
SELECT `id`, `parent`
INTO _id, _parent
FROM `database`.`table`
WHERE `table`.`id` = `input`;
IF _parent IS NULL THEN
SET _path = _id;
ELSE
CALL `PATH`(_parent, _path);
SELECT CONCAT(_path, ',', _id) INTO _path;
END IF;
SELECT _path INTO `output`;
END $$
DELIMITER ;
我们在 SELECT
查询中需要这些结果,因此我们也需要一个 FUNCTION
来包装 PROCEDURE
.
DELIMITER $$
CREATE DEFINER=`root`@`localhost` FUNCTION `GETPATH`(`input` INT) RETURNS VARCHAR(128)
BEGIN
CALL `PATH`(`input`, @path);
RETURN @path;
END $$
DELIMITER ;
现在我们可以在查询中使用路径了。在具有 10000 行的 table 上,在我的工作站上只需要一秒多一点。
SELECT `id`, `parent`, `name`, GETPATH(`id`) `path` FROM `database`.`table`;
示例输出:
+----+--------+---------+-----------+
| id | parent | name | path |
+----+--------+---------+-----------+
| 1 | NULL | Pets | 1 |
| 2 | 1 | Dogs | 1,2 |
| 3 | 2 | Katie | 1,2,3 |
| 4 | 1 | Cats | 1,4 |
| 5 | 4 | George | 1,4,5 |
| 6 | 4 | Pete | 1,4,6 |
| 7 | 4 | Alice | 1,4,7 |
| 8 | 1 | Other | 1,8 |
| 9 | 8 | Rabbits | 1,8,9 |
| 10 | 9 | Noah | 1,8,9,10 |
| 11 | 9 | Teddy | 1,8,9,11 |
| 12 | 9 | Bella | 1,8,9,12 |
| 13 | 8 | Rats | 1,8,13 |
| 14 | 13 | Henry | 1,8,13,14 |
+----+--------+---------+-----------+
并且要查找 Other (8) 的所有 children 以及 Other 本身,我们可以使用与 FIND_IN_SET
:
SELECT `id`, `parent`, `name`, GETPATH(`id`) `path` FROM `database`.`table` WHERE FIND_IN_SET(8, GETPATH(`id`));
最后是结果。我们在程序中设置了 50 级的递归限制,但除此之外我们在深度上没有限制。
+----+--------+---------+-----------+
| id | parent | name | path |
+----+--------+---------+-----------+
| 8 | 1 | Other | 1,8 |
| 9 | 8 | Rabbits | 1,8,9 |
| 10 | 9 | Noah | 1,8,9,10 |
| 11 | 9 | Teddy | 1,8,9,11 |
| 12 | 9 | Bella | 1,8,9,12 |
| 13 | 8 | Rats | 1,8,13 |
| 14 | 13 | Henry | 1,8,13,14 |
+----+--------+---------+-----------+
7 rows in set (0,01 sec)
如果您想要单个值而不是行,那么您可能需要像这样使用 GROUP_CONCAT
:
SELECT GROUP_CONCAT(`id`) `children` FROM `database`.`table` WHERE FIND_IN_SET(8, GETPATH(`id`));
给出以下结果:
+--------------------+
| children |
+--------------------+
| 8,9,10,11,12,13,14 |
+--------------------+
1 row in set (0,00 sec)