В запросе используется подзапрос в месте, где ожидается одно значение. Определите, что произойдёт при выполнении запроса и почему.
WITH prices(product_id, price) AS (
VALUES (1, 100), (1, 120)
)
SELECT
(SELECT price
FROM prices
WHERE product_id = 1) AS current_price;
Запрос завершится ошибкой нарушения кардинальности: скалярный подзапрос вернул две строки вместо допустимых нуля или одной. В месте выражения SQL ожидает одно значение, поэтому СУБД не может однозначно определить current_price.
Если скалярный подзапрос не возвращает строк, его результатом обычно становится NULL; если возвращает одну строку — её единственное значение.
Вложенные запросы появились как способ выражать зависимые вычисления и условия средствами декларативного SQL, не перенося логику обработки строк в прикладной код. Подзапрос может выступать источником таблицы, проверкой существования или выражением, возвращающим одно значение.
Для последнего случая используется скалярный подзапрос. Его контракт прост: результат должен быть совместим с одним значением в конкретной строке внешнего запроса.
В примере внешний SELECT пытается получить одно значение current_price, но условие подзапроса находит две строки: 100 и 120. Автоматический выбор одной из них был бы неоднозначным и мог бы приводить к разным результатам в зависимости от плана выполнения.
Особенно опасна такая ошибка в запросах UPDATE, INSERT ... SELECT и расчёте отчётов: вместо корректного результата операция может завершиться ошибкой целиком. Если разработчик необдуманно добавит ограничение строк, можно скрыть нарушение бизнес-правила и выбрать неверную запись.
Скалярный подзапрос должен вернуть не более одной строки:
Здесь подзапрос возвращает одну строку, поэтому результатом будет 100. Если заменить product_id = 1 на условие, не находящее строк, результатом выражения станет NULL.
Если по смыслу требуется проверить наличие хотя бы одной строки, нужно использовать EXISTS, а не скалярный подзапрос. EXISTS возвращает логическое значение и не требует выбора конкретной строки.
Если нужно получить одно обобщённое значение, следует явно задать правило агрегации, например MAX(price), MIN(price) или AVG(price). Это устраняет ошибку кардинальности, но меняет смысл: агрегат выбирает результат по определённому правилу, а не «находит текущую запись».
LIMIT 1 технически ограничивает результат, однако без ORDER BY выбранная строка не определяется требованием порядка. Такой вариант допустим только при доказанной неважности конкретной строки или вместе с детерминированной сортировкой.
Сервис рассчитывал цену товара через подзапрос к таблице цен. После повторной загрузки данных в таблице появились две активные цены для одного товара, и массовый UPDATE начал завершаться ошибкой на таких товарах.
Рассматривались три решения. LIMIT 1 было отклонено: без явного приоритета оно могло выбрать устаревшую цену. MAX(price) также не подошёл, поскольку самая высокая цена не означала самую новую или действующую.
Выбранное решение состояло из двух частей: уникальное ограничение на одну активную цену товара и предварительная проверка качества данных. Для отчётного запроса дополнительно использовали ORDER BY valid_from DESC, price_id DESC с ограничением одной строки, поскольку правило выбора последней версии было явно определено.
В результате новые дубликаты блокировались на уровне данных, а существующие ошибки выявлялись отдельно, не маскируясь случайным выбором строки.
Что вернёт скалярный подзапрос, если он не найдёт ни одной строки?
Обычно результатом будет NULL, а не ошибка. Поэтому внешнее выражение может стать NULL, а сравнение с ним — перейти в состояние UNKNOWN из-за трёхзначной логики SQL. Если отсутствие значения недопустимо, это нужно явно обработать через COALESCE, CASE или ограничение целостности.
Чем скалярный подзапрос отличается от EXISTS при наличии нескольких строк?
Скалярный подзапрос должен выбрать одно значение и выдаёт ошибку при нескольких строках. EXISTS проверяет только факт наличия хотя бы одной подходящей строки, поэтому дополнительные совпадения не вызывают ошибки и не дублируют строки внешнего запроса.
Например, WHERE EXISTS (SELECT 1 FROM prices ...) подходит для проверки существования цены, но не для получения самой цены.
Почему LIMIT 1 без ORDER BY не является надёжным устранением ошибки?
SQL не обязан возвращать строки в каком-либо порядке без явного ORDER BY. План, индекс или физическое расположение данных могут измениться, и первой окажется другая строка.
Поэтому для выбора конкретной записи нужно описать критерий приоритета в ORDER BY, а для гарантии единственности бизнес-объекта — закрепить правило уникальным ограничением или иным механизмом целостности данных.