最稳解法是先用子查询聚合再JOIN:对order_items按order_id聚合SUM/COUNT,customers子查询过滤active;ON中加右表条件控基数,避免WHERE后过滤导致膨胀。

直接用子查询先聚合,再JOIN回主表,是解决分组中笛卡尔积最稳的路。不是加个 DISTINCT 或调换 WHERE 位置就能修好的——问题出在 JOIN 后行数已经膨胀,GROUP BY 只是“事后补救”,越补越错。
为什么 COUNT/SUM 结果翻倍?
本质是中间结果被放大了:比如 orders 和 order_items 一对多,再 LEFT JOIN customers,只要 order_items 有 3 条,customers 有 1 条,那这单就变成 3 行;COUNT(*) 就是 3,SUM(amount) 也会算 3 遍。
- 查执行计划时注意
MERGE JOIN CARTESIAN或预估行数突然跳到百万/十亿级 - 用
EXPLAIN或dbms_xplan.display_cursor看实际Rows列,对比你预期的主表行数 - 别只看最终
GROUP BY后的行数——膨胀发生在它之前
子查询聚合怎么写才不踩坑?
核心是让每个被 JOIN 的“多”侧表,先按关联键聚合成单行,再上卷。不能留明细字段直接连。
- 对
order_items:写成(SELECT order_id, SUM(quantity) AS qty_sum, COUNT(*) AS item_cnt FROM order_items GROUP BY order_id) - 对
customers:如果只取name,就别LEFT JOIN customers,改用(SELECT id, name FROM customers WHERE status = 'active') - 千万别在子查询里漏
GROUP BY关联字段,否则又变交叉连接 - 子查询外层别加
ORDER BY或LIMIT——优化器可能无法下推,导致全表扫
LEFT JOIN 中 ON 和 WHERE 放错位置会加剧笛卡尔积
这不是语义区别问题,是物理执行顺序问题:放 WHERE 会先 JOIN 出一堆行,再过滤;放 ON 才能控制右表参与连接的基数。
- 错误写法:
LEFT JOIN customers c ON o.customer_id = c.id WHERE c.status = 'active'→ 先全量 JOIN,再踢掉 NULL 和非 active 行,膨胀已发生 - 正确写法:
LEFT JOIN customers c ON o.customer_id = c.id AND c.status = 'active'→ 右表只拉符合条件的行进来,从源头控量 - 如果右表本身有重复(如历史客户表含多条同
id记录),光靠ON不够,必须先子查询去重或取最新一条
真正难的不是写出聚合子查询,而是判断哪张表该聚合、按什么字段聚合、是否要加时间范围或状态过滤——这些都得贴着业务逻辑抠。一不留神,子查询里漏了个 AND deleted = 0,结果还是错的。










