May 28, 2024

Популярные ошибки при написании 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;
Результат фильтрации с оператором 'WHERE'

В данном примере фильтрация выполняется после объединения таблиц, поэтому в результате нет строк, в которых d.product_id≤1.

Здесь:

  1. RIGHT JOIN: объединяет все строки из таблицы «Скидки» и соответствующие строки из таблицы «Товары», где p.product_id = d.product_id.
  2. 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;
Результат фильтрации с 'ON' и 'AND'

Здесь оператор ‘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) или подзапроса.

Вариант с использованием CTE:

WITH CTE AS
(
SELECT product_id,
RANK() OVER (ORDER BY price desc) AS rk
FROM Товары
)
SELECT product_id
FROM
CTE
WHERE rk=2;

В этом исправленном запросе:

  1. CTE создается с помощью WITH. Внутри CTE происходит выбор product_id и вычисление ранга с использованием RANK() OVER (ORDER BY price DESC).
  2. Основной запрос затем использует CTE для фильтрации строк, где rk = 2.

Или:

SELECT product_id
FROM
(
SELECT product_id,
RANK() OVER (ORDER BY price desc) AS rk
FROM Товары;
)
WHERE rk=2;

В данном случае:

  1. Подзапрос сначала вычисляет rk и создает временную таблицу с этим значением.
  2. Затем внешний запрос фильтрует строки на основе значений 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;
Результат только с датами 2025 года

Использование ‘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: основные запросы, группировку таблиц и агрегирующие функции, работу со строками и датами, оконные функции, а также сможете подготовиться к собеседованию. Подробнее о программе обучения: по ссылке.