LATERAL比ROW_NUMBER()快,因其对每组单独执行带LIMIT的子查询,配合(dept_id, salary)复合索引可快速定位、排序、截断,仅读最多N行/组;而ROW_NUMBER()需全表扫描、排序、编号后再过滤,IO与排序开销大。

LATERAL 是 PostgreSQL 16 中实现每组 Top-N 最高效的方式之一,前提是子查询能走索引 —— 否则它会比 ROW_NUMBER() 更慢。
为什么 LATERAL 比 ROW_NUMBER() 在 Top-N 场景下更快
因为 ROW_NUMBER() 必须对整个分组扫描、排序、编号,再过滤出前 N 行;而 LATERAL 对每个分组单独执行一次带 LIMIT N 的子查询,只要索引设计得当,数据库可直接定位、排序、截断,实际只读取最多 N 行/组。
- 必须有复合索引,且分组字段在最左:比如按
dept_id分组取 Top-2 薪资,索引应为CREATE INDEX ON employees (dept_id, salary DESC);只建(dept_id)或(salary)单列索引无效 -
ROW_NUMBER()在分组数少、N 大(如 Top-50)时反而更稳;LATERAL在分组数多、N 小(如 Top-1~3)且索引到位时优势明显 - EXPLAIN 中看到
Lateral节点下嵌套的是Index Scan(而非Seq Scan),且Actual Rows接近预期 N×分组数,说明索引生效
LATERAL 写法必须带 JOIN 和别名,漏写就报错
常见错误是写成 FROM departments, LATERAL (SELECT ...) 或直接在 SELECT 里嵌子查询 —— 这两种都会触发 ERROR: invalid reference to FROM-clause entry。
- 正确写法必须显式用
JOIN LATERAL或LEFT JOIN LATERAL,并带ON true - 外层表必须有别名(如
d),子查询中引用必须带该别名(d.dept_id),不能写departments.dept_id - 子查询本身也必须有别名(如
e),否则后续无法在SELECT或GROUP BY中引用其字段 - 示例(每个部门取工资最高的 2 名员工并算平均值):
SELECT d.dept_id, AVG(e.salary) AS avg_top2_salary FROM departments d JOIN LATERAL ( SELECT salary FROM employees e WHERE e.dept_id = d.dept_id ORDER BY e.salary DESC LIMIT 2 ) e ON true GROUP BY d.dept_id;
LEFT JOIN LATERAL 才能保留空分组,但 ON true 不可省
如果某个部门没有员工,用 JOIN LATERAL 会导致该部门整行被丢弃;想保留所有部门(空部门显示 NULL 或用 COALESCE 补默认值),必须改用 LEFT JOIN LATERAL。
-
LEFT JOIN LATERAL后必须写ON true(或ON 1=1),语法强制要求,不写会报错 - 此时子查询返回空,右侧行全为
NULL,AVG()会跳过该组(结果为NULL),如需返回 0,得用COALESCE(AVG(e.salary), 0) - MySQL 8.0+ 不支持
LEFT JOIN LATERAL,只能靠UNION ALL补空行;SQL Server 用OUTER APPLY等价替代
UNNEST 和 JSON 展开必须配 LATERAL,但大数组要提前截断
对 jsonb_array_elements() 或 UNNEST() 这类集合返回函数(SRF),PostgreSQL 允许隐式 LATERAL(写在 SELECT 列表里也能跑),但不可控、无法前置过滤、容易爆炸。
- 显式写
LATERAL UNNEST(col) AS t(elem)才能确保绑定外层行,并在子查询里加WHERE或嵌套限制 - 单行含上千元素时,
UNNEST()会生成海量中间行;应在展开前用子查询截断:LATERAL (SELECT elem FROM UNNEST(p.tags) AS elem LIMIT 100) - 若字段是逗号分隔字符串(如
cst.is_job),FIND_IN_SET类函数无法走索引,应转为jsonb或独立关联表
真正卡住性能的往往不是 LATERAL 语法本身,而是外层 10 万行触发了 10 万次没走索引的子查询 —— 每次都扫全表,比 ROW_NUMBER() 还糟。先看 EXPLAIN 里 Lateral 下是不是 Index Scan,再决定要不要换方案。










