LATERAL 让每一行都有自己的子查询窗口
LATERAL 允许 FROM 中的一个项目引用它前面项目提供的列。每个父记录需要一个带自身参数的小结果集时,它很实用,例如每位客户最近的两笔订单。整个查询仍是一条 SQL、一次数据库往返,横向子查询明确表达哪些外层值决定当前结果。但这种逻辑关系不承诺唯一的物理执行策略,规划器仍可进行保持语义的优化。 把关联条件写清楚 普通 FROM 子查询不能随意引用旁边表的列,LATERAL 允许在合适的连接位置使用这些引用。o.customer_id = c.id 才是当前客户与订单之间的关联;只写关键词不会自动产生这个条件。如果删除关联,每位客户可能都拿到同一份全局结果。语法通过只能证明语句合法,不能证明取到的是这个客户自己的订单。 实现每组前几名时,把父记录过滤条件、ORDER BY 和 LIMIT 都放进横向子查询。放在整个连接外面的 LIMIT 限制的是最终组合结果,不是每位客户最多几笔订单。排序还需要唯一的决胜字段:两笔订单完全可能具有相同时间戳,只按时间排序时,边界位置选择哪一行没有保证,换一个计划就可能换一个答案。 子查询里的 ORDER BY 决定入选哪些行,并不保证最终结果的展示顺序。客户端要求稳定的客户与订单顺序时,还需要外层 ORDER BY。导出报表和自动化检查尤其依赖这一点;不能因为当前执行计划碰巧按某个顺序返回,就把这种偶然表现当成接口契约,否则升级或统计信息刷新后会出现难以解释的行顺序变化。 决定没有匹配时保留什么 CROSS JOIN LATERAL 在子查询没有返回行时会丢掉对应父记录。LEFT JOIN LATERAL 配合 ON true 则保留父记录,并把右侧列补成 null。对尚未下单的客户,后者往往才符合页面或报表需求。如果把右侧条件移到外层 WHERE,补出来的 null 行可能被过滤掉,原本想保留空客户的行为就被破坏了。 聚合还有一层区别。COUNT(r.id) 只统计非空订单标识,COUNT(*) 连左连接补出的那一行也会计算。没有 GROUP BY 的相关聚合,即使输入为空也可能返回一行,COUNT 是零,SUM 则是 null。因此不能把所有 LATERAL 表达式都理解成同一种列表查询,必须看它实际返回零行、一行还是多行,以及每一列的空值含义。 ...