Популярные ошибки при написании SQL-запросов и методы их исправления
Для этой подборки мы будем использовать гипотетический бизнес-пример. Предположим, вы работаете в команде аналитиков электронной коммерции популярного маркетплейса, и вам нужно выполнить несколько простых запросов. У вас есть две таблицы с названиями «Товары» и «Скидки».
Подсчет NULL-столбцов
Когда вам нужно при подсчете нужно учесть и NULL-столбцы, стоит понимать, как работает функция COUNT(). Допустим, вы хотите подсчитать количество товаров, даже если первичный ключ ‘product_id’ таблицы «Товары» отсутствует.
SELECT COUNT(product_id) FROM Товары;
COUNT(product_id) "3"
Вероятно, вы ожидали увидеть в качестве результата «4», поскольку хотели подсчитать и нулевые значения столбца ‘product_id’. Но COUNT() не учитывает нулевые значения.
Используйте COUNT(*) — в таком случае, нулевые значения будут учтены.
Select Count(*) From Товары;
Count(*) "4"
Использование зарезервированных слов в качестве названий столбцов
SELECT product_id, RANK() OVER (ORDER BY price desc) As Rank FROM Товары;
Данный код не сработает, поскольку название столбца «Rank» является зарезервированным словом для функции RANK().
SELECT product_id, RANK() OVER (ORDER BY price desc) As 'Rank' FROM Товары;
Использование операторов сравнения с NULL
SELECT product_name FROM Товары WHERE product_id=NULL;
Это вызовет исключение, поскольку вы использовали оператор сравнения ‘=‘ с NULL, то же самое произойдет и с оператором сравнения ‘!=‘.
Используя данный код, вы хотите проверить, является ли значение в столбце ‘product_id’ неизвестным, а не равно ли оно чему-то неизвестному.
SELECT product_name FROM Товары WHERE product_id ISNULL;
В данном случае, мы получим ожидаемый результат:
product_name "D"
Фильтрация c помощью оператора ‘ON’ / условного оператора ‘WHERE’
Этот пример не является ошибкой, а лишь демонстрирует отличия фильтрации с помощью оператора 'ON' от фильтрации с помощью 'WHERE'. Вы можете выбрать подходящий вариант в зависимости от требований решаемой задачи.
SELECT d.product_id, price, discount_amount FROM Товары p RIGHT JOIN Скидки d ON p.product_id=d.product_id WHERE p.product_id>1;
В данном примере фильтрация выполняется после объединения таблиц, поэтому в результате нет строк, в которых d.product_id≤1.
- RIGHT JOIN: объединяет все строки из таблицы «Скидки» и соответствующие строки из таблицы «Товары», где p.product_id = d.product_id.
- WHERE: фильтрует результат после объединения, оставляя только строки, где p.product_id > 1.
Далее: используем оператор ‘AND’, фильтрацию выполним непосредственно в ‘ON’ и посмотрим на разницу в результатах.
SELECT d.product_id, price, discount_amount FROM Товары p RIGHT JOIN Скидки d ON p.product_id=d.product_id AND p.product_id>1;
Здесь оператор ‘AND’ применяется до того, как будет выполнено объединение таблиц. Его можно рассматривать как оператор ‘WHERE’, который применяется только к таблице «Товары».
В таком случае, благодаря применению RIGHT JOIN, в результат попадают даже те строки, в которых d.product_id≤1 .
Важно отметить, что различия в фильтрации при использовании ‘ON’ и ‘WHERE’ проявляются только при использовании LEFT, RIGHT или OUTER JOIN. При использовании INNER JOIN, где в результате остаются только совпадающие строки из обеих таблиц, порядок применения условий фильтрации не имеет значения, так как оба подхода дадут один и тот же результат.
Использование столбцов, созданных с помощью оконных функций / ‘CASE WHEN’, с ‘WHERE’ в одном и том же запросе
Следует быть осторожным с тем, чтобы не использовать названия столбцов, созданных с помощью оконных функций / ‘CASE WHEN’, с ‘WHERE’ в одном и том же запросе.
SELECT product_id, RANK() OVER (ORDER BY price desc) AS rk FROM Товары WHERE rk=2;
Этот запрос вызовет исключение, поскольку столбец ‘rk’ был создан с помощью оконной функции и использован совместно с ‘WHERE’ в одном и том же запросе.
А именно, в этом SQL-запросе ошибка заключается в попытке использовать псевдоним rk в предложении WHERE, но псевдонимы, определенные в списке выбора (SELECT), недоступны в WHERE. Это связано с тем, что предложения выполняются в следующем порядке: сначала FROM, затем WHERE, лишь только потом SELECT.
Это можно исправить с помощью CTE (Common Table Expressions) или подзапроса.
WITH CTE AS ( SELECT product_id, RANK() OVER (ORDER BY price desc) AS rk FROM Товары ) SELECT product_id FROM CTE WHERE rk=2;
- CTE создается с помощью WITH. Внутри CTE происходит выбор product_id и вычисление ранга с использованием RANK() OVER (ORDER BY price DESC).
- Основной запрос затем использует CTE для фильтрации строк, где rk = 2.
SELECT product_id FROM ( SELECT product_id, RANK() OVER (ORDER BY price desc) AS rk FROM Товары; ) WHERE rk=2;
- Подзапрос сначала вычисляет rk и создает временную таблицу с этим значением.
- Затем внешний запрос фильтрует строки на основе значений rk из этой временной таблицы.
Таким образом, использование подзапроса позволяет сначала создать временную таблицу с псевдонимом rk, который затем доступен для фильтрации в основном запросе. Это решает проблему, когда нужно фильтровать по вычисляемому значению, которое определяется с помощью функции ранжирования.
Та же идея применима к столбцу, созданному с помощью ‘CASE WHEN’.
Неверное использование BETWEEN
Если не знать о диапазоне, который охватывается оператором ‘BETWEEN’, можно получить неожиданные результаты.
Так, ‘BETWEEN x AND y’ включает как x, так и y в требуемый диапазон.
SELECT * FROM Скидки WHERE offer_valid_till BETWEEN '2025/01/01' AND '2026/01/01' ORDER BY offer_valid_till;
В этом запросе вы, возможно, ожидали получить все даты 2025 года, но в результате получили и 1-й день 2026 года. Это происходит по причине того, что ‘BETWEEN’ включает в себя как ‘2025/01/01’, так и ‘2026/01/01’.
Соответствующая настройка диапазона позволит решить эту проблему.
SELECT * FROM Скидки WHERE offer_valid_till BETWEEN '2025/01/01' AND '2025/12/31' ORDER BY offer_valid_till;
Использование ‘WHERE’ после ‘GROUP BY’
Будьте внимательны к тому, где вы пишете ‘WHERE’, когда используете ‘GROUP BY’.
SELECT category, AVG (price) FROM Товары p INNER JOIN Скидки d ON p.product_id=d.product_id GROUP BY category WHERE discount_amount>10;
Это неверно, потому что ‘WHERE’ использовано после 'GROUP BY'.
‘WHERE’ используется для фильтрации результатов, может показаться, что его надо применить до группировки, а не после. Но ‘WHERE’ сначала отфильтрует данные, а затем ‘GROUP BY’ сгруппирует их в соответствии с используемой агрегатной функцией (в данном случае, AVG).
SELECT category, AVG (price) FROM Товары p INNER JOIN Скидки d ON p.product_id=d.product_id WHERE discount_amount>10 GROUP BY category;
Изучить язык запросов SQL, научиться работать с базами данных и проводить аналитику с помощью SQL и Python можно на очном курсе «SQL для анализа данных» Центра непрерывного образования ФКН НИУ ВШЭ.
Длительность курса — 10 недель, за которые вы освоите основные операторы, функции и запросы на языке SQL, а также научитесь анализировать с его помощью данные клиентов.
Дата старта — 29 мая, но вы еще можете присоединиться. Подробнее о программе обучения: по ссылке.
Для тех, кто предпочитает онлайн-формат: 25 июня стартует курс «SQL для начинающих».
За 2 месяца дистанционного обучения вы освоите язык SQL: основные запросы, группировку таблиц и агрегирующие функции, работу со строками и датами, оконные функции, а также сможете подготовиться к собеседованию. Подробнее о программе обучения: по ссылке.