如何创建一个MySQL层次递归查询?

438
我有一个MySQL表格的结构如下:
``` id name parent_id ----------------------------- 19 category1 0 20 category2 19 21 category3 20 22 category4 21 ... ... ... ```
现在,我想要一个单一的MySQL查询语句,它可以根据给定的id(例如 `id=19`)返回所有子id(即结果应该是 `20,21,22`)......
这些子项的层次结构是不确定的,可能会变化......
我知道如何使用 `for` 循环来实现此操作......但如何使用单个MySQL查询语句实现呢?

假设层次结构深度为7级。您期望输出表格会是什么样子? - Jonathan Leffler
2
MySQL(仍然)不支持分层查询(像其他现代数据库管理系统一样)。您需要编写存储过程或使用不同的数据模型。 - a_horse_with_no_name
3
MYSQL 8.0 将支持使用 CTE(公共表达式)的递归查询。 - user3712320
获取从最后一条评论ID开始的所有帖子列表怎么样?或者最后一个子级呢? - joe
16个回答

642

对于MySQL 8+:请使用递归的with语法。
对于MySQL 5.x:请使用内联变量、路径ID或自连接。

MySQL 8+

with recursive cte (id, name, parent_id) as (
  select     id,
             name,
             parent_id
  from       products
  where      parent_id = 19
  union all
  select     p.id,
             p.name,
             p.parent_id
  from       products p
  inner join cte
          on p.parent_id = cte.id
)
select * from cte;

parent_id = 19中指定的值设置为您要选择所有后代的父级id

MySQL 5.x

对于不支持通用表达式(直到版本5.7)的MySQL版本,您可以使用以下查询实现:

select  id,
        name,
        parent_id 
from    (select * from products
         order by parent_id, id) products_sorted,
        (select @pv := '19') initialisation
where   find_in_set(parent_id, @pv)
and     length(@pv := concat(@pv, ',', id))

这里是一个演示

在这个查询中,@pv := '19' 中指定的值应该设置为您想选择所有子代的父项的id

如果一个父项有多个子项,这也可以工作。但是,必须满足每个记录的条件parent_id < id,否则结果将不完整。

查询内变量赋值

此查询使用特定的MySQL语法:变量在执行期间被分配和修改。对执行顺序做出了一些假设:

  • 首先评估from语句。因此,在那里初始化了@pv
  • 按照从from别名检索的顺序为每个记录评估where子句。因此,在这里会加上一个条件,只包括已确定为在后代树中的父项的记录(主要父项的所有后代都逐步添加到@pv中)。
  • where子句中的条件按顺序评估,并且在总结果确定后中断评估。因此,第二个条件必须位于第二个位置,因为它将id添加到父项列表中,仅在id通过第一个条件时才会发生。调用length函数只是为了确保此条件始终为真,即使由于某种原因pv字符串产生错误值也一样。

总之,您可能会发现这些假设过于冒险以依赖。文档警告:

您可能会获得所期望的结果,但这并不保证[...]涉及用户变量的表达式的评估顺序未定义。

因此,即使它与上述查询一致地工作,评估顺序仍可能更改,例如添加条件或在更大的查询中使用此查询作为视图或子查询。这是一个“功能”,将在MySQL的未来版本中删除

MySQL的早期版本使得在语句中除SET外的其他语句中为用户变量分配值成为可能。尽管基于向后兼容性,MySQL 8.0支持这种功能,但可能会在将来的MySQL版本中被删除。

如上所述,从MySQL 8.0开始,您应该使用递归的with语法。

效率

对于非常大的数据集,此解决方案可能会变慢,因为在列表中查找数字的find_in_set操作不是最理想的方式,特别是在列表大小与返回记录数相同数量级时。

备选方案1:with recursiveconnect by
越来越多的数据库实现 SQL:1999 ISO 标准 WITH [RECURSIVE] 语法用于递归查询(例如,Postgres 8.4+SQL Server 2005+DB2Oracle 11gR2+SQLite 3.8.4+Firebird 2.1+H2HyperSQL 2.1.0+TeradataMariaDB 10.2.2+)。从8.0版本开始,MySQL也支持它。请参见本答案顶部的要使用的语法。

有些数据库具有替代的非标准层次查询语法,例如在OracleDB2InformixCUBRID以及其他数据库中可用的CONNECT BY子句。

MySQL版本5.7不提供此功能。如果您的数据库引擎提供了此语法或者您可以迁移到支持此功能的数据库,则这肯定是最好的选择。如果没有,请考虑以下替代方案。

替代方案2:路径样式标识符

如果您将包含分层信息的id值分配给对象,则情况会变得更加容易。例如,在您的情况下,可能如下所示:

ID 名称
19 category1
19/1 category2
19/1/1 category3
19/1/1/1 category4

然后您的select将如下所示:

select  id,
        name 
from    products
where   id like '19/%'

替代方案3:重复自连接

如果你知道你的分层树可能有多深,你可以使用一个标准的sql查询,例如:

select      p6.parent_id as parent6_id,
            p5.parent_id as parent5_id,
            p4.parent_id as parent4_id,
            p3.parent_id as parent3_id,
            p2.parent_id as parent2_id,
            p1.parent_id as parent_id,
            p1.id as product_id,
            p1.name
from        products p1
left join   products p2 on p2.id = p1.parent_id 
left join   products p3 on p3.id = p2.parent_id 
left join   products p4 on p4.id = p3.parent_id  
left join   products p5 on p5.id = p4.parent_id  
left join   products p6 on p6.id = p5.parent_id
where       19 in (p1.parent_id, 
                   p2.parent_id, 
                   p3.parent_id, 
                   p4.parent_id, 
                   p5.parent_id, 
                   p6.parent_id) 
order       by 1, 2, 3, 4, 5, 6, 7;

请查看这个 fiddle

where条件指定了您想要检索后代的父级。如有需要,您可以扩展此查询以获取更多级别。


3
@Avión,这不是你需要将某些东西放在某处,而是一个要求,即对于所有记录,这个条件都必须成立。如果你有一个或多个记录的parent_id > id,那么你就不能使用这个解决方案。 - trincot
2
@trincot,是否有可能将其改为“反向”工作?因此获取所有行的父级、祖父母等?我已经使用了您的第一个查询来获取后代,但我也想获取祖先。 - shreddish
1
是的,这是可能的,而且更容易。如果您理解了此答案中使用的逻辑,那么做到这一点就不难。如果遇到问题,请提出新问题。 - trincot
2
对于任何想要使用WITH RECURSIVE方法的人,我发现以下文章在递归深度、去重、检测和关闭循环等不同场景下非常有帮助。 - Horse
2
如果其他人正在寻找@shreddish提出的问题的答案,解决方案是将on p.parent_id = cte.id更改为on p.id = cte.parent_id - fanfare
显示剩余26条评论

92

来自博客《在 MySQL 中管理分层数据》

表结构

+-------------+----------------------+--------+
| category_id | name                 | parent |
+-------------+----------------------+--------+
|           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 |
+-------------+----------------------+--------+

查询:

SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
LEFT JOIN category AS t3 ON t3.parent = t2.category_id
LEFT JOIN category AS t4 ON t4.parent = t3.category_id
WHERE t1.name = 'ELECTRONICS';

输出

+-------------+----------------------+--------------+-------+
| lev1        | lev2                 | lev3         | lev4  |
+-------------+----------------------+--------------+-------+
| ELECTRONICS | TELEVISIONS          | TUBE         | NULL  |
| ELECTRONICS | TELEVISIONS          | LCD          | NULL  |
| ELECTRONICS | TELEVISIONS          | PLASMA       | NULL  |
| ELECTRONICS | PORTABLE ELECTRONICS | MP3 PLAYERS  | FLASH |
| ELECTRONICS | PORTABLE ELECTRONICS | CD PLAYERS   | NULL  |
| ELECTRONICS | PORTABLE ELECTRONICS | 2 WAY RADIOS | NULL  |
+-------------+----------------------+--------------+-------+

大多数用户在某个时候都需要处理SQL数据库中的分层数据,并且毫无疑问地了解到,管理分层数据并不是关系型数据库的本意。关系型数据库的表不是分层的(像XML一样),而只是一个平面列表。分层数据具有父子关系,这种关系在关系型数据库表中没有自然表示。

阅读更多

请参考博客获取更多详细信息。

编辑:

select @pv:=category_id as category_id, name, parent from category
join
(select @pv:=19)tmp
where parent=@pv

