Основные ошибки SQL и как их исправить
Основные ошибки SQL и как их исправить
Составили список распространённых ошибок в работе с запросами баз данных SQL и лучших практик, о которых забывают некоторые пользователи. Рассказываем, как найти такие ошибки и исправить их.
1. Ошибки синтаксиса
Это самый частый тип ошибок в SQL — как среди новичков, так и среди опытных разработчиков. Сюда относятся опечатки, пропуски букв или символов или лишние знаки в запросах. Найти такую проблему довольно легко — в ответ на запрос база данных выдаст результат: «Такой команды не существует» или SQL укажет, что произошла конкретная ошибка синтаксиса.
Возьмём для примера такой запрос:
SELECT *
FORM dish
WHERE NAME = 'Prawn Salad';
Syntax error in SQL statement "SELECT * FORM[*] dish WHERE NAME = 'Prawn Salad';";
SQL statement: SELECT * FORM dish WHERE NAME = 'Prawn Salad'; [42000-176]
Здесь опечатка в слове FROM, вместо него написано FORM. Избежать таких ошибок помогут онлайн-редакторы SQL, которые подсветят опечатки.
2. Отсутствие работы с пропусками
Частая проблема при работе с большими таблицами в SQL, где легко просмотреть пропуски в данных, что приведёт к ошибке в результатах. Заполнить пропуски лучше с помощью оператора COALESCE: он подставит в пустые места средние или конкретные значения.
Предположим, есть таблица Customers с полями CustomerID («идентификатор клиента»), FirstName («имя») и LastName («фамилия»). Если нужно выбрать имя и фамилию клиента, но одно из полей может быть пустым, мы можем использовать функцию COALESCE.
SELECT COALESCE(FirstName, 'Нет имени'), COALESCE(LastName, 'Нет фамилии')
FROM Customers;
Этот запрос вернёт значения полей FirstName и LastName или строку «Нет имени» или «Нет фамилии», если соответствующее поле пустое.
3. Непонимание конструкций запросов
Сюда относится, например, незнание принципов работы запросов join, когда автор не разбирается в том, какие типы join бывают, какой результат они дают или как использовать их с разными типами колонок. Если в результате запроса join к базе данных SQL результат получился не таким, как ожидалось, скорее всего, произошла ошибка.
Пример от apastron:
SELECT c.id as customer_id, count(o.id) as number_of_orders
FROM Customers c
LEFT JOIN Orders o
ON c.customer_id = o.customer_id
WHERE o.date > ‘2024-01-01’
GROUP BY c.id
Здесь выбран LEFT JOIN, потому что нужно, чтобы в выборку попали в том числе и клиенты, не совершившие ни одного заказа за период. Но из-за применения фильтра по дате после JOIN такие потребители теряются.
Если нужно добавить пользователей с нулём заказов, то корректный запрос может выглядеть так:
WITH order_agg AS (
SELECT
o.customer_id as customer_id,
count(o.id) AS order_count
FROM Orders o
WHERE o.date > ‘2024-01-01’
GROUP BY o.customer_id
)
SELECT
c.id AS customer_id,
COALESCE(o.order_count, 0) AS order_count
FROM Customers c
LEFT JOIN order_agg o
ON c.customer_id = o.customer_id
Разобраться, как использовать SQL и не допускать ошибок, можно на курсе «SQL для работы с данными и аналитики». На занятиях изучают PostgreSQL, ClickHouse и другие инструменты, учатся находить и фильтровать данные при помощи SQL-запросов, а также рассчитывать продуктовые метрики.
4. Пренебрежение преобразованием типов данных
В разных базах данных SQL один и тот же запрос может давать разные результаты. Причина в различных способах обработки типов данных. Возьмём для примера такой запрос:
SELECT a/b FROM demo
# столбец целых чисел
SELECT 1 / 2
# 0
В PostgreSQL результатом будет целое число, а в MySQL — дробное. В зависимости от того, какой результат нужен пользователю, любое из этих значений может быть ошибкой. Поэтому важно изучить особенности разных систем управления базами данных (СУБД) и при переходе в другую базу перепроверять, как работает преобразование типов данных.
При необходимости можно заранее преобразовать данные в нужный тип. Например, так:
SELECT CAST(123 AS DECIMAL(10, 2)) AS Result;
5. Сравнение nullable и not null столбцов
В SQL nullable поле может не иметь никакого значения, а not null всегда должно иметь какое-либо значение. Довольно частый кейс, когда сравнивают две nullable колонки, но это ошибка, потому что любое сравнение с NULL выдаст False — то есть операция неравенства не является операцией, обратной равенству.
Важно помнить, что:
● NULL = NULL -> False (два значения NULL не могут быть равны);
● NULL != NULL -> False (два значения NULL не могут быть неравны).
Проверку на NULL лучше всегда прописывать явно через IS (NOT) NULL.
6. Пренебрежение индексами
Индекс — это структура, которая помогает ускорить обработку информации в SQL. Он содержит значения столбцов таблицы и ссылки на строки, где эти значения встречаются. Когда пользователь пишет запрос, СУБД может использовать индекс для быстрого поиска нужных строк без необходимости просматривать всю таблицу. Это особенно полезно при работе с большими объёмами данных.
Если СУБД слишком долго обрабатывает запрос, есть вероятность, что произошла ошибка и пользователь забыл о создании индекса. Лучше его добавить, чтобы запросы обрабатывались быстрее.
Предположим, есть таблица Customers с полями CustomerID («идентификатор клиента»), FirstName («имя») и LastName («фамилия»). Необходимо выбрать всех клиентов из Санкт-Петербурга. Обычный SQL-запрос будет таким:
SELECT * FROM Customers WHERE City = Санкт-Петербург;
Без индекса СУБД будет сканировать всю таблицу, что может занять много времени, особенно если она большая. Чтобы ускорить процесс, нужно создать индекс по полю City:
CREATE INDEX idx_city ON Customers(City);
В этом случае при выполнении запроса СУБД сможет использовать индекс для быстрого поиска всех строк, где значение City — «Санкт-Петербург». Это значительно ускорит выполнение запроса.
7. Незнание порядка выполнения запроса
С такой ошибкой часто сталкиваются новички, когда пишут запросы, основываясь на человеческой логике, а не на логике SQL. Казалось бы, выполнение команд и фильтров в запросе должно идти в том порядке, в каком их указал пользователь, но у систем управления базами данных своя очерёдность.
Допустим, нужно выделить в таблице строки, где разница между старой и новой ценой товара больше 100. Пользователь может написать так:
SELECT old_price - new_price AS diff
FROM goods
WHERE diff > 100
ERROR: column "diff" does not exist
В результате СУБД выдаёт error, потому что в SQL сначала выполняется запрос WHERE и только потом — SELECT. Пользователь не написал условие о разнице больше сотни в WHERE — это ошибка.
SELECT old_price - new_price AS diff
FROM goods
WHERE old_price - new_price > 100
8. Неправильная работа с транзакциями
Транзакция — это набор операций, выполняемых как единое целое. Они используются для обеспечения целостности данных. Проблемы тут бывают различные — допустим, аналитик открыл и не закрыл транзакцию или неверно подобрал уровень её изоляции. Из-за таких ошибок запросы блокируют базу данных SQL и замедляют её работу.
Например, люди, перешедшие с SQL Server на PostgreSQL и привыкшие для снижения нагрузки на базу ставить уровень изоляции READ UNCOMMITTED, могут быть удивлены появлению SHARED блокировок на чтение при выполнении SELECT-запросов.
BEGIN TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT * FROM some_table
COMMIT;
Дело в том, что уровень READ UNCOMMITTED в PostgreSQL не существует. Его можно выставить, но поведение базы данных будет таким же, как при READ COMMITTED.
9. Отсутствие условий запроса
Ошибка, которая является страшным сном любого разработчика SQL, — неверные условия или их отсутствие при выполнении запроса delete. Например, если пользователь забыл добавить WHERE при написании DELETE, вместо того чтобы удалить несколько строк, он сотрёт данные целиком.
Некорректный запрос:
DELETE FROM products
В этом случае из базы удаляются все элементы таблицы, а пользователь всего лишь скопировал запрос не полностью.
Корректный запрос:
DELETE FROM products
WHERE category_id = 50
AND product_name = 'Pear';
Теперь элементы удалены в конкретной категории.
10. Пренебрежение оконными функциями
В SQL это функции, которые выполняют вычисления для набора строк внутри окна. Они позволяют выполнять вычисления сумм, средних, максимальных или минимальных значений над набором строк без необходимости использования подзапросов. Неиспользование этого инструмента — типичная ошибка новичка.
Возьмём для примера ситуацию, когда нужно вывести топ клиентов компании по выручке в разных странах. Запрос без использования оконных функций может быть таким:
SELECT c1.country, c1.customer_id, c1.revenue
FROM customers c1
JOIN (
SELECT country, MAX(revenue) as max_revenue
FROM customers
GROUP BY country
) c2
ON c1.country = c2.country AND c1.revenue = c2.max_revenue;
Здесь сначала с помощью подзапроса вычисляется максимум для каждой страны, а потом фильтруется с помощью JOIN. В зависимости от типа данных в колонке revenue такой трюк может либо сработать, либо нет. Если используется формат с плавающей точкой, то при прямом сравнении есть риск потерять данные из-за потери точности.
В такой ситуации лучше использовать запрос с оконными функциями SQL:
SELECT country, customer_id, revenue
FROM (
SELECT
country,
customer_id,
revenue,
RANK() OVER (PARTITION BY country ORDER BY revenue DESC) as rank
FROM customers
) ranked_customers
WHERE rank = 1;
Здесь есть функция RANK() для ранжирования клиентов в каждой стране. Предложение OVER с параметром PARTITION BY группирует строки по странам, а функция RANK() позволяет упорядочить клиентов по выручке. В результате запроса будет получена таблица с тремя столбцами: CustomerID, Country и NumberOfCustomers.
Совет эксперта
Читать также: