简体   繁体   English

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

[英]Hierarchical table - how to get paths of the items [linked lists in MySQL]

I have a hierarchical table in MySQL: parent field of each item points to the id field of its parent item. 我在MySQL中有一个分层表:每个项目的parent字段都指向其父项目的id字段。 For each item I can get the list of all its parents [regardless the depth] using the query described here . 对于每一项,我都可以使用此处描述查询获取所有父项的列表(无论深度如何)。 With GROUP_CONCAT I get the full path as a single string: 使用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

I can make this work only if the id of the item is fixed [it's 200 in this case]. 仅当该项目的id是固定的[在这种情况下为200 ]时,我才能使这项工作有效。

I want to do the same for all rows: retrieve the whole table with one additional field ( path ) which will display the full path. 我想对所有行执行相同的操作:使用一个附加字段( path )检索整个表,该字段将显示完整路径。 The only solution that comes to my mind is to wrap this query in another select, set a temporary variable @id and use it inside the subquery. 我想到的唯一解决方案是将此查询包装在另一个选择中,设置一个临时变量@id并在子查询中使用它。 But it doesn't work. 但这是行不通的。 I get NULL s in the path field. 我在path字段中得到NULL

SELECT @id := id, parent, (
    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 := @id,
                     @l := 0
             ) vars,
             t_hierarchy h
    WHERE    @r <> 0
    ORDER BY lvl DESC
    ) x
) as path
 FROM t_hierarchy

PS I know I can store the paths in a separate field and update them when inserting/updating, but I need a solution based on the linked list technique . PS我知道我可以将路径存储在单独的字段中,并在进行插入/更新时对其进行更新,但是我需要基于链表技术的解决方案。

UPDATE: I would like to see a solution that will not use recursion or constructs like for and while . 更新:我希望看到一个不会使用递归或forwhile构造的解决方案。 The above method for finding paths doesn't use any loops or functions. 上面的查找路径的方法不使用任何循环或函数。 I want to find a solution in the same logic. 我想以相同的逻辑找到解决方案。 Or, if it's impossible, please try to explain why! 或者,如果不可能,请尝试解释原因!

Consider the difference between the following two queries: 请考虑以下两个查询之间的区别:

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;

They look nearly identical, but give dramatically different results. 它们看起来几乎相同,但给出的结果却截然不同。 On my version of MySQL, _id in the second query is the same for each row in its result set, and equal to the id of the last row. 在我的MySQL版本上,第二个查询中的_id对于其结果集中的每一行都是相同的,并且等于最后一行的id However, that last bit is only true because I executed the two queries in the order given; 但是,最后一点是正确的,因为我按照给定的顺序执行了两个查询。 after SET @id := 1 , for example, I can see that _id is always equal to the value in the SET statement. SET @id := 1 ,例如,我可以看到_id始终等于SET语句中的值。

So what's going on here? 那么这是怎么回事? An EXPLAIN yields a clue: 一个EXPLAIN产量线索:

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)

That third row, the DERIVED table with no tables used, indicates to MySQL that it can be calculated exactly once, at any time. 第三行,即不使用任何表的DERIVED表,向MySQL指示可以随时精确地计算一次。 The server doesn't notice that the derived table uses a variable defined elsewhere in the query, and has no clue that you want it to be run once per row. 服务器没有注意到派生表使用查询中其他位置定义的变量,并且不知道您希望它每行运行一次。 You're being bitten by a behavior mentioned in the MySQL documentation on user-defined variables : 您被MySQL文档中有关用户定义变量的行为所困扰:

As a general rule, you should never assign a value to a user variable and read the value within the same statement. 通常,永远不要为用户变量分配值,并且不要在同一条语句中读取该值。 You might get the results you expect, but this is not guaranteed. 您可能会得到预期的结果,但这不能保证。 The order of evaluation for expressions involving user variables is undefined and may change based on the elements contained within a given statement; 涉及用户变量的表达式的求值顺序是不确定的,并且可能根据给定语句中包含的元素而改变; in addition, this order is not guaranteed to be the same between releases of the MySQL Server. 此外,在MySQL服务器的发行版之间,不能保证此顺序相同。

In my case, it chooses to do calculate that table first, before @id is (re)defined by the outer SELECT . 就我而言,它选择先计算表,然后再由外部SELECT (重新)定义@id In fact, that's exactly why the original hierarchical data query works; 实际上,这就是为什么原始分层数据查询有效的原因。 the @r definition is computed by MySQL before anything else in the query, precisely because it's that kind of derived table. @r定义是由MySQL在查询中的其他任何内容之前计算的,正是因为它是这种派生表。 However, we need here a way to reset @r once per table row, not just once for the whole query. 但是,我们这里需要一种方法来对每个表行重置@r ,而不仅仅是对整个查询重置一次。 To do that, we need a query that looks like the original one, resetting @r by hand. 为此,我们需要一个看起来像原始查询的查询,并手动重置@r

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

This query uses the second t_hierarchy the same way the original query does, to ensure there are enough rows in the result for the parent subquery to loop over. 此查询以与原始查询相同的方式使用第二个t_hierarchy ,以确保结果中有足够的行供父子查询循环。 It also adds a row for each _id that includes itself as a parent; 它还为每个_id添加一行,并将其自身作为父级。 without that, any root objects (with NULL in the parent field) would fail to appear in the results at all. 否则,所有根对象(父字段中为NULL )将根本不会出现在结果中。

Oddly, running the result through GROUP_CONCAT seems to disrupt ordering. 奇怪的是,通过GROUP_CONCAT运行结果似乎干扰了排序。 Fortunately, that function has its own ORDER BY clause: 幸运的是,该函数具有自己的ORDER BY子句:

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;

Fair warning: These queries implicitly rely on the @r and @l updates happening before the @c update. 合理的警告:这些查询隐式依赖于@c更新之前发生的@r@l更新。 That order is not guaranteed by MySQL, and may change with any version of the server. MySQL不能保证该顺序,并且可能随服务器的任何版本而变化。

Define the getPath function and run the following query: 定义getPath函数并运行以下查询:

select id, parent, dbo.getPath(id) as path from t_hierarchy 

Defining the getPath function: 定义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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM