如何创建MySQL分层递归查询?

How to create a MySQL hierarchical recursive query?

提问人:Tarun Parswani 提问时间:11/26/2013 最后编辑:trincotTarun Parswani 更新时间:2/19/2021 访问量:443277

问:

我有一个MySQL表,如下所示:

编号 名字 parent_id
19 类别1 0
20 类别2 19
21 类别3 20
22 类别4 21
... ... ...

现在,我想要一个MySQL查询,我只需提供id[例如,说],然后我应该获取其所有子id[即结果应具有ids'20,21,22']......id=19

子级的等级制度尚不清楚;它可能会有所不同......

我知道如何使用循环来做到这一点......但是如何使用单个MySQL查询实现相同的目的呢?for

mysql sql 分层数据 递归查询

评论

0赞 Jonathan Leffler 11/26/2013
假设层次结构有 7 个级别。您期望输出表是什么样子的?
1赞 philipxy 2/7/2017
关系数据库中存储分层数据的选项有哪些可能重复?
3赞 user3712320 8/4/2017
MYSQL 8.0 将支持使用 CTE(通用表表达式)的递归查询

答:

92赞 Damodaran 11/26/2013 #1

摘自博客 在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赞 Jonathan Leffler 11/26/2013
只要层次结构中最多不超过 4 个级别,就可以了。如果有 N 个级别,则必须知道该级别才能正确创建查询。
2赞 Tarun Parswani 11/26/2013
@Damodaran,感谢您的回复...我需要的是一个不知道孩子数量的情况......在使用内部连接概念的博客中,需要知道层次结构,这在我的情况下不是......所以让我知道你对同样的看法......所以,简单来说,我需要一个查询来处理“n”未知的“n”层次......
1赞 11/26/2013
@user3036105:在MySQL中,使用单个SQL查询无法执行此操作。MySQL根本不够先进。如果确实需要此功能,请考虑升级到支持递归查询的 DBMS。
5赞 Peter Nosko 9/19/2014
>大多数用户都曾处理过 SQL 数据库中的分层数据,毫无疑问,他们了解到分层数据的管理并不是关系数据库的用途。也许你的意思是MySQL数据库。Oracle 数据库可以很好地处理分层数据和查询。
1赞 Dave L 9/28/2016
"...分层数据的管理不是关系数据库的本意......”虽然这可能不是关系数据库的初衷,但在现实世界中,分层数据非常普遍,MySQL应该反映人们在现实世界场景中实际需要如何使用他们的数据。
2赞 senK 11/27/2013 #2

这有点棘手,请检查它是否适合您

select a.id,if(a.parent = 0,@varw:=concat(a.id,','),@varw:=concat(a.id,',',@varw)) as list from (select * from recursivejoin order by if(parent=0,id,parent) asc) a left join recursivejoin b on (a.id = b.parent),(select @varw:='') as c  having list like '%19,%';

SQL 小提琴链接 http://www.sqlfiddle.com/#!2/e3cdf/2

适当地替换为您的字段和表名称。

评论

0赞 Jaugar Chang 9/12/2014
在这种情况下,它不起作用,sqlfiddle.com/#!2/19360/2,使用这个技巧,至少你应该先按层次结构级别排序。
11赞 Der Zinger 7/24/2014 #3

我想出的最好的方法是

  1. 使用世系来存储\排序\跟踪树。这绰绰有余,而且阅读速度比任何其他方法快数千倍。 即使数据库会更改,它也允许保持该模式(因为任何数据库都允许使用该模式)
  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)

希望它能帮助到:)

16赞 Dheerendra Kulkarni 2/7/2015 #4

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

Mysql select recursive get all child with multiple level

查询将是:

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;

评论

