LATERAL 是什么
LATERAL 让 FROM 里的子查询可以引用同一 FROM 中写在它左侧的表,效果类似「逐行的相关子查询」,区别是它能返回多列甚至多行。典型用途是「每组取最新一条并带上该行的其他字段」——标量子查询做不到返回多列,普通 JOIN 又要额外绕一圈。PostgreSQL 9.3+ 与 MySQL 8.0.14+ 支持,MySQL 只允许 LATERAL 用在派生表位置,范围比 PostgreSQL 窄;另一种等效思路是窗口函数 ROW_NUMBER。
基本信息
| 适用平台 | PostgreSQL 9.3+ / MySQL 8.0.14+ |
|---|---|
| 语义 | FROM 中的子查询可以引用它左侧的表 |
| MySQL 限制 | 只支持 LATERAL 派生表,不能用于函数调用等其他位置 |
| 替代方案 | 窗口函数 ROW_NUMBER,一次扫描后筛选 |
| 性能关键 | 内层查询要走索引,否则逐行执行的代价很高 |
典型使用场景
- 每组取最新一条记录并带上该行的其他列
- 明细行附带该组的前 N 条子记录
- 需要引用外层列做相关计算的展开查询
- 与 JSON 数组展开配合,逐行展开并关联
- 替换返回多列的标量子查询写法
- 与返回集合的函数配合:这类函数写在 FROM 里已隐含 LATERAL 语义
PostgreSQL 写法
PostgreSQL 写法
SELECT u.id, u.name, o.amount, o.created_at
FROM users u
CROSS JOIN LATERAL (
SELECT amount, created_at
FROM orders o
WHERE o.user_id = u.id
ORDER BY o.created_at DESC, o.id DESC
LIMIT 1
) o;MySQL 写法与窗口函数替代
MySQL 写法与窗口函数替代
-- MySQL:LATERAL 只能放在 FROM 的派生表位置
SELECT u.id, u.name, o.amount, o.created_at
FROM users u,
LATERAL (
SELECT amount, created_at
FROM orders o
WHERE o.user_id = u.id
ORDER BY o.created_at DESC, o.id DESC
LIMIT 1
) o;
-- 等效思路:窗口函数一次扫描后过滤行号
SELECT id, user_id, amount, created_at
FROM (
SELECT id, user_id, amount, created_at,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC, id DESC) AS rn
FROM orders
) t WHERE t.rn = 1;LATERAL 与窗口函数对比
| 对比项 | LATERAL | 窗口函数 ROW_NUMBER |
|---|---|---|
| 执行方式 | 外层每行执行一次内层 | 一次扫描后统一编号 |
| 索引利用 | 必须走索引才高效 | 需要排序,可能额外排序 |
| 写法可读性 | 接近「每组取一条」的直觉 | 需包一层派生表过滤行号 |
| 适用场景 | 每组取少量列、内层条件复杂 | 大结果集统一编号与筛选 |
PostgreSQL 里子查询要引用左侧表必须显式写 LATERAL 或 CROSS JOIN LATERAL;而返回集合的函数(如 jsonb_array_elements)写在 FROM 中时隐含 LATERAL 语义,可以直接引用左侧的列。
LATERAL 子查询通常逐行执行,外层行数大且内层没有可用索引时会明显变慢;先用 EXPLAIN 与窗口函数写法对比,确认哪个更划算再定稿。