如何创建MySQL分层递归查询


271

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

id | name        | parent_id
19 | category1   | 0
20 | category2   | 19
21 | category3   | 20
22 | category4   | 21
......

现在,我想有一个简单的MySQL查询,只需向其提供ID [例如说'ID = 19'],然后就应该获取其所有子ID [即结果应具有ID '20,21,22']。 ...此外,孩子的等级也不知道它会有所不同...

另外,我已经有了使用for循环的解决方案.....如果可能的话,让我知道如何使用单个MySQL查询实现相同的解决方案。


假设层次结构深7级。您期望输出表看起来如何?
乔纳森·莱夫勒

1
MySQL(仍然)不支持分层查询(就像其他现代DBMS一样)。您将需要编写存储过程或使用其他数据模型。
a_horse_with_no_name


1
MYSQL 8.0将支持使用CTE(公用表表达式)的递归查询
user3712320

从上一个评论ID开始获取完整的帖子列表怎么办?还是最后一个孩子?

Answers:


392

对于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

对于不支持通用表表达式的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初始化的地方。
  • where按照从from别名检索的顺序为每个记录评估该子句。因此,在这里将条件放在仅包括已将其父级标识为后代树的记录中(主要父级的所有后代都逐渐添加到中@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+DB2甲骨文11gR2的+SQLite的3.8.4+火鸟2.1+H2的HyperSQL 2.1.0+Teradata的MariaDB 10.2.2+)。从8.0版开始,MySQL也支持它。有关使用的语法,请参见此答案的顶部。

一些数据库具有用于分层查找的替代非标准语法,例如OracleDB2InformixCUBRID和其他数据库CONNECT BY上可用的子句。

MySQL 5.7版不提供这种功能。如果您的数据库引擎提供了这种语法,或者您可以迁移到该语法,那么这无疑是最佳选择。如果不是,则还考虑以下替代方法。

备选方案2:路径样式标识符

如果分配id包含层次信息的值(路径),事情就会变得容易得多。例如,在您的情况下,可能如下所示:

ID       | NAME
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;

看到这个小提琴

where你想要的条件指定哪个家长要检索的后裔。您可以根据需要将查询扩展到更多级别。


26
我喜欢你的解释。它不仅给出了答案,还解释了为什么它可以解决问题,因此我们可以从中学到东西。编辑:也很好,它不依赖于事先知道级别的数量。
Byson 2015年

1
@Bison,非常感谢您的反馈。谢谢!
特里科特,2015年

2
@Avión,这不是您必须放置在某处的东西,它要求所有记录都满足此条件。如果您有一个或多个记录,parent_id > id则不能使用此解决方案。
Trincot

2
对于任何希望使用该WITH RECURSIVE方法的人,我发现以下文章对不同的场景(例如递归深度,差异,检测和关闭周期)确实很有帮助
Horse

2
我在自己的表上的计算机上的MySQL5.7上尝试了主要解决方案,但是由于子句@pv:= concat(@pv,',',id)的等效结果为false,因此该方法不起作用。我通过将其更改为length(@pv:= concat(@pv,',',id))> 0来修复它,因此它始终是正确的。
KC Wong

82

来自博客“ 在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查询?


23
只要层次结构中最多不超过4个级别,就可以了。如果有N个级别,则必须知道这一点才能正确创建查询。
乔纳森·莱夫勒

2
@Damodaran,感谢您的回复...我需要的是一个未知的孩子数量的情况...并且在使用内部联接概念的博客中,需要了解层次结构,而这是未知的就我而言...让我知道您对同一观点的看法...因此,用简单的话,我需要一个查询来处理不知道'n'的'n'等级....
Tarun Parswani

1
@ user3036105:在MySQL中无法通过单个 SQL查询执行此操作。MySQL根本不够先进。如果确实需要此功能,请考虑升级到支持递归查询的DBMS。
a_horse_with_no_name

5
>大多数用户一次或一次都在处理SQL数据库中的分层数据,并且毫无疑问地了解到,分层数据的管理不是关系数据库的目的。也许您的意思是MySQL数据库。Oracle数据库能够很好地处理分层数据和查询。
彼得·诺斯科

