我在MySQL中有一个分层表:每个项目的parent
字段指向其父项的id
字段。对于每个项目,我可以使用query described here获取所有父项的列表[无论深度]。使用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
只有在项目的id
被修复后才能使这项工作正常[在这种情况下它是200
。
我想对所有行执行相同操作:使用一个附加字段(path
)检索整个表,该字段将显示完整路径。我想到的唯一解决方案是将此查询包装在另一个select中,设置一个临时变量@id
并在子查询中使用它。但它不起作用。我在NULL
字段中获得了path
。
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 我知道我可以将路径存储在单独的字段中并在插入/更新时更新它们,但我需要一个基于链接列表技术的解决方案。 / p>
更新:我希望看到一个不使用递归或for
和while
等结构的解决方案。上述查找路径的方法不使用任何循环或函数。我想在同一个逻辑中找到一个解决方案。或者,如果不可能,请尝试解释原因!
答案 0 :(得分:2)
考虑以下两个查询之间的区别:
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
语句中的值。
那么这里发生了什么? 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)
第三行,即没有使用表的DERIVED
表,向MySQL表明它可以在任何时间准确计算一次。服务器没有注意到派生表使用查询中其他位置定义的变量,并且不知道您希望每行运行一次。您被user-defined variables上的MySQL文档中提到的行为所困扰:
作为一般规则,您不应该为用户变量赋值 并在同一语句中读取值。你可能会得到 你期望的结果,但这不能保证。的顺序 涉及用户变量的表达式的评估是未定义的 可能会根据给定声明中包含的元素进行更改; 另外,这个顺序不保证是相同的 发布MySQL服务器。
在我的情况下,它选择在外部@id
(重新)定义SELECT
之前先计算该表。事实上,这正是原始分层数据查询工作的原因; @r
定义是由MySQL在查询中的任何其他内容之前计算的,正是因为它是那种派生表。但是,我们需要一种方法来为每个表行重置@r
一次,而不是仅对整个查询重置一次。为此,我们需要一个看起来像原始查询的查询,手动重置@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
此查询以与原始查询相同的方式使用第二个t_hierarchy
,以确保结果中有足够的行供父子查询循环。它还为每个包含自身作为父项的_id添加一行;没有它,任何根对象(在父字段中都有NULL
)将根本不会出现在结果中。
奇怪的是,通过GROUP_CONCAT
运行结果似乎会破坏排序。幸运的是,该函数有自己的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;
公平警告:这些查询隐含地依赖于@r
更新之前发生的@l
和@c
更新。 MySQL不保证该顺序,并且可能随任何版本的服务器而改变。
答案 1 :(得分:1)
定义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