Урок курса

Отладка 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.

Стратегия: три шага отладки. Когда запрос выполняется, но результат подозрительный, действуй последовательно.

Читай сообщение об ошибке — если оно есть, это первый и самый точный источник информации. Там указан тип и позиция проблемы.

  1. Упрощай по частям. Убери JOIN, WHERE, GROUP BY — оставь только SELECT ... FROM ... и проверь, работает ли базовая часть. Затем добавляй предложения по одному, пока проблема не проявится снова. Так ты изолируешь именно то место, которое ломает логику.

  2. Проверяй ожидаемое число строк. Если знаешь, сколько строк должно вернуться, сравни с фактическим: 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. В чём причина?

Продолжить с проверкой и прогрессом

Откройте интерактивный раннер с заданиями урока.

Перейти к интерактивному уроку