[英]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
. 更新:我希望看到一个不会使用递归或
for
和while
构造的解决方案。 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.