输出:

category_id name    parent
19  category1   0
20  category2   19
21  category3   20
22  category4   21

参考:如何在Mysql中执行递归SELECT查询?


30
只要层级不超过4级,那就没问题。如果有N层级,你需要知道这一点才能正确创建查询。 - Jonathan Leffler
2
@Damodaran,感谢您的回复...我需要的是一个条件,其中子项的数量是未知的...在博客中,使用了内部连接概念,其中需要知道层次结构,而这在我的情况下不是必需的...所以请让我知道您对此的看法...因此,简单地说,我需要一个查询来处理“n”个层次级别,其中“n”是未知的... - Tarun Parswani
1
@user3036105:在MySQL中,使用单个 SQL查询无法实现这一点。MySQL并不足够先进。如果您真的需要这个功能,请考虑升级到支持递归查询的DBMS。 - user330315
5
许多用户在使用SQL数据库时,不可避免地会涉及到分层数据的处理,他们也许已经了解到关系型数据库并不适用于管理分层数据。如果你是指MySQL数据库,那么这一点可能是正确的。但是,Oracle数据库可以很好地处理和查询分层数据。 - Peter Nosko
1
“管理分层数据并不是关系型数据库的初衷...”虽然这可能不是关系型数据库最初的意图,但在现实世界中,分层数据非常普遍,MySQL应该反映人们在实际场景中需要使用数据的方式。 - Dave L
显示剩余10条评论

19

请尝试以下内容:

表格定义:

DROP TABLE IF EXISTS category;
CREATE TABLE category (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(20),
    parent_id INT,
    CONSTRAINT fk_category_parent FOREIGN KEY (parent_id)
    REFERENCES category (id)
) engine=innodb;

实验行:

INSERT INTO category VALUES
(19, 'category1', NULL),
(20, 'category2', 19),
(21, 'category3', 20),
(22, 'category4', 21),
(23, 'categoryA', 19),
(24, 'categoryB', 23),
(25, 'categoryC', 23),
(26, 'categoryD', 24);

递归存储过程:

DROP PROCEDURE IF EXISTS getpath;
DELIMITER $$
CREATE PROCEDURE getpath(IN cat_id INT, OUT path TEXT)
BEGIN
    DECLARE catname VARCHAR(20);
    DECLARE temppath TEXT;
    DECLARE tempparent INT;
    SET max_sp_recursion_depth = 255;
    SELECT name, parent_id FROM category WHERE id=cat_id INTO catname, tempparent;
    IF tempparent IS NULL
    THEN
        SET path = catname;
    ELSE
        CALL getpath(tempparent, temppath);
        SET path = CONCAT(temppath, '/', catname);
    END IF;
END$$
DELIMITER ;

存储过程的包装函数:

DROP FUNCTION IF EXISTS getpath;
DELIMITER $$
CREATE FUNCTION getpath(cat_id INT) RETURNS TEXT DETERMINISTIC
BEGIN
    DECLARE res TEXT;
    CALL getpath(cat_id, res);
    RETURN res;
END$$
DELIMITER ;

选择示例:

SELECT id, name, getpath(id) AS path FROM category;

输出:

+----+-----------+-----------------------------------------+
| id | name      | path                                    |
+----+-----------+-----------------------------------------+
| 19 | category1 | category1                               |
| 20 | category2 | category1/category2                     |
| 21 | category3 | category1/category2/category3           |
| 22 | category4 | category1/category2/category3/category4 |
| 23 | categoryA | category1/categoryA                     |
| 24 | categoryB | category1/categoryA/categoryB           |
| 25 | categoryC | category1/categoryA/categoryC           |
| 26 | categoryD | category1/categoryA/categoryB/categoryD |
+----+-----------+-----------------------------------------+

筛选包含特定路径的行:

SELECT id, name, getpath(id) AS path FROM category HAVING path LIKE 'category1/category2%';

输出:

+----+-----------+-----------------------------------------+
| id | name      | path                                    |
+----+-----------+-----------------------------------------+
| 20 | category2 | category1/category2                     |
| 21 | category3 | category1/category2/category3           |
| 22 | category4 | category1/category2/category3/category4 |
+----+-----------+-----------------------------------------+

