For each циклы с LATERAL соединениями

Источник: «For each loops with LATERAL Joins»
Объединять данные с помощью SQL очень просто. Однако иногда очень нужны зависимые соединения, возвращающие лишь небольшое подмножество данных объединённой таблицы. С помощью LATERAL соединения знакомый для каждого цикл может быть воспроизведён в SQL.

При объединении нескольких таблиц строки обеих таблиц связываются между собой на основе некоторых условий. Однако если результат должен быть ограничен, например, тремя последними купленными товарами для каждого покупателя, стандартное предложение соединение не будет работать: Объединённая таблица купленных товаров не может быть ограничена только тремя строками для каждого покупателя. Так не работает объединение, в него включаются все строки, соответствующие критериям объединения. Но при латеральном присоединении вместо таблицы будет присоединён подзапрос, который выполняется один раз для каждого покупателя, что приводит к эквивалентному циклу for-each в SQL.

Использование

MySQL / PostgreSQL

SELECT customers.*, recent_sales.*
FROM customers
LEFT JOIN LATERAL (
SELECT *
FROM sales
WHERE sales.customer_id = customers.customer_id
ORDER BY created_at DESC
LIMIT 3
) AS recent_sales ON true;

Подробное объяснение

Обычно соединение выполняется константным образом: Образно говоря, каждая строка одной таблицы сопоставляется с каждой строкой другой таблицы. Только если условие соединения справедливо для комбинации обеих строк, они включаются в результирующий набор. Такой алгоритм называется вложенным циклом (nested-loop join) и является наиболее простым для понимания. Однако в некоторых ситуациях при не константном соединении требуется не ограничивать весь результат, а LIMIT строки объединённой таблицы для каждой строки исходной таблицы. Но это требование невозможно при использовании константного соединения, поскольку оно нарушает реляционную алгебру, заключающуюся в простом соединении двух наборов данных с помощью некоторых операторов.

Соединение таблицы с подзапросом также не поможет решить поставленную задачу. Любой соединённый подзапрос выполняется как независимый подзапрос; он выполняется один раз для всей операции соединения. База данных просто преобразует подзапрос в структуру, подобную таблице:

Существенным отличием и замечательной особенностью LATERAL соединения является изменение модели выполнения: Вместо того чтобы выполнять подзапрос один раз для всех строк, теперь он выполняется один раз для каждой строки, к которой присоединяется подзапрос. Процедура имитирует цикл for-each в SQL, который выполняет итерацию по исходной таблице и выполняет литеральное соединение для каждой строки, при этом строка исходной таблицы является входной. Раньше было невозможно запросить три последних купленных продукта для каждого покупателя, но с помощью литерального соединения, подобного циклу for-each, это стало проще.

При латеральном соединении выполнение подзапроса в соединение меняется с независимого подзапроса на зависимый/производный подзапрос. Выполнение запроса для каждой строки по одному разу может иметь определённые последствия для производительности. Однако эти последствия не являются причиной для отказа от их использования. Без латеральных соединений приложение реализует цикл for-each в программном коде: Подзапрос посылается в базу данных несколько раз, и его нужно многократно разбирать, планировать и выполнять. При использовании латерального соединения база данных может выбрать более оптимальный план выполнения и выполнить этап разбора и планирования только один раз. Таким образом, нельзя сказать, что латеральное соединение — это медленная функция базы данных, которой следует избегать. Напротив, это мощная функция, переносящая цикл for-each приложения в базу данных, которая может выполнять его гораздо эффективнее.

Дополнительные ресурсы

Дополнительные материалы

Предыдущая Статья

Глубокое погружение в отношения Фабрик

Следующая Статья

Vim: Изменение размера сплитов