0赞 Rahul 6/12/2015
我们怎样才能做到这一点? ;我无法引用 F1.idFolder 以获取@pvSELECT 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
0赞 Digital Ninja 1/19/2020
我使用他们评论中显示的数据从 OP 的原始问题重新创建了表格,然后在此处运行您的查询,结果得到了一个。你知道为什么会这样吗?在数据库引擎方面是否存在先决条件,或者自您做出此答案以来是否发生了某些更改,从而使此查询过时?NULL
-1赞 cripox 9/18/2015 #5

我发现它更容易:

1) 创建一个函数,用于检查一个项目是否在另一个项目的父层次结构中的任何位置。像这样的东西(我不会写函数,用 WHILE DO 制作):

is_related(id, parent_id);

在您的示例中

is_related(21, 19) == 1;
is_related(20, 19) == 1;
is_related(21, 18) == 0;

2)使用子选择,如下所示:

select ...
from table t
join table pt on pt.id in (select i.id from table i where is_related(t.id,i.id));
644赞 trincot 11/16/2015 #6

对于 MySQL 8+:使用带有语法的递归。
对于 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 = 19id

MySQL 5.x

对于不支持通用表表达式的 MySQL 版本(最高版本 5.7),您可以通过以下查询实现此目的:

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
  • 按照从别名中检索的顺序对每条记录计算子句。因此,这是条件仅包括父项已被标识为在后代树中的记录(主父项的所有子项都逐步添加到 )。wherefrom@pv
  • 该条款中的条件按顺序进行评估,一旦确定总结果,评估就会中断。因此,第二个条件必须排在第二位,因为它将 添加到父列表中,并且只有在通过第一个条件时才会发生这种情况。调用该函数只是为了确保此条件始终为真,即使字符串由于某种原因会产生虚假值。whereididlengthpv

总而言之,人们可能会发现这些假设风险太大,无法依赖。文档警告:

你可能会得到你所期望的结果,但这并不能保证 [...] 涉及用户变量的表达式的计算顺序是未定义的。

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

MySQL的早期版本可以在除 之外的语句中为用户变量赋值。MySQL 8.0 支持此功能以实现向后兼容性,但在 MySQL 的未来版本中可能会删除此功能。SET

如上所述,从 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

编号 名字
19 类别1
19/1 类别2
19/1/1 类别3
19/1/1/1 类别4

那么你的会看起来像这样: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;

看到这把小提琴

该条件指定要检索其后代的父项。您可以根据需要使用更多级别扩展此查询。where

评论

3赞 trincot 3/13/2017
@Avión,它不是你必须放在某个地方的东西,而是要求对于所有记录,这个条件都是真的。如果您有一条或多条记录,则不能使用此解决方案。parent_id > id
2赞 shreddish 7/19/2017
@trincot 是否有可能将其更改为“反向”工作?所以抓住所有一排的父母、祖父母等?我已经使用您的第一个查询来获取后代,但我想获取祖先?也。
1赞 trincot 7/19/2017
是的,这将是可能的,而且更容易。如果你得到这个答案中使用的逻辑,那么做到这一点应该不难。如果您遇到问题,请提出新问题。
2赞 Horse 11/17/2017
对于任何希望使用该方法的人,我发现以下文章对不同的场景非常有帮助,例如递归深度、不同以及检测和关闭周期WITH RECURSIVE
2赞 fanfare 12/10/2017
如果其他人正在寻找@shreddish提出的问题的答案,解决方案是改为on p.parent_id = cte.idon p.id = cte.parent_id
4赞 Phil John 11/21/2015 #7

在其他数据库中,您可以使用递归查询(性能上的 YMMV)轻松完成此操作。

另一种方法是存储两个额外的数据位,一个左值和右值。左边和右边的值派生自你所表示的树结构的预序遍历。

这称为修改后的预排序树遍历,允许您运行简单的查询来一次获取所有父值。它也被称为“嵌套集”。

评论

0赞 Miroslaw Opoka 2/28/2017
我想为您的评论添加类似的评论,但既然您这样做了,我将只添加一个指向“嵌套集”的好示例的链接:mikehillyer.com/articles/managing-hierarchical-data-in-mysql
12赞 Justin Howard 1/14/2016 #8

