在PostgreSQL的查询场景中,相关子查询是很多开发者习惯使用的写法,它依赖外层查询的每一行数据来驱动内层子查询的执行,但在数据量较大时容易出现性能问题。LATERAL JOIN提供了一种更灵活的关联查询方式,能够在连接过程中引用外层查询的列,实现和相关子查询类似的逻辑,同时具备更好的优化空间。

相关子查询与LATERAL JOIN的基本概念
相关子查询
相关子查询是指子查询的执行依赖外层查询的当前行数据,外层查询每处理一行,子查询就会执行一次。比如我们需要查询每个用户最近的一笔订单信息,就可以用相关子查询实现。
以下是一个典型的相关子查询示例,查询每个用户的最新订单:
-- 用户表
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50)
);
-- 订单表
CREATE TABLE orders (
id INT PRIMARY KEY,
user_id INT,
order_time TIMESTAMP,
amount DECIMAL(10,2)
);
-- 相关子查询写法:查询每个用户的最新订单
SELECT
u.id AS user_id,
u.name,
o.id AS order_id,
o.order_time,
o.amount
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
AND o.order_time = (
SELECT MAX(order_time)
FROM orders o2
WHERE o2.user_id = u.id
);
LATERAL JOIN
LATERAL JOIN允许右边的子查询引用左边表的列,和普通JOIN不同,LATERAL子查询会为左边表的每一行执行一次,类似相关子查询的执行逻辑,但PostgreSQL优化器可以对LATERAL JOIN做更多优化。LATERAL可以配合JOIN使用,也可以配合LEFT JOIN使用,保留左边表没有匹配到右边结果的行。
用LATERAL JOIN替换相关子查询的示例
场景1:查询每个用户的最新订单
上面的相关子查询示例可以改写为LATERAL JOIN的写法,逻辑更清晰,执行效率也更高:
-- 用LATERAL JOIN替换相关子查询,查询每个用户的最新订单
SELECT
u.id AS user_id,
u.name,
o.id AS order_id,
o.order_time,
o.amount
FROM users u
LEFT JOIN LATERAL (
SELECT *
FROM orders o2
WHERE o2.user_id = u.id
ORDER BY o2.order_time DESC
LIMIT 1
) o ON true;
这里LATERAL子查询会为每一个用户查询其对应的订单,按时间倒序取第一条,也就是最新的订单,通过LEFT JOIN LATERAL可以保留没有订单的用户,结果和之前的相关子查询一致。
场景2:带聚合条件的相关子查询替换
如果相关子查询中包含聚合函数,比如查询每个用户的订单总金额,同时只保留金额大于平均订单金额的订单,相关子查询写法如下:
-- 相关子查询写法:查询每个用户中金额大于该用户平均订单金额的订单
SELECT
u.id AS user_id,
u.name,
o.id AS order_id,
o.amount
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE o.amount > (
SELECT AVG(o2.amount)
FROM orders o2
WHERE o2.user_id = u.id
);
改写为LATERAL JOIN的写法:
-- LATERAL JOIN替换写法
SELECT
u.id AS user_id,
u.name,
o.id AS order_id,
o.amount
FROM users u
JOIN orders o ON o.user_id = u.id
JOIN LATERAL (
SELECT AVG(o2.amount) AS avg_amount
FROM orders o2
WHERE o2.user_id = u.id
) avg_info ON o.amount > avg_info.avg_amount;
场景3:多列匹配的相关子查询替换
如果相关子查询需要匹配多个列,比如查询每个用户在每个月份的最新订单,相关子查询写法可能比较复杂,用LATERAL JOIN可以更直观:
-- 相关子查询写法:查询每个用户每个月份的最新订单
SELECT
u.id AS user_id,
u.name,
DATE_TRUNC('month', o.order_time) AS order_month,
o.id AS order_id,
o.order_time,
o.amount
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE o.order_time = (
SELECT MAX(o2.order_time)
FROM orders o2
WHERE o2.user_id = u.id
AND DATE_TRUNC('month', o2.order_time) = DATE_TRUNC('month', o.order_time)
);
改写为LATERAL JOIN的写法:
-- LATERAL JOIN替换写法
SELECT
u.id AS user_id,
u.name,
DATE_TRUNC('month', o.order_time) AS order_month,
o.id AS order_id,
o.order_time,
o.amount
FROM users u
JOIN LATERAL (
SELECT
o2.*,
DATE_TRUNC('month', o2.order_time) AS order_month
FROM orders o2
WHERE o2.user_id = u.id
ORDER BY o2.order_time DESC
) o ON true;
LATERAL JOIN替换相关子查询的优势
- 执行计划更优:PostgreSQL优化器对LATERAL JOIN的支持更好,很多时候可以避免相关子查询导致的多次重复扫描,减少IO开销。
- 逻辑更清晰:LATERAL JOIN把子查询的逻辑放在连接部分,整个查询的结构更符合关联查询的阅读习惯,后续维护更容易理解。
- 功能更灵活:LATERAL子查询中可以写更复杂的逻辑,比如包含LIMIT、ORDER BY等操作,而相关子查询在这些场景下写法会更繁琐。
注意事项
虽然LATERAL JOIN可以替换大部分相关子查询,但并不是所有场景都适用:
- 如果子查询不依赖外层查询的列,不需要使用LATERAL JOIN,普通JOIN即可。
- LATERAL JOIN的子查询如果返回多行,会和外层行做笛卡尔积,需要确认业务逻辑是否符合预期,必要时加LIMIT限制。
- 在低版本的PostgreSQL中,LATERAL JOIN的支持可能有限,需要确认数据库版本是否在9.3及以上,因为LATERAL语法是在9.3版本引入的。
在实际开发中,建议先通过EXPLAIN命令对比相关子查询和LATERAL JOIN的执行计划,选择性能更好的写法。对于数据量较大、相关子查询执行较慢的场景,优先尝试用LATERAL JOIN改写,通常能获得明显的性能提升。
PostgreSQLLATERAL_JOIN相关子查询SQL优化修改时间:2026-07-04 08:24:27