1
这对于多个子项不起作用。例如 (20, 'category2', 19), (21, 'category3', 20), (22, 'category4', 20), - Basheer Kharoti
6
我相信这种方法适用于不止一个孩子,我甚至重新测试过了。 - Fandi Susanto
@Fandi Susanto,谢谢你,这对我很有帮助。 - Dogan Ozer
这个解决方案对我来说是有效的,但重要的是要检查顶层父级(类别)是否由 parent_id 标识为 NULL 或 0。因此,tempparent 检查必须像这样:IF (tempparent IS NULL OR tempparent = 0) - Benjamin
谢谢!对我很有帮助。只需要在我的情况下将“IF tempparent IS NULL”更改为“IF tempparent = 0”即可。 - Pkchkchiseu

16

在这里为另一个问题做了同样的事情。

Mysql查询递归获取多级所有子项

查询将会是:

SELECT GROUP_CONCAT(lv SEPARATOR ',') FROM (
  SELECT @pv:=(
    SELECT GROUP_CONCAT(id SEPARATOR ',')
    FROM table WHERE parent_id IN (@pv)
  ) AS lv FROM table 
  JOIN
  (SELECT @pv:=1)tmp
  WHERE parent_id IN (@pv)
) a;

我们该怎么做?SELECT idFolder, (SELECT GROUP_CONCAT(lv SEPARATOR ',') FROM ( SELECT @pv:=(SELECT GROUP_CONCAT(idFolder SEPARATOR ',') FROM Folder WHERE idFolderParent IN (@pv)) AS lv FROM Folder JOIN (SELECT @pv:= F1.idFolder )tmp WHERE idFolderParent IN (@pv)) a) from folder F1 where id > 10;我无法将F1.idFolder用于@pv。 - Rahul
我使用OP原始问题中他们在评论中展示的数据重新创建了表格,然后在此处运行了您的查询,结果只得到了一个NULL。您知道这可能是为什么吗?在数据库引擎方面是否有前提条件,或者自从您回答这个问题以来是否发生了一些变化,使得这个查询已经过时了? - Digital Ninja

12

如果您需要快速阅读速度,则最好使用闭包表。闭包表包含每个祖先/后代对的一行。因此,在您的示例中,闭包表看起来像

ancestor | descendant | depth
0        | 0          | 0
0        | 19         | 1
0        | 20         | 2
0        | 21         | 3
0        | 22         | 4
19       | 19         | 0
19       | 20         | 1
19       | 21         | 3
19       | 22         | 4
20       | 20         | 0
20       | 21         | 1
20       | 22         | 2
21       | 21         | 0
21       | 22         | 1
22       | 22         | 0

一旦您拥有这个表,层次查询变得非常容易和快速。要获取类别20的所有后代:

SELECT cat.* FROM categories_closure AS cl
INNER JOIN categories AS cat ON cat.id = cl.descendant
WHERE cl.ancestor = 20 AND cl.depth > 0

当你使用类似于这样的非规范化数据时,当然存在一个巨大的缺点。您需要在维护类别表的同时维护闭包表。最好的方法可能是使用触发器,但要正确跟踪闭包表的插入/更新/删除有些复杂。与任何事物一样,您需要查看您的需求并决定哪种方法最适合您。

编辑:有关更多选项,请参见问题“在关系型数据库中存储分层数据的选项是什么?”。不同的情况有不同的最佳解决方案。


12
根据@trincot的回答,解释得非常好,我使用WITH RECURSIVE ()语句来创建一个“面包屑导航”,使用当前页面的id并沿着层级向后查找route表中的每个parent。因此,这里调整了@trincot的解决方案以相反的方向查找父级而不是后代。我还添加了depth值,它对于反转结果顺序非常有用(否则面包屑导航将颠倒)。
WITH RECURSIVE cte (
    `id`,
    `title`,
    `url`,
    `icon`,
    `class`,
    `parent_id`,
    `depth`
) AS (
    SELECT   
        `id`,
        `title`,
        `url`,
        `icon`,
        `class`,
        `parent_id`,
        1 AS `depth` 
    FROM     `route`
    WHERE    `id` = :id
      
    UNION ALL 
    SELECT 
        P.`id`,
        P.`title`,
        P.`url`,
        P.`icon`,
        P.`class`,
        P.`parent_id`,
        `depth` + 1
    FROM `route` P
        
    INNER JOIN cte
        ON P.`id` = cte.`parent_id`
)
SELECT * FROM cte ORDER BY `depth` DESC;