1
“……分层数据的管理不是关系数据库的本意……”虽然这可能并不是关系数据库的初衷,但在现实世界中,分层数据实在是司空见惯,MySQL应该反映出人们在实际场景中实际需要如何使用他们的数据。
戴夫L

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
这对于一个孩子以上的孩子来说是行不通的。例如(20, 'category2', 19), (21, 'category3', 20), (22, 'category4', 20),
清理代码

3
我很确定它适用于多个孩子。我什至再次测试了它。
Fandi Susanto

@Fandi Susanto,谢谢Alote帮助我。
Dogan Ozer

9

我想出的最好方法是

  1. 使用沿袭来存储\排序\跟踪树。这已经绰绰有余,并且比任何其他方法阅读速度快数千倍。即使数据库将更改,它也允许保持该模式(因为ANY db将允许使用该模式)
  2. 使用确定特定ID沿袭的功能。
  3. 根据需要使用它(在选择中,在CUD操作中,甚至在作业中)。

沿袭方法描述。可以在任何地方找到,例如 Herehere。从功能上来说- 就是我的灵感。

最后,获得了或多或少的简单,相对快速和简单的解决方案。

功能主体

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

希望它对某人有帮助:)


9

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

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; 我无法为@pv引用F1.idFolder
Rahul

我使用他们评论中的数据从OP的原始问题重新创建了表格,然后在此处运行查询,NULL结果得到了一个。你知道为什么会这样吗?在数据库引擎方面是否存在先决条件,或者自从您做出使该查询过时的答案以来发生了变化?
Digital Ninja

7

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

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

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

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


4

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

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


3

您可以通过递归查询(性能为YMMV)在其他数据库中轻松地做到这一点。

另一种方法是存储两个额外的数据位,即左值和右值。左值和右值是从您要表示的树结构的预遍历中得出的。

这就是所谓的“修改的预排序树遍历”,它使您可以运行一个简单的查询来一次获取所有父值。它也被称为“嵌套集”。


我想向您添加类似的注释,但是既然您这样做了,我将仅添加指向“嵌套集”的一个很好示例的链接:mikehillyer.com/articles/managing-hierarchical-data-in-mysql
Miroslaw Opoka

2

只需在MySQL中使用BlueM / tree php类为自关系表的make树。

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); 
...

2

在此处输入图片说明

这是一个类别表。

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)

输出: 在此处输入图片说明


1
你能解释一下吗?但我保证这是可行的。谢谢。
wobsoriano

1
请解释查询,@ pv是什么意思?循环在此查询中如何工作?
阿曼霍特·考尔

2
如果孩子的ID低于父母,则似乎并不能在所有级别上正常工作。:(
乔纳斯

1
@Jonas用了20分钟的时间来确定实际问题,并尝试使用不同的组合。你是对的。ID不能低于其父ID。你有什么解决办法吗?
muaaz

@muaaz我最后使用“ path”字段解决了它,该字段包含相应行的路径,例如ID 577的行的路径为“ / 1/2/45/577 /”。如果要查找ID 2的所有子级,则只需选择路径为“ / 1/2 /%”的所有行。唯一的缺点是您必须在更新方法中更新路径。但是对于MySQL 5.6(兼容),它是唯一对我有用的解决方案。
乔纳斯(Jonas)

1

它有点棘手,请检查它是否对您有用

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

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


在这种情况下,它将无法使用sqlfiddle.com/#!2/19360/2,使用此技巧,至少您应该首先按层次结构级别进行排序。
Jaugar Chang 2014年

1

此处未提及的内容,尽管有点类似于已接受答案的第二种选择,但是对于大型层次结构查询和简单(插入更新删除)项目而言,其成本低廉,但将为每个项目添加一个持久路径列。

一些像:

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

优化路径长度,并ORDER BY path使用base36编码代替实数路径ID

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

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

通过使用固定长度并填充到已编码的id,还抑制斜杠“ /”分隔符

此处有详细的优化说明:https : //bojanz.wordpress.com/2014/04/25/storing-hierarchical-data-materialized-path/

去做

建立一个功能或过程为一个条目的后代祖先分割路径


谢谢!有趣的是base36
Vlad

0

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

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;

如果孩子的身份证比父母多,似乎并不能在所有级别上工作
muaaz

-1

我发现更容易:

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));

-1

我已向您查询。这将为您提供带有单个查询的递归类别:

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

这是一个小提琴


请删除/编辑您的答案以恢复您的正面声誉。
fWd82
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.