派生表是避免标量子查询反复执行的关键手段,必须加括号和语义化别名,否则MySQL报错;它显式实现“先聚合再连接”,但性能依赖联合索引与统计信息。

派生表(Derived Table)在多层聚合场景下不是“锦上添花”,而是避免嵌套子查询反复执行的关键手段;它强制把“先聚合、再过滤/连接”拆成两步,否则 WHERE 或 SELECT 中的标量子查询会为每一行重算一次。
FROM 中写子查询必须加括号和别名
MySQL 直接报错 Every derived table must have its own alias,PostgreSQL 和 SQL Server 虽不报错但字段引用会歧义。这不是风格问题,是语法硬约束。
- 错误写法:
SELECT * FROM (SELECT user_id, COUNT(*) FROM orders GROUP BY user_id)—— 缺少别名,MySQL 拒绝执行 - 正确写法:
SELECT * FROM (SELECT user_id, COUNT(*) AS cnt FROM orders GROUP BY user_id) AS user_order_summary - 别名不能是保留字,比如
AS order或AS group会触发语法错误;推荐用下划线命名,如AS user_activity_stats - 即使只用一次,也别省括号:
SELECT * FROM SELECT id FROM users是非法语法,必须是SELECT * FROM (SELECT id FROM users) AS t
用派生表替代 SELECT 中的标量子查询
比如查“每个部门平均薪资”,若在 SELECT 列里写 (SELECT AVG(salary) FROM emp e WHERE e.dept_id = d.id),1000 个部门 = 执行 1000 次子查询扫描,性能断崖式下跌。
- 改成派生表后,聚合只做一次:
(SELECT dept_id, AVG(salary) AS avg_sal FROM emp GROUP BY dept_id) AS dept_avg - 再 JOIN 部门表:
SELECT d.name, da.avg_sal FROM dept d LEFT JOIN dept_avg da ON d.id = da.dept_id - 注意:LEFT JOIN 可保全部门不丢失,而 INNER JOIN 会过滤掉无员工的部门
- 如果还要加条件(如“平均薪资 > 15000”),必须在外层 WHERE 写
WHERE da.avg_sal > 15000,不能塞进派生表内部——因为派生表无法引用外层字段
聚合结果需二次过滤时,必须用派生表或 CTE
想查“平均订单金额超 500 的用户”,不能直接 WHERE AVG(amount) > 500 —— 聚合函数不能出现在 WHERE;也不能在 HAVING 后再套一层逻辑,因为 HAVING 作用域有限。
- 派生表方案:
SELECT user_id FROM (SELECT user_id, AVG(amount) AS avg_amt FROM orders GROUP BY user_id) AS u_avg WHERE avg_amt > 500 - CTE 方案(MySQL 8.0+ / PostgreSQL)更易读:
WITH user_avg AS (SELECT user_id, AVG(amount) AS avg_amt FROM orders GROUP BY user_id) SELECT user_id FROM user_avg WHERE avg_amt > 500 - 二者等价,但 CTE 支持多次引用同一中间结果,而派生表每次都要重写
- 别指望数据库自动优化:派生表默认不物化,
EXPLAIN里看到Using temporary就说明有内存开销,大结果集要警惕
真正容易被忽略的是索引配合——派生表本身不加速,它只是把聚合逻辑显式拎出来;如果 orders(user_id, amount) 没建联合索引,GROUP BY 还是全表扫描。先看执行计划,再动 SQL。

















