Каков результат использования скалярного подзапроса, если он возвращает несколько строк?
Скалярный подзапрос обязан вернуть не более одной строки: одну строку с одним значением или не вернуть строк вовсе. Если он возвращает несколько строк, возникает ошибка нарушения кардинальности, потому что SQL не может преобразовать несколько значений в одно скалярное значение.
Если скалярный подзапрос не возвращает строк, его результатом обычно является NULL. Это отличается от случая с несколькими строками: отсутствие строки формирует значение NULL, а избыток строк делает выражение недопустимым.
Реляционные запросы часто используются как выражения внутри других запросов: например, для сравнения значения с результатом отдельного поиска. Чтобы такое вложенное выражение можно было трактовать как одно значение, SQL определяет для него ограничение кардинальности.
Этот подход отделяет два разных случая: отсутствие результата и неоднозначный результат. Первый можно представить как NULL, а второй требует исправления запроса или данных, поскольку выбор одного значения был бы произвольным.
Предположим, запрос получает цену товара по его идентификатору, а затем сравнивает с ней цену заказа. Если идентификатор товара не уникален, внутренний запрос может вернуть несколько цен.
Автоматический выбор первой или любой строки скрыл бы нарушение модели данных. Это может привести к разным результатам в зависимости от плана выполнения или физического порядка строк, поэтому стандартное поведение — сообщить об ошибке.
Скалярный подзапрос должен иметь форму выражения, возвращающего одно значение. Возможны три логических результата:
Пример:
Если для товара с идентификатором 10 найдена одна цена, она попадёт в столбец price. Если цены нет, price будет NULL. Если найдено две или более цены, запрос завершится ошибкой, даже если внешнему запросу фактически нужна только одна строка.
Чтобы выразить намерение выбрать единственное значение, нужно обеспечить уникальность данных или явно задать правило выбора. Ограничение UNIQUE может гарантировать, что для одного идентификатора существует не более одной подходящей строки; агрегатная функция, например MAX, сворачивает несколько строк в одно значение, но одновременно меняет смысл запроса и может скрыть ошибочные дубликаты.
Ограничение количества строк средствами сортировки и ограничения результата также задаёт правило выбора, но это уже не проверка уникальности. Такой вариант допустим, когда бизнес-логика действительно определяет приоритет записи, например выбор самой свежей цены.
В сервисе расчёта счёта цена товара хранилась в таблице истории цен. Разработчик использовал скалярный подзапрос, ожидая получить текущую цену, но не учёл, что у товара может быть несколько активных записей. После добавления второй записи расчёт начал завершаться ошибкой.
Рассматривались три варианта. Удалять дубликаты вручную было ненадёжно: проблема могла повториться. Использовать агрегат MAX было быстро, но скрывало нарушение правила и могло выбрать неверную цену. Ограничить данные уникальностью для одного товара и периода действия было корректнее, поскольку именно это отражало бизнес-инвариант.
Выбрали ограничение целостности и отдельное правило закрытия предыдущей цены перед созданием новой. В результате скалярный подзапрос снова имел гарантированно одну строку, а нарушение модели стало обнаруживаться при записи данных, а не во время расчёта счета.
1. Что возвращает скалярный подзапрос при отсутствии строк?
Он возвращает NULL, а не пустую строку и не ноль. Поэтому последующее сравнение с результатом такого подзапроса может дать UNKNOWN и не пройти фильтрацию. Если вместо NULL требуется конкретное значение, это нужно задавать явно средствами обработки NULL.
2. Чем агрегатный подзапрос отличается от обычного скалярного подзапроса при нескольких строках?
Обычный скалярный подзапрос обнаруживает несколько строк как ошибку. Агрегат без группировки, например подсчёт или максимум, преобразует набор строк в одну строку и потому может быть использован как скалярный результат.
Однако агрегат не доказывает, что исходные строки были уникальны. Например, максимум из двух разных цен вернёт одно значение, хотя наличие двух цен может быть дефектом данных. Выбор агрегата должен соответствовать бизнес-смыслу, а не только устранять ошибку кардинальности.
3. Почему нельзя полагаться на физический порядок строк и брать произвольную строку?
Реляционная модель не задаёт естественного порядка строк. Без явно определённого правила выбора «первой» строки результат не имеет устойчивого смысла и может измениться после изменения плана выполнения, индекса или объёма данных.
Если допустима любая запись, это должно быть осознанным правилом. Если важна конкретная запись, необходимо определить критерий приоритета и обеспечить однозначность выбора; если важна единственность, её следует закрепить ограничением данных.