PostgreSQL 中 LATERAL JOIN 横向关联

FreeGuideOnline 最新 2026-07-07

sql SELECT ... FROM left_table JOIN LATERAL ( subquery_or_function ) AS alias ON true


- `LATERAL` 只能出现在 `FROM` 子句的连接中,可以用于 `INNER JOIN`、`LEFT JOIN` 等。
- 右侧的子查询或函数可以引用左侧表的任何列。
- 对于内连接,可以省略 `ON true`(但显式写出更清晰);对于左连接,必须保留 `ON` 条件或 `ON true`。

另一种常见写法是直接使用逗号,这在 PostgreSQL 中等价于 `CROSS JOIN LATERAL`:

```sql
SELECT *
FROM left_table,
LATERAL some_function(left_table.column) AS alias;

示例一:与集返回函数结合

假设有一个表 projects,每条记录代表一个项目,我们希望为每个项目生成从 1 到 tasks_count 的任务编号。

SELECT p.project_name, task_no
FROM projects p
CROSS JOIN LATERAL generate_series(1, p.tasks_count) AS task_no;

这里 generate_series 的第二个参数引用了 p.tasks_count,如果没有 LATERAL,这句会报错。该查询为每个项目产生与其任务数相等的行。


示例二:查找每个分类下最新的 N 条记录

有一个 products 表,每个产品属于一个分类,我们想取出每个分类中价格最高的 3 个产品。

SELECT c.category_name, best.name, best.price
FROM categories c
LEFT JOIN LATERAL (
    SELECT p.name, p.price
    FROM products p
    WHERE p.category_id = c.id
    ORDER BY p.price DESC
    LIMIT 3
) best ON true;

对于每个分类 c,右侧子查询都会使用 c.id 作为过滤条件,取出其价格最高的 3 个产品。使用 LEFT JOIN 可以保留没有产品的分类。


示例三:在 SELECT 中多次引用计算列

有时我们需要基于一个复杂的计算结果做过滤,同时又想在结果中保留该值。LATERAL 可以让计算只发生一次。

SELECT u.username, calc.total_spent
FROM users u,
LATERAL (
    SELECT COALESCE(SUM(amount), 0) AS total_spent
    FROM orders
    WHERE orders.user_id = u.id
) calc
WHERE calc.total_spent > 1000;

这里 calc.total_spent 不仅出现在 SELECT 中,还用于 WHERE 过滤,而聚合计算只在 LATERAL 子查询中执行一次。


LATERAL 与 LEFT JOIN 的配合

LEFT JOIN LATERAL 的行为和普通左连接一致:当右侧子查询没有返回任何行时,右侧列填充 NULL

这对于“可能没有结果”的场景非常有用,例如为每篇文章获取最新的一条评论,即使文章没有评论也显示一行:

SELECT a.title, latest.comment_body, latest.created_at
FROM articles a
LEFT JOIN LATERAL (
    SELECT comment_body, created_at
    FROM comments
    WHERE article_id = a.id
    ORDER BY created_at DESC
    LIMIT 1
) latest ON true;