left join查三级分类丢数据是因为where中对中间表字段的过滤条件(如c2.parent_id = c1.id)将null行误删,正确做法是仅用where限定一级分类(c1.parent_id is null),所有关联逻辑严格放在on子句中。

为什么直接用LEFT JOIN查三级分类会丢数据?
因为三级分类表里可能有空值(比如某二级分类下还没建三级分类),INNER JOIN会直接过滤掉这些二级分类;但用LEFT JOIN时,如果没注意ON条件里的NULL处理,结果里会出现重复行或错位匹配。常见错误是把三级分类的parent_id和二级的id做JOIN,却忘了二级分类本身也可能没有上级(即parent_id IS NULL),导致一级分类被漏掉。
关键点在于:每一级JOIN都要独立判断该级是否存在,不能假设“有二级就一定有三级”。
- 一级分类表:
category,id、name、parent_id为NULL - 二级分类表:同样是
category,靠parent_id指向一级id - 三级分类表:也是同一张表,
parent_id指向二级id
怎么写三层LEFT JOIN才能不丢一级和二级分类?
必须让每层JOIN都基于上一级的id,且全部用LEFT JOIN,ON条件只依赖关联字段,不加WHERE过滤。一旦在WHERE里写c2.parent_id = c1.id这类条件,就会把c2为空的情况过滤掉——这正是丢数据的根源。
SELECT c1.name AS level1, c2.name AS level2, c3.name AS level3 FROM category c1 LEFT JOIN category c2 ON c2.parent_id = c1.id LEFT JOIN category c3 ON c3.parent_id = c2.id WHERE c1.parent_id IS NULL;
注意:WHERE c1.parent_id IS NULL只用于限定c1是一级,不能写成WHERE c2.parent_id = c1.id或类似条件。
如何避免平铺后出现“同一级分类重复多行”?
典型现象:一个一级分类下有5个二级,每个二级下又有3个三级,结果出来15行——这其实是正确结果,不是bug。但如果你想要“每个一级只占一行,二级/三级用逗号拼接”,那就不能靠JOIN平铺,得换思路。
- MySQL 5.7+ 可用
GROUP_CONCAT(DISTINCT c2.name)配合GROUP BY c1.id - PostgreSQL 用
STRING_AGG(DISTINCT c2.name, ', ') - SQL Server 用
STRING_AGG(c2.name, ', ') WITHIN GROUP (ORDER BY c2.name)
但要注意:聚合后三级分类会丢失与二级的归属关系,除非嵌套子查询或用JSON函数(如JSON_OBJECT)构造层级结构。
当分类层级超过三级时,JOIN还能撑住吗?
能语法上撑住,但性能和可维护性会快速恶化。比如四级分类要再LEFT JOIN category c4 ON c4.parent_id = c3.id,查询计划变复杂,索引利用率下降,尤其当category表没对parent_id建索引时,全表扫描立马出现。
真正容易被忽略的是:平铺显示本质是把树形结构强行展成二维表,它天然不适合任意深度。如果业务真需要N级动态展示,不如在应用层递归查,或者用闭包表(closure table)、路径枚举(path enumeration)等模型替代自引用设计。











