分层表-如何获取项的路径[MySQL中的链表]

分层表-如何获取项的路径[MySQL中的链表],mysql,sql,linked-list,hierarchical-data,Mysql,Sql,Linked List,Hierarchical Data,我在MySQL中有一个分层表:每个项的父字段指向其父项的id字段。对于每个项目,我可以使用。使用GROUP_CONCAT,我将完整路径作为单个字符串: SELECT GROUP_CONCAT(_id SEPARATOR ' > ') FROM ( SELECT @r AS _id, ( SELECT @r := parent FROM t_hierarchy WHERE id = _id

我在MySQL中有一个分层表:每个项的父字段指向其父项的id字段。对于每个项目,我可以使用。使用GROUP_CONCAT,我将完整路径作为单个字符串:

SELECT GROUP_CONCAT(_id SEPARATOR ' > ') FROM (
SELECT  @r AS _id,
         (
         SELECT  @r := parent
         FROM    t_hierarchy
         WHERE   id = _id
         ) AS parent,
         @l := @l + 1 AS lvl
 FROM    (
         SELECT  @r := 200,
                 @l := 0
         ) vars,
         t_hierarchy h
WHERE    @r <> 0
ORDER BY lvl DESC
) x
另外,我知道我可以将路径存储在单独的字段中,并在插入/更新时进行更新,但我需要基于链表技术的解决方案


更新:我希望看到一个解决方案,它不会像for和while那样使用递归或构造。上述查找路径的方法不使用任何循环或函数。我想用同样的逻辑找到一个解决方案。或者,如果不可能,请尝试解释原因

定义getPath函数并运行以下查询:

select id, parent, dbo.getPath(id) as path from t_hierarchy 
定义getPath函数:

create function dbo.getPath( @id int)
returns varchar(400)
as
begin
declare @path varchar(400)
declare @term int
declare @parent varchar(100)
set @path = ''
set @term = 0
while ( @term <> 1 )
begin
   select @parent = parent from t_hierarchy where id = @id
   if ( @parent is null or @parent = '' or  @parent = @id )
        set @term = 1
   else
        set @path = @path + @parent   
   set @id = @parent     
end
return @path
end

考虑以下两个查询之间的差异:

SELECT @id := id as id, parent, (
    SELECT concat(id, ': ', @id)
) as path
FROM t_hierarchy;

SELECT @id := id as id, parent, (
    SELECT concat(id, ': ', _id)
    FROM (SELECT @id as _id) as x
) as path
FROM t_hierarchy;
它们看起来几乎相同,但结果却截然不同。在我的MySQL版本中,第二个查询中的_id对于结果集中的每一行都是相同的,并且等于最后一行的id。然而,最后一位是正确的,因为我按照给定的顺序执行了两个查询;例如,在SET@id:=1之后,我可以看到_id始终等于SET语句中的值

这是怎么回事?解释产生了一个线索:

mysql>     explain SELECT @id := id as id, parent, (
    ->         SELECT concat(id, ': ', _id)
    ->         FROM (SELECT @id as _id) as x
    ->     ) as path
    ->     FROM t_hierarchy;
+----+--------------------+-------------+--------+---------------+------------------+---------+------+------+----------------+
| id | select_type        | table       | type   | possible_keys | key              | key_len | ref  | rows | Extra          |
+----+--------------------+-------------+--------+---------------+------------------+---------+------+------+----------------+
|  1 | PRIMARY            | t_hierarchy | index  | NULL          | hierarchy_parent | 9       | NULL | 1398 | Using index    |
|  2 | DEPENDENT SUBQUERY | <derived3>  | system | NULL          | NULL             | NULL    | NULL |    1 |                |
|  3 | DERIVED            | NULL        | NULL   | NULL          | NULL             | NULL    | NULL | NULL | No tables used |
+----+--------------------+-------------+--------+---------------+------------------+---------+------+------+----------------+
3 rows in set (0.00 sec)
此查询使用第二个t_层次结构,与原始查询相同,以确保结果中有足够的行供父子查询循环。它还为每个_id添加一行,该id将自身作为父项;否则,任何在父字段中为NULL的根对象都将无法显示在结果中

奇怪的是,通过GROUP_CONCAT运行结果似乎扰乱了排序。幸运的是,该函数有自己的ORDER BY子句:


公平警告:这些查询隐式地依赖于@c更新之前发生的@r和@l更新。MySQL不保证这种顺序,并且可能会随着服务器的任何版本而改变。

我得到一个错误:1064-您的SQL语法有一个错误;检查与您的MySQL服务器版本对应的手册,了解在第1行“@id int将varchar400返回为begin declare@path varchar400 declare@term”附近使用的正确语法,以及没有循环的解决方案如何?!答案是SQLServer语法;我可以帮助您将其转换为MySQL语法。因为它有层次数据,并且您不想在更新/插入/删除中添加任何列,即:需要更新的深度/路径;我看不出有一个没有回路的解。如果MySQL有CTE,您可以在没有用户定义函数和循环的情况下解决相同的问题。感谢您回答这个问题!这个答案为我澄清了很多事情。虽然最终的查询为我返回了空结果[MySQL 5.1.40],但它有很多重要的想法,所以我要奖励它。我会再读几遍,试着理解为什么最后一个查询在我的数据库上不起作用,也许会要求澄清一些事情。再次感谢!奇怪的是它在5.1.40中不起作用;它在Ubuntu11.10Oneiric上进行了5.1.63的测试。你可以试着移动@c线;删除或注释掉HAVING行也可能有助于调试。根据您给出的引用,您的代码未定义。您没有理由认为它会工作,也没有理由声称它在内部选择了任何内容,只是您运行了它并给出了正确的结果。在同一select语句中读取和分配同一变量在MySQL中是未定义的行为,请参阅文档re assignment&variables。在8.0&CTEs之前,在指定的语义下没有非循环解决方案。
mysql>     explain SELECT @id := id as id, parent, (
    ->         SELECT concat(id, ': ', _id)
    ->         FROM (SELECT @id as _id) as x
    ->     ) as path
    ->     FROM t_hierarchy;
+----+--------------------+-------------+--------+---------------+------------------+---------+------+------+----------------+
| id | select_type        | table       | type   | possible_keys | key              | key_len | ref  | rows | Extra          |
+----+--------------------+-------------+--------+---------------+------------------+---------+------+------+----------------+
|  1 | PRIMARY            | t_hierarchy | index  | NULL          | hierarchy_parent | 9       | NULL | 1398 | Using index    |
|  2 | DEPENDENT SUBQUERY | <derived3>  | system | NULL          | NULL             | NULL    | NULL |    1 |                |
|  3 | DERIVED            | NULL        | NULL   | NULL          | NULL             | NULL    | NULL | NULL | No tables used |
+----+--------------------+-------------+--------+---------------+------------------+---------+------+------+----------------+
3 rows in set (0.00 sec)
SELECT  @r := if(
          @c = th1.id,
          if(
            @r is null,
            null,
            (
              SELECT  parent
              FROM    t_hierarchy
              WHERE   id = @r
            )
          ),
          th1.id
        ) AS parent,
        @l := if(@c = th1.id, @l + 1, 0) AS lvl,
        @c := th1.id as _id
FROM    (
        SELECT  @c := 0,
                @r := 0,
                @l := 0
        ) vars
        left join t_hierarchy as th1 on 1
        left join t_hierarchy as th2 on 1
HAVING  parent is not null
SELECT  _id,
        GROUP_CONCAT(parent ORDER BY lvl desc SEPARATOR ' > ') as path,
        max(lvl) as depth
FROM    (
  SELECT  @r := if(
            @c = th1.id,
            if(
              @r is null,
              null,
              (
                SELECT  parent
                FROM    t_hierarchy
                WHERE   id = @r
              )
            ),
            th1.id
          ) AS parent,
          @l := if(@c = th1.id, @l + 1, 0) AS lvl,
          @c := th1.id as _id
  FROM    (
          SELECT  @c := 0,
                  @r := 0,
                  @l := 0
          ) vars
          left join t_hierarchy as th1 on 1
          left join t_hierarchy as th2 on 1
  HAVING  parent is not null
  ORDER BY th1.id
) as x
GROUP BY _id;