我认为在层次结构表中查找所有子节点的关键是首先找到父节点的路径,然后使用 FIND_IN_SET
来查看请求的节点是否在路径中。向上搜索比向下搜索更容易和更有效,因为表中已经存在到父节点的链接。
让我们以这样的层次结构开始:
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
的所有子级(包括类别本身),则预期结果如下:
8,9,10,11,12,13,14
现在让我们来看一下Henry的层次路径。Henry(14)的父亲是Rats(13),它有一个父亲是Other(8),最终是Pets(1)。如果我们使用ID来为Henry制作路径,它看起来像这样:
1,8,13,14
这就是MySQL函数
FIND_IN_SET
发挥作用的地方。使用
FIND_IN_SET
函数,您可以过滤掉一个变量在逗号分隔列表中出现的结果。在这个例子中,我们正在寻找所有属于Other(8)类别的孩子,我们可以简单地使用
FIND_IN_SET(8, path)
。
要获取分层表的路径,我想引用我在这篇文章中的答案
MySql: ORDER BY parent and child。只需将破折号更改为逗号,这样我们就可以使用
FIND_IN_SET
函数。
在分层表中,上面的示例将如下所示:
+----+--------+---------+
| 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
的父级前加上路径,直到达到
NULL
父级。
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行的表格,只需要略过一秒钟。
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)的所有子元素(包括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)