如果您需要快速的读取速度,最好的选择是使用闭合表。闭包表包含每个祖先/后代对的一行。因此,在您的示例中,闭包表如下所示

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

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

编辑:请参阅问题在关系数据库中存储分层数据的选项有哪些?针对不同的情况有不同的最佳解决方案。

19赞 Fandi Susanto 3/11/2017 #9

请尝试以下操作:

表定义:

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赞 Basheer Kharoti 4/3/2018
这对一个以上的孩子不起作用。例如(20, 'category2', 19), (21, 'category3', 20), (22, 'category4', 20),
6赞 Fandi Susanto 4/7/2018
我很确定它对不止一个孩子有效。我什至再次测试了它。
0赞 Dogan Ozer 3/9/2019
@Fandi苏珊托,谢谢你帮助了我。
0赞 Benjamin 9/7/2020
该解决方案对我有用,但重要的是要检查顶级父级(类别)是否由NULL 或 0 的parent_id标识。因此,临时父检查必须如下所示:IF (tempparent IS NULL OR tempparent = 0)
0赞 Pkchkchiseu 11/13/2021
谢谢伙计!对我来说效果很好,只是在我的情况下不得不改变IF tempparent IS NULLIF tempparent = 0
4赞 Saleh Mosleh 5/22/2017 #10

只需使用 BlueM/tree php 类在 mysql 中制作自关系表的树即可。

Tree 和 Tree\Node 是 PHP 类,用于处理使用父 ID 引用分层构建的数据。一个典型的示例是关系数据库中的表,其中每条记录的“父”字段引用另一条记录的主键。当然,Tree 不能只使用来自数据库的数据,而是使用任何东西:您提供数据,Tree 使用它,而不管数据来自哪里以及如何处理。阅读更多

以下是使用 BlueM/tree 的示例:

<?php 
require '/path/to/vendor/autoload.php'; $db = new PDO(...); // Set up your database connection 
$stm = $db->query('SELECT id, parent, title FROM tablename ORDER BY title'); 
$records = $stm->fetchAll(PDO::FETCH_ASSOC); 
$tree = new BlueM\Tree($records); 
...
6赞 lynx_74 7/19/2017 #11

列出第一个递归子项的简单查询:

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

7赞 MTK 1/21/2018 #12

这里没有提到的东西,虽然有点类似于接受答案的第二种选择,但对于大型层次结构查询和简单的(插入更新删除)项目来说,不同且成本低,将为每个项目添加一个持久路径列。

比如:

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 路径

 // 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/

待办事项

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

评论

0赞 Liki Crus 5/30/2022
@MTK,我们可以通过 DESC 获得结果,例如从帖子评论中获取最新回复吗?
0赞 MTK 5/31/2022
@LikiCrus我将其用于分层查询。如果您想按最新回复排序,我认为您必须玩评论的日期。例。 或者玩也在这里看看 stackoverflow.com/questions/5362160/......Select comment FROM comments WHERE ... (subject or user or theme or whatever condition) ... ORDER BY posting_date DESCGROUP BY user ODER BY posting date
0赞 Liki Crus 6/1/2022
@MTK,我不认为我们可以通过你的方法得到结果。因为只支持 ASC 本身。DESCpathpath
0赞 MTK 6/2/2022
@LikiCrus我之前说过,您必须为此目的使用另一列,而不是路径列。例如日期、ID 等。path 列用于分层
-2赞 Manish 2/14/2018 #13

我为你提出了一个问题。这将为您提供具有单个查询的递归类别:

