DBAtlas · Атлас эксплуатации баз данных

LATERAL / CROSS APPLY (построчный подзапрос)

Oracle

-- Each customer's 3 most recent orders (LATERAL, 12c+)
SELECT m.name, s.created_at, s.amount
FROM customer m,
     LATERAL (SELECT created_at, amount FROM orders
              WHERE customer_id = m.id
              ORDER BY created_at DESC FETCH FIRST 3 ROWS ONLY) s;

-- Alternative: CROSS APPLY (12c+)
SELECT m.name, s.amount
FROM customer m
CROSS APPLY (SELECT amount FROM orders WHERE customer_id = m.id) s;

LATERAL/APPLY позволяет подзапросу в FROM ссылаться на столбцы таблицы слева, поэтому можно писать коррелированные соединения вроде «первые N строк для каждой строки». Oracle 12c поддерживает и синтаксис ANSI LATERAL, и CROSS/OUTER APPLY.

PostgreSQL

SELECT m.name, s.created_at, s.amount
FROM customer m
CROSS JOIN LATERAL (
  SELECT created_at, amount FROM orders
  WHERE customer_id = m.id
  ORDER BY created_at DESC LIMIT 3
) s;

В PostgreSQL подзапросы, зависящие от строки слева, пишутся с ключевым словом LATERAL (JOIN LATERAL / CROSS JOIN LATERAL / LEFT JOIN LATERAL). Очень удобно для «первых N в группе» и развёртывания строк функциями, возвращающими множества. Синтаксиса CROSS APPLY нет; используется LATERAL.

MySQL

-- MySQL 8.0.14+ supports LATERAL
SELECT m.name, s.amount
FROM customer m
JOIN LATERAL (
  SELECT amount FROM orders
  WHERE customer_id = m.id
  ORDER BY created_at DESC LIMIT 3
) s ON true;

С MySQL 8.0.14 поддерживаются производные таблицы LATERAL; подзапрос может ссылаться на столбцы внешней таблицы. До этого, а также для CROSS APPLY, эквивалента не было; «первые N в группе» решались оконными функциями или коррелированными подзапросами. В MariaDB LATERAL пока нет.

SQL Server

SELECT m.name, s.created_at, s.amount
FROM customer m
CROSS APPLY (
  SELECT TOP 3 created_at, amount FROM orders
  WHERE customer_id = m.id
  ORDER BY created_at DESC
) s;
-- Use OUTER APPLY to return non-matching rows too

Эквивалент LATERAL в SQL Server — оператор APPLY: CROSS APPLY (как INNER JOIN LATERAL, отбрасывает несовпадения) и OUTER APPLY (как LEFT JOIN LATERAL, NULL для несовпадений). Обычно используется для вызова табличных функций для каждой строки и запросов «первые N в группе».

MongoDB

// Closest equivalent: $lookup with a sub-pipeline
db.customer.aggregate([
  { $lookup: {
      from: "orders",
      let: { mid: "$id" },
      pipeline: [
        { $match: { $expr: { $eq: ["$customer_id", "$$mid"] } } },
        { $sort: { created_at: -1 } }, { $limit: 3 }
      ],
      as: "last3"
  } }
])

В MongoDB эквивалент LATERAL/APPLY — форма $lookup с вложенным конвейером и переменными let: поля внешнего документа передаются во вложенный конвейер через let как $$variable, где выполняются сопоставление/сортировка/ограничение. Так решается паттерн «связанные первые N для каждого документа».

Redis

# Not applicable: Redis has no subqueries / LATERAL.

Redis не предлагает построчных коррелированных подзапросов. Паттерн «связанные первые N для каждого элемента» решается циклом на стороне приложения (напр. отдельный ZRANGE на каждый ключ) или серверным Lua-скриптом.

Elasticsearch

# Closest equivalent: terms + top_hits sub-aggregation
GET orders/_search
{
  "size": 0,
  "aggs": { "by_customer": { "terms": { "field": "customer_id" },
    "aggs": { "last3": { "top_hits": {
      "sort": [ { "created_at": "desc" } ], "size": 3 } } } } }
}

В Elasticsearch нет LATERAL/APPLY; паттерн «первые N записей в группе» достигается вложенной агрегацией top_hits внутри агрегации terms. Вместо коррелированного подзапроса это группировка плюс выборка внутри группы по одному индексу.

Открыть в DBAtlas →