Исправление ошибки SQL: subquery возвращает больше одного столбца
Быстрый ответ
С целью обеспечения эффективности команды SELECT
в SQL вы должны просить только один столбец в подзапросе, если не используется EXISTS
. Ваша структура запроса должна следовать паттерну (SELECT Column FROM Table)
проконтролировав, что возвращается именно один столбец. Если вам требуется использовать несколько столбцов, то оптимизируйте ваш запрос с помощью JOIN
или создайте индивидуальные условия для каждого из столбцов.
Пример:
SELECT *
FROM MainTable
WHERE id = (SELECT id FROM SubqueryTable WHERE condition); -- Здесь проверяется один столбец
Неверно:
SELECT *
FROM MainTable
WHERE (id, name) = (SELECT id, name FROM SubqueryTable WHERE condition); -- Запрос для нескольких столбцов вызовет ошибку.
Для избежания подобных проблем следует убедиться в том, что запросы предназначены для работы лишь с одним столбцом, или пересмотреть выбранную стратегию.
Решение для подзапросов: Как избежать ошибок
Дубликаты – излишне
Бывают случаи, когда подзапрос возвращает дубликаты, что недопустимо при запросе одного столбца. В таком случае использование DISTINCT
позволяет избавиться от повторений, оставляя только уникальные значения:
WHERE id = (SELECT DISTINCT id FROM SubqueryTable WHERE condition); -- Осуществляется выборка уникального значения 🦄
Сводные данные и группировки
В случаев когда требуется получить сводные данные, в подзапросе следует применить COUNT
и GROUP BY
так, чтобы в результате мы получали только одно значение, с которым сможет работать основной запрос:
SELECT CustomerID, COUNT(*)
FROM Orders
GROUP BY CustomerID
HAVING COUNT(*) = (SELECT TOP 1 COUNT(*) FROM Orders GROUP BY CustomerID ORDER BY COUNT(*) DESC); -- Вот они сводные данные для условий!
Фильтрация по процентилям
Для выборки на основе процентилей следует использовать TOP (0.1) PERCENT
в подзапросе, помните, что результат должен быть представлен только одним числом:
WHERE ProductPrice > (SELECT TOP (0.1) PERCENT AVG(ProductPrice) FROM Products ORDER BY ProductPrice DESC); -- Осуществляется отбор по верхнему процентилю!
Решение для случаев с JSON
При возврате данных в формате JSON следует воспользоваться FOR JSON PATH
, чтобы подзапрос формировал JSON корректно и принцип единого значения не был нарушен:
SELECT JSON_Query((SELECT name, price FROM Products FOR JSON PATH)) AS ProductInfo FROM MainTable; -- Вот Она гармония SQL и JSON 🎶
Визуализация
Представьте себе выбор начинки для пиццы: нужна одна начинка для одной пиццы. Если, однако, было бы определено несколько начинок, то это было бы аналогично запросу с несколькими столбцами без EXISTS
.
Тесто для пиццы: 🍕
Варианты начинок: [🍅, 🧀, 🍄, 🍖]
Пицца с избытком начинки аналогична подзапросу, не поддерживающему работу с несколькими столбцами.
SELECT (SELECT 🍅, 🧀 FROM toppings) FROM pizza -- 🚫 Ошибка! Неверный подход!
Правильный подход: SELECT (SELECT 🍅 FROM toppings) FROM pizza -- ✅ Идеально!
Важная ремарка: В подзапросах лучше меньше, да лучше. 🍕+ 🍅 = Совершенство!
Фильтрация и сортировка: синхронизация
Внутренний порядок при внешнем спокойствии
Даже если сортировка в подзапросах может быть не обязательна, нередко без неё не обойтись. Вы можете использовать ORDER BY
в подзапросе, это не затронет сортировку основного запроса:
SELECT name
FROM Employees
WHERE EXISTS (
SELECT 1
FROM Sales
WHERE Sales.EmployeeID = Employees.ID
ORDER BY Amount DESC
); -- Вот Вам внутренняя сортировка!
EXISTS
: для случаев с несколькими столбцами в подзапросе
Если предстаёт вернуть несколько столбцов в подзапросе, тогда следует применить EXISTS
. Этот оператор позволяет проверять наличие строк, без сравнения значений напрямую:
SELECT *
FROM MainTable m
WHERE EXISTS (
SELECT *
FROM SubqueryTable s
WHERE m.id = s.id
AND m.name = s.name
); -- EXISTS позволяет обойти ограничения на количество столбцов!
Проверяем подзапросы перед применением
Тестируйте подзапрос независимо перед его интеграцией, чтобы убедиться в корректности результата:
SELECT id FROM SubqueryTable WHERE condition; -- Проверка подзапроса перед использованием.
Взаимное соответствие подзапроса и основного запроса
Проверьте, что условия подзапроса соответствуют WHERE
условию основного запроса. Такое соответствие гарантирует, что подзапрос действительно дополняет общую логику:
SELECT Name
FROM Products
WHERE Category = (SELECT DISTINCT Category FROM Categories WHERE SpecialFlag = 1); -- Пример совершенной гармонии подзапроса и основного запроса!
Оптимизация для улучшения производительности
Избегайте лишних операций в подзапросах для повышения производительности. Подзапрос, беспрепятственно работающий без лишнего, гарантирует эффективность основного запроса:
SELECT *
FROM MainTable
WHERE id IN (
SELECT id
FROM SubqueryTable
WHERE condition -- Лишние элементы могут замедлить запрос!
);
С применением этого подхода роль подзапросов как фильтрованных данных становится очевиднее, а ваш SQL-код – чище и точнее.
Полезные материалы
- Создание сценария для определения параметров SQL Server и их значений по умолчанию — руководство по составлению скриптов для параметров SQL Server и их значений по умолчанию.
- Исправляем ошибки в подзапросах SQL, возвращающих несколько значений — практическое руководство по исправлению типичных ошибок в подзапросах SQL.
- Зачем нужно выбирать подзапросы вместо соединений в SQL Server? — обсуждение на Stack Overflow, раскрывающее использование подзапросов и JOIN в SQL.