Всегда ли коррелированный подзапрос физически выполняется заново для каждой строки внешнего запроса?
Нет. Логически результат коррелированного подзапроса определяется отдельно для каждой строки внешнего запроса, но физически оптимизатор может преобразовать его в соединение, агрегацию, полусоединение или другой эквивалентный план. Поэтому корреляция описывает зависимость выражения, а не обязательный алгоритм выполнения.
Коррелированные подзапросы появились как удобный декларативный способ выразить зависимость внутреннего запроса от текущей строки внешнего запроса. Они позволяют записывать условия существования, построчные вычисления и сравнение с агрегатом без явного описания промежуточных соединений.
Наивная реализация действительно могла бы запускать внутренний запрос для каждой внешней строки. Однако такой подход плохо масштабируется, поэтому оптимизаторы баз данных научились устранять корреляцию и преобразовывать запрос в более эффективные реляционные операции.
Предположим, для каждого сотрудника нужно получить дату его последнего заказа. Формулировка через коррелированный подзапрос понятна, но при большом числе сотрудников возникает риск повторного обращения к таблице заказов.
Если принять логическую модель за физический план, можно ошибочно объявить любой такой запрос медленным. Обратная ошибка тоже опасна: наличие оптимизатора не гарантирует устранение корреляции, особенно при сложных выражениях, ограничениях семантики или использовании функций с наблюдаемыми побочными эффектами.
Коррелированный подзапрос обращается к столбцу внешней строки. В логическом смысле для каждой строки внешнего набора определяется собственный результат внутреннего выражения:
Оптимизатор может преобразовать такую форму в предварительную агрегацию заказов по employee_id, а затем присоединить результат к сотрудникам. Семантика сохраняется: для каждого сотрудника используется максимум только по его заказам, а отсутствие заказов обычно представляется значением NULL.
Другой вариант — индексный план с поиском по employee_id для каждой строки внешней таблицы. Он может быть выгоднее полной агрегации, если внешних строк мало или индекс эффективно поддерживает поиск последней даты.
Возможны также кэширование результатов для одинаковых значений коррелирующего выражения и преобразование условий EXISTS в полусоединение. Конкретный план зависит от статистики, индексов, оценки кардинальности, стоимости сортировки и особенностей СУБД.
Преобразование допустимо только при сохранении семантики. Оптимизатор должен учитывать NULL, количество строк, особенности агрегатов и свойства функций. Например, нельзя безоговорочно считать произвольную недетерминированную функцию обычным чистым вычислением, которое можно свободно переместить или выполнить другое число раз.
Практический вывод: анализировать нужно не текст запроса, а фактический план выполнения. Коррелированный синтаксис сам по себе не доказывает построчный запуск, но и не гарантирует автоматическую оптимизацию.
В отчёте по 20 миллионам сотрудников использовался коррелированный подзапрос для поиска последней операции. Сначала рассматривали два варианта: оставить подзапрос и рассчитывать на оптимизатор либо явно агрегировать операции по сотруднику и присоединить агрегат.
Первый вариант был проще, но его план зависел от статистики и при некоторых параметрах выполнял множество индексных поисков. Явная агрегация давала предсказуемый план полного прохода по операциям, однако обрабатывала больше данных, чем требовалось для небольшого списка сотрудников.
Выбрали две формы на разных сценариях: индексный вариант для выборки небольшого числа сотрудников и предварительную агрегацию для массового отчёта. Решение проверяли по плану и фактическому времени выполнения, а не по наличию коррелированного подзапроса в тексте.
Нет, это описание логической зависимости, а не обязательного физического алгоритма. Оптимизатор может заменить повторные вычисления соединением или агрегацией, если преобразование сохраняет результат. Поэтому число фактических обращений к внутренней таблице определяется планом.
Нет. Нужно сохранить кардинальность и обработку NULL. Например, обычное соединение может размножить внешнюю строку при нескольких совпадениях, тогда как скалярный подзапрос должен вернуть не более одного значения, а EXISTS вообще не размножает строки внешнего результата. Поэтому для разных типов подзапросов нужны разные преобразования: агрегат, полусоединение или соединение с дополнительным контролем уникальности.
Главныe факторы — количество внешних строк, число подходящих внутренних строк, наличие подходящего индекса и точность статистики. Индексный поиск может быть выгоден при малой выборке, а предварительная агрегация — при массовой обработке и большом числе повторных обращений. Надёжный вывод делают по фактическому плану, оценкам кардинальности и измерениям на репрезентативных данных.