对于MySQL 8+:使用带语法的递归。
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.倍
对于不支持通用表表达式的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语法:变量在执行过程中被赋值和修改。对执行顺序做了一些假设:
The from clause is evaluated first. So that is where @pv gets initialised.
The where clause is evaluated for each record in the order of retrieval from the from aliases. So this is where a condition is put to only include records for which the parent was already identified as being in the descendant tree (all descendants of the primary parent are progressively added to @pv).
The conditions in this where clause are evaluated in order, and the evaluation is interrupted once the total outcome is certain. Therefore the second condition must be in second place, as it adds the id to the parent list, and this should only happen if the id passes the first condition. The length function is only called to make sure this condition is always true, even if the pv string would for some reason yield a falsy value.
总而言之,人们可能会发现这些假设风险太大,无法依赖。文档警告:
你可能会得到你期望的结果,但这并不能保证…包含用户变量的表达式的求值顺序未定义。
因此,即使它与上面的查询一致,求值顺序仍然可能发生变化,例如当您添加条件或将此查询用作较大查询中的视图或子查询时。这个“特性”将在未来的MySQL版本中被移除:
以前的MySQL版本允许在语句中为用户变量赋值,而不是SET。为了向后兼容,MySQL 8.0支持这个功能,但在MySQL的未来版本中可能会被删除。
如上所述,从MySQL 8.0开始,您应该使用带有语法的递归。
效率
对于非常大的数据集,这个解决方案可能会很慢,因为find_in_set操作不是在列表中查找数字的最理想的方法,当然不是在与返回的记录数量大小相同数量级的列表中。
备选方案1:用递归,用连接
越来越多的数据库实现了用于递归查询的SQL:1999 ISO标准WITH [RECURSIVE]语法(例如Postgres 8.4+, SQL Server 2005+, DB2, Oracle 11gR2+, SQLite 3.8.4+, Firebird 2.1+, H2, HyperSQL 2.1.0+, Teradata, MariaDB 10.2.2+)。从8.0版本开始,MySQL也支持它。请参阅答案顶部的语法。
有些数据库有用于层次结构查找的替代非标准语法,例如Oracle、DB2、Informix、CUBRID和其他数据库上可用的CONNECT BY子句。
MySQL 5.7版本不提供这样的特性。如果您的数据库引擎提供了这种语法,或者您可以迁移到提供这种语法的数据库引擎,那么这当然是最好的选择。如果不是,那么也要考虑以下备选方案。
备选方案2:路径样式标识符
如果您分配包含层次信息的id值(路径),事情就会变得简单得多。例如,在你的例子中,它可能是这样的:
ID |
NAME |
19 |
category1 |
19/1 |
category2 |
19/1/1 |
category3 |
19/1/1/1 |
category4 |
然后你的选择看起来像这样:
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条件指定要检索哪个父节点的后代。您可以根据需要使用更多级别扩展此查询。