lateral在n小(≤10)、分组数多、有复合索引时最高效,适用于每组top-n后聚合;n大(如top-50)或分组少时应改用row_number()。

LATERAL 不是“更高效”的万能写法,而是在特定场景下比 ROW_NUMBER() 或标量子查询快得多——关键看你要算什么、数据量多大、有没有合适索引。
什么时候用 LATERAL 真正快?
典型场景:对每个分组取 Top-N 行后做聚合(比如“每个部门工资最高的 2 人平均薪资”),且 N 很小(≤10)、分组数很多(如几千个部门)。
原因很实在:LATERAL 允许数据库为每个分组单独执行带 LIMIT 的子查询,配合 (dept_id, salary) 这类复合索引,实际只读最多 N 行/组;而 ROW_NUMBER() 必须先给全表打序号,IO 和排序开销巨大。
- PostgreSQL 和 MySQL 8.0+ 支持
LATERAL,SQL Server 对应的是CROSS APPLY - 必须有能覆盖排序字段的复合索引,否则
LATERAL优势归零 - 如果 N 较大(如 Top-50),或分组总数很少(如只有 5 个品类),
ROW_NUMBER()反而更稳
LATERAL 常见报错和写法陷阱
报错 invalid reference to FROM-clause entry 几乎全是引用没加别名导致的。
- 外层表必须显式加别名,子查询里引用时要带这个别名,比如
d.dept_id,不能写departments.dept_id -
LATERAL关键字不能省——LEFT JOIN (SELECT ...)是普通子查询,不支持外层列引用 - 子查询里
WHERE条件必须用等值谓词(=),不能用!=、IS NULL等非确定性条件 - MySQL 8.0 有时需关掉优化器合并:
SET optimizer_switch='derived_merge=off';
替代方案:什么时候该放弃 LATERAL?
如果你要的不是聚合值,而是每组前 N 条的完整行(含姓名、ID、邮箱等多列),且 N > 10 或分组数 ROW_NUMBER() 更合适。
-
LATERAL展开后会变成宽表结构,JOIN 后结果集可能爆炸(比如 1000 个部门 × 10 行 = 1 万行),内存和网络压力陡增 - 需要同时输出排名 + 累计占比(如
SUM() OVER()),只能靠窗口函数,LATERAL无法嵌套窗口 - 旧版 MySQL(5.7)、SQLite、部分云数据库不支持
LATERAL,强行用会直接报语法错误 - 子查询里用了
LIMIT就没法被 PawSQL 这类优化器重写成解关联形式——它只处理无LIMIT的聚合型LATERAL
真正难的不是写对语法,而是判断「这需求到底适不适合用 LATERAL」——得看执行计划里是不是真走了索引扫描,而不是悄悄退化成嵌套循环。