SELECT id,NAME,'' AS subName,'' AS subsubName,'' AS subsubsubName FROM Table1 WHERE prent is NULL
UNION 
SELECT b.id,a.name,b.name AS subName,'' AS subsubName,'' AS subsubsubName FROM Table1 AS a LEFT JOIN Table1 AS b ON b.prent=a.id WHERE a.prent is NULL AND b.name IS NOT NULL 
UNION 
SELECT c.id,a.name,b.name AS subName,c.name AS subsubName,'' AS subsubsubName FROM Table1 AS a LEFT JOIN Table1 AS b ON b.prent=a.id LEFT JOIN Table1 AS c ON c.prent=b.id WHERE a.prent is NULL AND c.name IS NOT NULL 
UNION 
SELECT d.id,a.name,b.name AS subName,c.name AS subsubName,d.name AS subsubsubName FROM Table1 AS a LEFT JOIN Table1 AS b ON b.prent=a.id LEFT JOIN Table1 AS c ON c.prent=b.id LEFT JOIN Table1 AS d ON d.prent=c.id WHERE a.prent is NULL AND d.name IS NOT NULL 
ORDER BY NAME,subName,subsubName,subsubsubName

这是一把小提琴

0赞 Monzur 12/13/2018 #14

这对我有用,希望这也对你有用。它将为您提供任何特定菜单的记录集 Root to Child。根据您的要求更改字段名称。

SET @id:= '22';

SELECT Menu_Name, (@id:=Sub_Menu_ID ) as Sub_Menu_ID, Menu_ID 
FROM 
    ( SELECT Menu_ID, Menu_Name, Sub_Menu_ID 
      FROM menu 
      ORDER BY Sub_Menu_ID DESC
    ) AS aux_table 
    WHERE Menu_ID = @id
     ORDER BY Sub_Menu_ID;

评论

0赞 Muaaz Khalid 10/31/2019
如果有些孩子的 ID 比他们的父母大,似乎并非在所有级别上都有效
3赞 Pradip Rupareliya 1/7/2019 #15

enter image description here

这是一个类别表。

SELECT  id,
        NAME,
        parent_category 
FROM    (SELECT * FROM category
         ORDER BY parent_category, id) products_sorted,
        (SELECT @pv := '2') initialisation
WHERE   FIND_IN_SET(parent_category, @pv) > 0
AND     @pv := CONCAT(@pv, ',', id)

输出:: enter image description here

评论

1赞 wobsoriano 7/15/2019
你能解释一下吗?但我保证这是有效的。谢谢。
1赞 Amanjot Kaur 7/22/2019
请解释查询,@pv是什么意思??循环在此查询中是如何工作的?
2赞 Jonas 7/22/2019
如果有些孩子的身份证比他们的父母低,似乎并不适用于所有级别。:(
1赞 Muaaz Khalid 10/30/2019
@Jonas我花了 20 分钟来确定实际问题,尝试不同的组合。是的,你是对的。它不适用于低于其父 ID 的 ID。你有什么解决方案吗?
1赞 trincot 7/2/2021
似乎您只是从顶部答案复制并更改了一些表和字段名称。
12赞 Meloman 1/7/2021 #16

根据@trincot答案,解释得很好,我使用语句使用当前页面创建面包屑,并在层次结构中向后查找表中的每一步。WITH RECURSIVE ()idparentroute

因此,@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 authroization Matrix

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

您可以看到包含以正确方式对菜单进行排序的功能的基本列。pathCONCAT()

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赞 David 2/13/2021
我用它来做一个有用的评论,带有面包屑路径,但它也可以用于菜单。谢谢!顺便说一句。我在@trincot的解决方案中附加了这个
1赞 Meloman 2/15/2021
是的,@David对于没有预定义深度的多级菜单,我们可以使用它,没有想到它,谢谢。
1赞 David 10/21/2022
我只是评论你的编辑,谢谢你的功劳;-)您可能对此讨论感兴趣: gist.github.com/DavidBruchmann/cf27eb309e48e0df326b3bafce2b30e3
0赞 ArrayIterator 11/6/2022
嵌套父子节点的出色子查询。