Урок курса
Отладка SQL: синтаксические, логические ошибки и ошибки с NULL
SQL с нуля: бесплатный курс с практикойВ предыдущем уроке мы разбирали тонкости LAG и LEAD — и один из уроков по оконным функциям наглядно показал, как легко написать синтаксически правильный запрос, который возвращает бессмысленный результат. Сейчас займёмся другим классом проблем: ошибками, которые либо прерывают выполнение запроса с сообщением, либо молча дают неверный ответ. Научимся их читать, локализовать и чинить.
Синтаксические ошибки: пропущенная запятая и неверное ключевое слово
Синтаксическая ошибка означает одно: СУБД не смогла разобрать текст запроса как грамматически корректный SQL. Выполнения не происходит — движок останавливается на стадии разбора.
Рассмотрим два типичных случая.
Пропущенная запятая между выражениями. Чтобы соседнее имя нельзя было принять за допустимый псевдоним, возьмём квалифицированные столбцы:
SELECT o.customer_id o.total_amount
FROM orders AS o;
SQLite остановится около точки во втором имени: после o.customer_id он ожидал запятую или FROM, а конструкцию o.total_amount уже не может разобрать как псевдоним. Исправление:
SELECT o.customer_id, o.total_amount
FROM orders AS o;
Важно: запрос SELECT customer_id name FROM orders; ошибку не вызовет — SQL прочитает name как неявный псевдоним. Поэтому при отладке проверяйте, не превратился ли пропуск запятой в формально допустимый алиас.
Опечатка в ключевом слове. Например:
SELECT customer_id, total_amount
FORM orders;
Сообщение может указывать на orders, хотя ошибочно слово перед ним: написано FORM вместо FROM. Парсер сообщает место, где разбор окончательно стал невозможен, а не обязательно символ, где человек впервые ошибся.
То же правило работает при неверном порядке предложений:
SELECT customer_id
WHERE total_amount > 1000
FROM orders;
Порядок фиксирован: SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY. Если сообщение указывает на правильный с виду токен, посмотрите на одно-два слова левее.
Ошибки столбцов при JOIN: неоднозначное имя и несуществующий столбец
Когда запрос соединяет несколько таблиц, СУБД сталкивается с двумя разными проблемами именования столбцов.
Несуществующий столбец (no such column в SQLite, column does not exist в PostgreSQL) означает, что среди доступных источников такого имени нет. Причиной бывает опечатка или старое имя после изменения схемы. Сначала проверьте схему и точное написание. SQLite разрешает некоторые обращения к SELECT-псевдонимам в WHERE как собственное расширение, поэтому это не надёжный пример ошибки отсутствующего столбца и не переносимое правило SQL.
Неоднозначный столбец (ambiguous column name) существует сразу в нескольких соединённых таблицах. В учебной схеме order_id есть и в orders, и в order_items:
-- Сломанный запрос
SELECT order_id, oi.quantity
FROM orders AS o
JOIN order_items AS oi ON o.order_id = oi.order_id;
SQLite не знает, нужен o.order_id или oi.order_id. Исправление — квалифицировать имя:
SELECT o.order_id, oi.quantity
FROM orders AS o
JOIN order_items AS oi ON o.order_id = oi.order_id;
Префикс нужен везде, где имя неоднозначно: в SELECT, WHERE, ON, GROUP BY или ORDER BY. Единый стиль псевдоним.столбец делает длинные запросы устойчивее: после добавления новой таблицы ранее уникальное имя может стать неоднозначным.
Сообщения требуют разных действий. no such column говорит: «такого имени среди источников нет». ambiguous column name говорит: «имя найдено в нескольких источниках, укажите нужный».
NULL в условиях и агрегатах, стратегия отладки логических ошибок
Логическая ошибка с NULL коварна именно потому, что СУБД молчит: запрос выполняется без сообщений, но возвращает пустой результат или неправильное число.
Сравнение с NULL. Выражение column = NULL в SQL всегда даёт NULL — не TRUE и не FALSE. Это следствие трёхзначной логики: NULL означает «неизвестно», и результат сравнения неизвестного с чем угодно тоже неизвестен. WHERE отбирает строки только при TRUE, поэтому WHERE manager_id = NULL не отберёт ни одной строки — даже если в таблице полно строк с manager_id IS NULL. Правильная запись:
-- Неверно: всегда пусто
SELECT * FROM employees WHERE manager_id = NULL;
-- Верно
SELECT * FROM employees WHERE manager_id IS NULL;
Аналогично работает IS NOT NULL — для исключения строк с отсутствующим значением.
NULL в агрегатах. COUNT(*) считает все строки без исключения. COUNT(column) считает только строки, где column не NULL — строки с NULL в этом столбце пропускаются. Это приводит к неожиданным расхождениям:
SELECT
COUNT(*) AS total_rows,
COUNT(manager_id) AS rows_with_manager
FROM employees;
Если три сотрудника не имеют менеджера, total_rows будет больше rows_with_manager на три. SUM и AVG тоже игнорируют NULL: если все значения в столбце NULL, оба вернут NULL, а не 0.
Стратегия: три шага отладки. Когда запрос выполняется, но результат подозрительный, действуй последовательно.
Читай сообщение об ошибке — если оно есть, это первый и самый точный источник информации. Там указан тип и позиция проблемы.
-
Упрощай по частям. Убери JOIN, WHERE, GROUP BY — оставь только
SELECT ... FROM ...и проверь, работает ли базовая часть. Затем добавляй предложения по одному, пока проблема не проявится снова. Так ты изолируешь именно то место, которое ломает логику. -
Проверяй ожидаемое число строк. Если знаешь, сколько строк должно вернуться, сравни с фактическим:
SELECT COUNT(*) FROM ...с тем же WHERE. Пустой результат сам по себе не доказывает ошибку: фильтр, диапазон или JOIN могут законно не найти строк. Сначала проверьте ожидаемый результат на известных данных, затем по отдельности WHERE и условия JOIN. Если условие сравнивает nullable-столбец с NULL, отдельно ищите= NULLи заменяйте его наIS NULLилиIS NOT NULL.
Эти три шага работают в связке: синтаксические ошибки закрываются первым шагом, ошибки неоднозначности столбцов — тоже первым, а логические ошибки с NULL требуют второго и третьего, потому что СУБД на них не жалуется.
Попробуйте решить
Аналитик выполнил запрос:
SELECT * FROM orders WHERE manager_id = NULL;
Результат — 0 строк, хотя в таблице точно есть строки с manager_id равным NULL. В чём причина?
Продолжить с проверкой и прогрессом
Откройте интерактивный раннер с заданиями урока.
