首页/数据/SQL 速查/先聚合再连接

先聚合再连接

先在子查询里过滤并汇总,得到小结果集再连接主表,通常比连接全部明细再聚合更省资源。

SQL 速查数据更新于 2026-10-13

先聚合再连接是什么

处理一对多关系时,常规思路是先在小结果集上做过滤与聚合,再与主表连接取名称等字段:把 SUM、COUNT、MAX 放进子查询,副表被压缩成每组一行,连接与后续排序的输入都变小了。相比先连接全部明细再聚合,这种写法能明显减少中间结果。前提是聚合口径能在子查询内算完;如果统计维度依赖主表列,就需要改写或换用别的写法。改写后务必用 EXPLAIN 复核,旧版本可能先把派生表整体物化。

基本信息

核心思路过滤与聚合放在子查询,连接发生在小结果集上
收益减少连接输入行数与聚合中间结果
版本注意MySQL 8.0.22 起支持派生表条件下推,旧版本可能整体物化
PostgreSQLFROM 子查询一般被内联展开,改写后计划通常直接体现
口径风险聚合层级不提前定,外层再聚合会把金额重复累计

典型使用场景

  • 用户列表带订单总额与最近下单时间
  • 商品列表带销量、库存与最新售价
  • 报表按维度汇总后关联维度名称与属性
  • 大表关联小维度:先把大表聚合变小,再连接
  • 替代逐行执行的标量子查询,避免外层行数放大时性能骤降

先聚合再连接

先聚合再连接
SELECT u.id, u.name, s.total, s.last_order
FROM users u
JOIN (
    SELECT user_id,
           SUM(amount)    AS total,         # 聚合在子查询里完成
           MAX(created_at) AS last_order
    FROM orders
    WHERE status = 1                        # 先过滤,缩小聚合输入
    GROUP BY user_id
) s ON s.user_id = u.id
ORDER BY s.total DESC;

几种写法的差别

写法连接输入聚合中间结果
先聚合再连接副表已压缩为每组一行小,只处理分组结果
先连接再聚合明细展开后的放大行数大,聚合要处理放大后的行
标量子查询逐行每行执行一次子查询取决于索引与执行次数
CTE 物化一次复用一次计算多次使用需注意物化开销与内存
MySQL 8.0.22 起支持派生表条件下推,能把外层过滤条件推进子查询内部;旧版本没有该能力,升级数据库后重跑 EXPLAIN 常能看到计划明显变化,这也是版本迁移后需要复核计划的原因之一。
聚合层级必须提前定:先连接明细再在外层 SUM,会把一对多放大后的行重复累计,金额类指标成倍偏高,这类口径错误往往要等对账时才发现。

易错点

  • 先连接明细再在外层聚合,一对多放大后金额被重复累计
  • 派生表在旧版本被整体物化,改写后没跑 EXPLAIN 复核,预期的收益落空
  • 子查询里过滤条件写漏,聚合输入仍是全量数据
  • 用标量子查询逐行取统计值,外层行数一大就反复执行,性能急剧下降

数据来源与参考

  • MySQL 8.0 Reference Manual · Optimizing Subqueries
  • PostgreSQL Documentation · Subquery Expressions
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