在升级到 MySQL 8+ 之前,我使用的是 vars 但它已被弃用,在我的 8.0.22 版本上不再工作!

编辑 2021-02-19: 分层菜单示例

在 @david 的评论后,我决定尝试制作一个完整的分层菜单,包括所有节点,并按照我想要的排序(使用sorting列对每个深度中的项目进行排序)。对于我的用户/授权矩阵页面非常有用。

这真的简化了我旧版本中深度循环查询的方式(PHP循环).

ERP 授权矩阵

此示例将 INNER JOIN 与 url 表集成,以按网站(多网站 CMS 系统)过滤路由。

您可以看到必要的path 列,其中包含CONCAT()函数以正确排序菜单。

SELECT R.* FROM (
    WITH RECURSIVE cte (
        `id`,
        `title`,
        `url`,
        `icon`,
        `class`,
        `parent`,
        `depth`,
        `sorting`,
        `path`
    ) AS (
        SELECT 
            `id`,
            `title`,
            `url`,
            `icon`,
            `class`,
            `parent`,
            1 AS `depth`,
            `sorting`,
            CONCAT(`sorting`, ' ' , `title`) AS `path`
        FROM `route`
        WHERE `parent` = 0
        UNION ALL SELECT 
            D.`id`,
            D.`title`,
            D.`url`,
            D.`icon`,
            D.`class`,
            D.`parent`,
            `depth` + 1,
            D.`sorting`,
            CONCAT(cte.`path`, ' > ', D.`sorting`, ' ' , D.`title`)
        FROM `route` D
        INNER JOIN cte
            ON cte.`id` = D.`parent`
    )
    SELECT * FROM cte
) R

INNER JOIN `url` U
    ON R.`id` = U.`route_id`
    AND U.`site_id` = 1

ORDER BY `path` ASC  

1
我用它来添加一个有用的面包屑路径注释,但也可以用于菜单。谢谢!顺便说一句,我将其作为@trincot解决方案的补充使用。 - David
1
是的 @David,对于没有预定义深度的多级菜单,我们可以使用它,之前没有想到,谢谢。 - Meloman
1
我刚刚注意到了你的编辑,感谢给予信用;-) 你可能会对这个讨论感兴趣:https://gist.github.com/DavidBruchmann/cf27eb309e48e0df326b3bafce2b30e3 - David
嵌套父子节点的超级子查询。 - ArrayIterator

11

我想到的最佳方法是:

  1. 使用族谱来存储、排序和跟踪树。这足够了,而且在阅读方面比任何其他方法都快数千倍。 它还允许保持该模式,即使DB将发生更改(因为任何DB都允许使用该模式)
  2. 使用确定特定ID的家族关系的函数。
  3. 按照您的希望使用它(在选择中,或在CUD操作中,甚至通过作业)。

可以在任何地方找到族谱方法描述,例如这里这里。 至于函数- 这个就是启发我的。

最终-得到了相对简单、相对快速和简单的解决方案。

函数的内容

-- --------------------------------------------------------------------------------
-- Routine DDL
-- Note: comments before and after the routine body will not be stored by the server
-- --------------------------------------------------------------------------------
DELIMITER $$

CREATE DEFINER=`root`@`localhost` FUNCTION `get_lineage`(the_id INT) RETURNS text CHARSET utf8
    READS SQL DATA
BEGIN

 DECLARE v_rec INT DEFAULT 0;

 DECLARE done INT DEFAULT FALSE;
 DECLARE v_res text DEFAULT '';
 DECLARE v_papa int;
 DECLARE v_papa_papa int DEFAULT -1;
 DECLARE csr CURSOR FOR 
  select _id,parent_id -- @n:=@n+1 as rownum,T1.* 
  from 
    (SELECT @r AS _id,
        (SELECT @r := table_parent_id FROM table WHERE table_id = _id) AS parent_id,
        @l := @l + 1 AS lvl
    FROM
        (SELECT @r := the_id, @l := 0,@n:=0) vars,
        table m
    WHERE @r <> 0
    ) T1
    where T1.parent_id is not null
 ORDER BY T1.lvl DESC;
 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    open csr;
    read_loop: LOOP
    fetch csr into v_papa,v_papa_papa;
        SET v_rec = v_rec+1;
        IF done THEN
            LEAVE read_loop;
        END IF;
        -- add first
        IF v_rec = 1 THEN
            SET v_res = v_papa_papa;
        END IF;
        SET v_res = CONCAT(v_res,'-',v_papa);
    END LOOP;
    close csr;
    return v_res;
