Что такое LATERAL JOIN в PostgreSQL?

SeniorPostgreSQL · Backend·Обновлено 13 августа 2026
Коротко
LATERAL JOIN — это модификатор, который позволяет подзапросу или функции в секции FROM обращаться к столбцам таблиц, перечисленных левее в том же FROM. Подзапрос выполняется отдельно для каждой строки внешней таблицы, что делает его аналогом «цикла по строкам» на уровне SQL.

LATERAL JOIN в PostgreSQL

Bez LATERAL подзапрос в секции FROM вычисляется один раз и изолирован от соседних таблиц. Ключевое слово LATERAL снимает это ограничение: подзапрос превращается в коррелированный и выполняется отдельно для каждой строки левой таблицы, при этом может возвращать произвольное количество строк и столбцов.

Синтаксис

SELECT t1.id, sub.value
FROM table1 t1
JOIN LATERAL (
  SELECT value
  FROM table2 t2
  WHERE t2.foreign_id = t1.id  -- ссылка на строку из t1
  ORDER BY t2.created_at DESC
  LIMIT 3
) sub ON true;

ON true обязателен синтаксически при использовании JOIN LATERAL — условие соединения уже задано внутри подзапроса.

Типичные сценарии

TOP-N на группу — классическая задача, где LATERAL значительно проще ROW_NUMBER() + CTE:

-- Последние 3 заказа для каждого пользователя
SELECT u.name, o.id AS order_id, o.total
FROM users u
JOIN LATERAL (
  SELECT id, total
  FROM orders
  WHERE user_id = u.id
  ORDER BY created_at DESC
  LIMIT 3
) o ON true;

Раскрытие массивов — функции в FROM неявно LATERAL:

-- Короткий синтаксис: запятая вместо JOIN LATERAL
SELECT u.name, tag
FROM users u,
     unnest(u.tags) AS tag;

Вычисление агрегата с контекстом строки:

SELECT p.title, stats.avg_rating
FROM products p
JOIN LATERAL (
  SELECT avg(rating) AS avg_rating
  FROM reviews r
  WHERE r.product_id = p.id
    AND r.created_at > p.release_date  -- release_date из внешней строки
) stats ON true;

LEFT JOIN LATERAL

Если внутренний подзапрос не возвращает строк, обычный JOIN LATERAL отфильтрует строку внешней таблицы. LEFT JOIN LATERAL ... ON true сохраняет все строки:

SELECT u.name, COALESCE(last_order.total, 0) AS last_total
FROM users u
LEFT JOIN LATERAL (
  SELECT total FROM orders
  WHERE user_id = u.id
  ORDER BY created_at DESC
  LIMIT 1
) last_order ON true;

LATERAL vs коррелированный подзапрос в SELECT

Аспект Подзапрос в SELECT LATERAL в FROM
Место SELECT-список FROM-список
Возвращает одно скалярное значение множество строк и столбцов
LEFT JOIN невозможен поддерживается
LIMIT внутри ограничен свободно

Производительность

Планировщик выбирает Nested Loop: для каждой строки внешней таблицы выполняется отдельный seek во внутренней. Это эффективно только при наличии индекса по join-колонке. Без индекса LATERAL деградирует до N полных сканирований. Проверяйте план через EXPLAIN (ANALYZE, BUFFERS).

Что хочет услышать интервьюер

Кандидат объясняет главное: LATERAL разрешает подзапросу ссылаться на столбцы внешней таблицы и возвращать несколько строк — в отличие от обычного подзапроса в FROM

Называет практические сценарии: TOP-N per group, раскрытие массивов через unnest(), вычисления с контекстом конкретной строки

Понимает разницу между JOIN LATERAL и LEFT JOIN LATERAL, знает зачем ON true

Осознаёт, что функции в FROM (unnest, generate_series) неявно LATERAL

Говорит о производительности: N выполнений подзапроса, критичность индекса на join-колонке, Nested Loop в плане

Пример: TOP-3 заказа на каждого пользователя

SELECT u.id, u.name, o.id AS order_id, o.total, o.created_at
FROM users u
JOIN LATERAL (
  SELECT id, total, created_at
  FROM orders
  WHERE user_id = u.id          -- ссылка на текущего пользователя
  ORDER BY created_at DESC
  LIMIT 3
) o ON true;
-- Индекс: CREATE INDEX ON orders(user_id, created_at DESC);

Пример: LEFT JOIN LATERAL — пользователи без заказов тоже попадут в результат

SELECT u.name,
       COALESCE(last_order.total, 0) AS last_total
FROM users u
LEFT JOIN LATERAL (
  SELECT total
  FROM orders
  WHERE user_id = u.id
  ORDER BY created_at DESC
  LIMIT 1
) last_order ON true;
-- Без LEFT: строки без заказов отфильтруются как при INNER JOIN

Пример: Раскрытие массива тегов через unnest (неявный LATERAL)

-- Запятая в FROM — это сокращение JOIN LATERAL
SELECT u.name, tag
FROM users u,
     unnest(u.tags) AS tag;

-- Эквивалентная явная форма:
SELECT u.name, tag
FROM users u
JOIN LATERAL unnest(u.tags) AS tag ON true;

Типичные ошибки

Путают LATERAL с коррелированным подзапросом в SELECT: оба работают построчно, но LATERAL в FROM и возвращает множество строк/столбцов

Забывают ON true после LATERAL-подзапроса — без него синтаксис некорректен

Применяют LATERAL там, где достаточно обычного JOIN + GROUP BY, усложняя запрос без необходимости

Не создают индекс на join-колонке внутренней таблицы и получают N полных сканирований

Не знают, что синтаксис через запятую (FROM t1, unnest(...)) — это неявный LATERAL без ключевого слова

Лучшие курсы по теме

изображение курса

Docker и Ansible

Антон Ларичев
AI-тренажерыAI-тренажеры
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Node.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Nest.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.6
3 999 ₽ 6 990 ₽
Подробнее