MySQL获取树形结构叶子节点:排除同商品下父类别
解决MySQL中筛选商品最低层级类别(叶子节点)的问题
针对你遇到的这个需求——要排除同一product_id下的父类别,只保留树形结构里的叶子节点类别,这里有两个高效的数据库层面解决方案,比PHP循环处理要更优,而且能直接满足你的所有场景需求:
需求再明确
先再理清楚判断规则:
- 如果某个
category_id在同一个product_id下,被其他行的parent_category引用,那它就是父类别,需要排除 - 如果一个类别没有任何子类别(包括顶级类别但没有子节点的情况,比如示例里product_id=2的ddd),就保留它作为叶子节点
方案1:用LEFT JOIN自连接筛选
这是最直观且性能不错的写法,通过自连接表来识别没有子类别的行:
SELECT t1.product_id, t1.category_id FROM your_table t1 LEFT JOIN your_table t2 ON t1.product_id = t2.product_id AND t1.category_id = t2.parent_category WHERE t2.product_id IS NULL;
逻辑拆解:
- 把表自己和自己连接(
t1作为主表,t2用来找子类别) - 关联条件是同一个商品,并且
t1的类别是t2的父类别 - 如果
t2里找不到匹配的行(也就是t2.product_id为NULL),说明t1的这个类别没有子类别,正是我们要的叶子节点
方案2:用NOT EXISTS子查询
如果你更习惯子查询的写法,也可以用NOT EXISTS来判断:
SELECT product_id, category_id FROM your_table t1 WHERE NOT EXISTS ( SELECT 1 FROM your_table t2 WHERE t2.product_id = t1.product_id AND t2.parent_category = t1.category_id );
逻辑拆解:
- 对
t1里的每一行,检查同商品下有没有以当前category_id为父类别的行 - 如果不存在这样的行(
NOT EXISTS成立),就说明这是叶子节点,保留该行
测试验证(用你的示例数据)
原表数据:
+------------+-------------+------------------+ | product_id | category_id | parent_category | +------------+-------------+------------------+ | 1 | aaa | 0 | | 1 | bbb | aaa | | 1 | ccc | bbb | | 2 | aaa | 0 | | 2 | bbb | aaa | | 2 | ddd | 0 |
执行任意一个SQL,都会得到你期望的结果:
+------------+---------------+ | product_id | category_id | +------------+---------------+ | 1 | ccc | | 2 | bbb | | 2 | ddd | +------------+---------------+
复杂场景验证
针对你提到的场景:类别结构是aaa->bbb->ccc、ddd->eee->fff,商品关联了aaa、bbb、ddd。此时:
aaa有子类别bbb,会被排除bbb没有子类别(商品没关联ccc),会被保留ddd没有子类别(商品没关联eee),会被保留
执行SQL后会得到bbb和ddd,完全符合你的需求。
性能小贴士
这两种方法都比PHP循环高效太多,因为数据库是做集合操作的,比应用层逐行处理数据快得多。如果你的表数据量很大,可以给product_id和parent_category建立联合索引,能进一步提升查询速度。
内容的提问来源于stack exchange,提问作者Hube
相关产品推荐
相关产品推荐