END

然后你只需要

select get_lineage(the_id)

希望它能帮助到某个人 :)


7

这里有一种方法,虽然与已接受答案的第二个替代方案有点相似,但对于大型层次查询和易于(插入更新删除)项目而言成本较低且简单,那就是为每个项目添加一个持久路径列。

具体来说,可以像下面这样实现:

id | name        | path
19 | category1   | /19
20 | category2   | /19/20
21 | category3   | /19/20/21
22 | category4   | /19/20/21/22

例子:

-- get children of category3:
SELECT * FROM my_table WHERE path LIKE '/19/20/21%'
-- Reparent an item:
UPDATE my_table SET path = REPLACE(path, '/19/20', '/15/16') WHERE path LIKE '/19/20/%'

使用Base36编码而不是实际的数字路径ID来优化路径长度和ORDER BY path

 // base10 => base36
 '1' => '1',
 '10' => 'A',
 '100' => '2S',
 '1000' => 'RS',
 '10000' => '7PS',
 '100000' => '255S',
 '1000000' => 'LFLS',
 '1000000000' => 'GJDGXS',
 '1000000000000' => 'CRE66I9S'

https://en.wikipedia.org/wiki/Base36

通过使用固定长度和填充编码的id,还可以抑制斜杠'/'分隔符。

详细的优化解释在这里: https://bojanz.wordpress.com/2014/04/25/storing-hierarchical-data-materialized-path/

待办事项

构建一个函数或过程来拆分路径以检索一个项目的祖先。


谢谢!base36很有趣。 - Vlad
@MTK,我们能否按照DESC的方式获取结果,就像获取帖子评论中的最新回复一样? - Liki Crus
@LikiCrus 我用它来进行分层查询。如果你想按最新回复排序,我认为你必须玩弄评论的日期。例如:Select comment FROM comments WHERE ... (subject or user or theme or whatever condition) ... ORDER BY posting_date DESC 或者玩弄 GROUP BY user ODER BY posting date 在这里也可以看看 https://dev59.com/LG435IYBdhLWcg3wigkk - MTK
@MTK,我认为你的“path”方法无法获得“DESC”结果。因为“path”本身只支持ASC。 - Liki Crus
@LikiCrus 我之前说过,你必须使用另一列来实现这个目的,而不是路径列。例如日期、ID等。路径列用于分层。 - MTK

6

列出第一次递归的子级的简单查询:

select @pv:=id as id, name, parent_id
from products
join (select @pv:=19)tmp
where parent_id=@pv

结果:

id  name        parent_id
20  category2   19
21  category3   20
22  category4   21
26  category24  22

...使用左连接:

select
    @pv:=p1.id as id
  , p2.name as parent_name
  , p1.name name
  , p1.parent_id
from products p1
join (select @pv:=19)tmp
left join products p2 on p2.id=p1.parent_id -- optional join to get parent name
where p1.parent_id=@pv

@tincot的解决方案列出所有子项:
select  id,
        name,
        parent_id 
from    (select * from products
         order by parent_id, id) products_sorted,
        (select @pv := '19') initialisation
where   find_in_set(parent_id, @pv) > 0
and     @pv := concat(@pv, ',', id)

使用Sql Fiddle在线测试,并查看所有结果。

http://sqlfiddle.com/#!9/a318e3/4/0


4

你可以通过递归查询(性能因数据库而异)在其他数据库中很容易地完成此操作。

另一种方法是存储两个额外的数据,即左值和右值。左值和右值是从所表示的树结构的前序遍历中派生出来的。

这被称为修改过的前序树遍历,它使您可以运行简单的查询一次获取所有父级值。它也被称为“嵌套集”。


我想在你的评论下添加一个类似的评论,但既然你已经这样做了,我只会添加一个好的“嵌套集”示例链接:http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/ - Miroslaw Opoka

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接