子查询可在SELECT中直接获取父类统计值,如对每个分类显示自身及所有子类销量总和;需通过WHERE关联父路径实现层级聚合,但MySQL 5.7不支持递归CTE,仅能模拟有限层级,且每行触发一次子查询影响性能。

子查询怎么查出父类的统计值
直接在 SELECT 里用子查询拿父类汇总,是最直观的做法。比如分类表 category 有 id、name、parent_id,想对每个子类显示“它自己销量 + 它所有子类销量之和”,就得先定位父类,再聚合。
常见错误是写成 (SELECT SUM(sales) FROM product p2 WHERE p2.category_id = c.id) —— 这只算本级,没递归。真正要的是:对当前分类 c,找出所有以它为祖先的分类(包括自己),再求和。
- 正确写法是子查询中用
WHERE关联父路径:(SELECT SUM(p.sales) FROM product p JOIN category c2 ON p.category_id = c2.id WHERE c2.id = c.id OR c2.parent_id = c.id OR c2.parent_id IN (SELECT id FROM category WHERE parent_id = c.id))—— 但这种手动展开只适用于固定 2 层 - 更通用的解法是用递归 CTE(PostgreSQL/SQL Server/MySQL 8.0+),但若只能用子查询,就需限制层级深度,且必须确保
parent_id字段允许NULL表示根节点 - 性能上,每行触发一次子查询,N 行就是 N 次全表扫描,数据量过千就要警惕响应延迟
MySQL 5.7 怎么绕过不支持 WITH 的限制
MySQL 5.7 不支持递归 WITH,但可以用多层嵌套子查询模拟两层父子关系。前提是业务能接受最多两级(如:一级类目 → 二级类目 → 商品),且二级类目不挂孙子类目。
假设表结构为 category(id, name, parent_id),商品表 product(category_id, sales),要查每个一级类目下所有直接子类目的销售总和:
SELECT c1.name AS parent_name, (SELECT SUM(p.sales) FROM product p JOIN category c2 ON p.category_id = c2.id WHERE c2.parent_id = c1.id) AS total_sales FROM category c1 WHERE c1.parent_id IS NULL;
- 关键点:外层查
c1.parent_id IS NULL得到根节点,子查询中用c2.parent_id = c1.id找直系子类,不涉及孙子类目 - 如果需要包含根类目自身的商品(即
category_id直接指向一级类目),得把子查询里的条件改成c2.parent_id = c1.id OR c2.id = c1.id - 别用
IN (SELECT ...)去查多级,MySQL 5.7 对子查询结果集大小敏感,容易触发Subquery returns more than 1 row错误
GROUP BY 和子查询一起用时为什么总数不对
典型现象:加了 GROUP BY c.id 后,子查询返回的不是当前分组的聚合值,而是全表匹配结果。本质是子查询没和外层形成关联上下文。
例如写成:
SELECT c.name, (SELECT COUNT(*) FROM product p WHERE p.category_id = c.id) cnt FROM category c GROUP BY c.id;
这其实没问题;但一旦子查询里出现跨表关联或漏写关联条件,比如:
SELECT c.name, (SELECT COUNT(*) FROM product p JOIN category c2 ON p.category_id = c2.id WHERE c2.parent_id = c.id) cnt FROM category c GROUP BY c.id;
- 这里子查询中的
c是外层category的别名,但 MySQL 在某些版本中会因作用域问题解析失败,建议显式用不同别名(如外层用c1,子查询用c2) - 如果外层
GROUP BY字段没覆盖子查询依赖的所有列,可能触发ONLY_FULL_GROUP_BY报错 - 最稳妥的方式是:子查询只依赖外层当前行的单个字段值(如
c1.id),避免在子查询中再次JOIN或GROUP
用 LEFT JOIN 替代子查询是否更安全
是的,尤其当需要汇总多层、或子查询逻辑变复杂时,LEFT JOIN + 聚合通常比相关子查询更可控、更容易调试。
比如要统计每个分类及其直接子类的总销量(含自身):
SELECT c1.id, c1.name, COALESCE(SUM(p.sales), 0) AS total_sales FROM category c1 LEFT JOIN category c2 ON c2.parent_id = c1.id OR c2.id = c1.id LEFT JOIN product p ON p.category_id = c2.id GROUP BY c1.id, c1.name;
- 注意
OR条件在JOIN中的语义:既连自己,也连子类;但会导致同一商品被重复计算(如果某商品属于子类,又恰好该子类被当作“自己”连了一次)—— 实际应拆成两个UNION或改用 CTE -
LEFT JOIN的聚合结果默认不含空组,要用COALESCE处理SUM(NULL)返回NULL的情况 - 比起子查询,
JOIN方案更容易加索引优化:确保category(parent_id)和product(category_id)都有索引
递归层级超过两层时,硬靠子查询拼接极易出错,也难维护。真要处理深树形结构,别硬扛——升级到支持递归 CTE 的数据库版本,或者把汇总逻辑移到应用层做预计算。

















