对于MySQL 8+:请使用递归的with
语法。
对于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.x
对于不支持通用表达式(直到版本5.7)的MySQL版本,您可以使用以下查询实现:
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语法:变量在执行期间被分配和修改。对执行顺序做出了一些假设:
- 首先评估
from
语句。因此,在那里初始化了@pv
。
- 按照从
from
别名检索的顺序为每个记录评估where
子句。因此,在这里会加上一个条件,只包括已确定为在后代树中的父项的记录(主要父项的所有后代都逐步添加到@pv
中)。
- 此
where
子句中的条件按顺序评估,并且在总结果确定后中断评估。因此,第二个条件必须位于第二个位置,因为它将id
添加到父项列表中,仅在id
通过第一个条件时才会发生。调用length
函数只是为了确保此条件始终为真,即使由于某种原因pv
字符串产生错误值也一样。
总之,您可能会发现这些假设过于冒险以依赖。文档警告:
您可能会获得所期望的结果,但这并不保证[...]涉及用户变量的表达式的评估顺序未定义。
因此,即使它与上述查询一致地工作,评估顺序仍可能更改,例如添加条件或在更大的查询中使用此查询作为视图或子查询。这是一个“功能”,将在MySQL的未来版本中删除:
MySQL的早期版本使得在语句中除SET
外的其他语句中为用户变量分配值成为可能。尽管基于向后兼容性,MySQL 8.0支持这种功能,但可能会在将来的MySQL版本中被删除。
如上所述,从MySQL 8.0开始,您应该使用递归的with
语法。
效率
对于非常大的数据集,此解决方案可能会变慢,因为在列表中查找数字的
find_in_set
操作不是最理想的方式,特别是在列表大小与返回记录数相同数量级时。
备选方案1:
with recursive
、
connect by
越来越多的数据库实现
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 |
名称 |
19 |
category1 |
19/1 |
category2 |
19/1/1 |
category3 |
19/1/1/1 |
category4 |
然后您的select
将如下所示:
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;
请查看这个 fiddle
where
条件指定了您想要检索后代的父级。如有需要,您可以扩展此查询以获取更多级别。