SQL — мощная штука, но даже простые запросы могут выдать не верный результат, если не знать подводных камней. В этой статье — список типовых ошибок, на которые чаще всего наступают новички. Всё объясняю на живых примерах с PostgreSQL.

Какие бывают ошибки в SQL

Ошибки в SQL можно условно разделить на несколько категорий:

  1. Синтаксические ошибки. Это ошибки в написании SQL-кода: пропущенные запятые, неверные ключевые слова, неправильный порядок конструкции. Они чаще всего ловятся самим движком базы при попытке выполнить запрос.

  2. Логические ошибки. Самые коварные. Код выполняется, но результат не тот. Например, неверный фильтр, JOIN по неправильному полю, перепутанный порядок WHERE и HAVING или лишний DISTINCT. Эти ошибки особенно опасны в аналитике, потому что могут привести к неверным бизнес-решениям.

  3. Ошибки работы с NULL. NULL — это отдельная категория значений в SQL, и она требует особого внимания. Сравнение через = и != с NULL не работает так, как многие ожидают. Здесь нужны IS NULL и IS NOT NULL.

  4. Ошибки при работе с JOIN. Отсутствие условия соединения, неправильный тип соединения (INNER вместо LEFT, или наоборот), дублирование строк из-за некорректного связывания — всё это может нарушить итоговую выборку.

  5. Ошибки производительности. Использование SELECT * в больших таблицах, отсутствие индексов на полях фильтрации, тяжёлые подзапросы и вложенные SELECT’ы там, где можно обойтись CTE или JOIN — всё это тормозит выполнение и грузит сервер.

  6. Ошибки доступа. Запрос к несуществующей таблице, попытка обращения к колонке с опечаткой, отсутствие прав на SELECT/INSERT — это технические ошибки, но тоже распространённые. Часто возникают при смене окружения (dev → prod, другой пользователь и т.д.).

Перейдем к примерам распространенных ошибок.

Синтаксическая ошибка с некорректным GROUP BY

Ошибка возникает, если указать в SELECT столбцы, которые не попадают ни в агрегатную функцию, ни в GROUP BY.

Ошибочный запрос, который выдаст ошибку “ERROR: column “sales.product” должен присутствовать в предложении GROUP BY или использоваться в агрегатной функции:

SELECT customer_id, product, amount
FROM sales
GROUP BY customer_id;

Исправленный запрос:

SELECT customer_id, SUM(amount) AS total_amount
FROM sales
GROUP BY customer_id;

Логическая ошибка при использовании DISTINCT с агрегатной функцией без GROUP BY

Комбинация DISTINCT и агрегатных функций, таких как SUM, AVG, COUNT, без явного указания GROUP BY, вводит SQL в замешательство. Запрос неясен: нужно ли агрегировать по customer_id, или просто выбрать уникальные строки? SQL требует однозначности — все неагрегированные поля в SELECT должны быть указаны в GROUP BY. Иначе возникает ошибка выполнения.

Ошибочный запрос:

SELECT DISTINCT customer_id, SUM(amount)
FROM sales;

Результатом будет:

ERROR: column “sales.customer_id” must appear in the GROUP BY clause or be used in an aggregate function

Исправленный запрос:

SELECT customer_id, SUM(amount) AS total_amount
FROM sales
GROUP BY customer_id;

Ошибка при использовании JOIN с несовместимыми типами данных

При соединении таблиц через поля с разными типами данных (INTEGER, TEXT, UUID, и т. д.) база данных может не только вернуть некорректные результаты, но и вовсе не выполнить соединение. Особенно это критично, если соединение происходит по полям с разной длиной или форматом — ошибки при этом могут быть скрытыми и долго не обнаруживаться.

Ошибочный запрос:

SELECT o.order_id, c.customer_name
FROM orders o
JOIN customers c ON o.order_id = c.customer_id;

Запрос будет ошибочным, если order_id и customer_id имеют разные типы данных.

Исправленный запрос:

SELECT o.order_id, c.customer_name
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id;

Удаление данных из таблицы без WHERE

Это классика жанра: забыть WHERE в DELETE — всё равно что взять и нажать «Удалить всё», и подтвердить. Вместо удаления пары строк исчезает вся таблица. Особенно больно, если это прод и нет бэкапа. Один неосторожный DELETE, и ваша база превращается в чистый лист.

Ошибочный запрос, который удалит все строки из таблицы:

DELETE FROM products;

Исправленный запрос:

DELETE FROM products
WHERE category_id = 50 AND product_name = 'Pear';

Ошибка работы с NULL при сравнении через =

NULL в SQL — это не просто «пусто», а «неизвестно». А с неизвестным нельзя сравнивать напрямую. Условие amount = NULL никогда не даст TRUE, потому что результат сравнения с NULL — всегда NULL, то есть «неизвестно». Поэтому такой запрос не вернёт ни одной строки, даже если NULL в колонке есть. Для проверки нужно использовать IS NULL и IS NOT NULL.

Ошибочный запрос, который не выдаст ни одной строки:

SELECT * 
FROM sales 
WHERE amount = NULL;

Исправленный запрос:

SELECT * 
FROM sales 
WHERE amount IS NULL;

Ошибка с JOIN без условий соединения

Забыть ON в JOIN — всё равно что сказать базе: «Соедини всё со всем, как хочешь». В первом случае вы получите синтаксическую ошибку, а во втором — декартово произведение: каждая строка из первой таблицы будет соединена с каждой строкой из второй. Это быстро превращает обычный запрос в лавину данных и боль для сервера (и аналитика).

Ошибочный запрос с синтаксической ошибкой:

SELECT *
FROM sales
JOIN customers;

Ошибочный запрос с декартовым произведением:

SELECT *
FROM sales
JOIN customers ON 1=1;

Исправленный запрос:

SELECT s.*, c.name
FROM sales s
JOIN customers c ON s.customer_id = c.id;

Неправильный порядок условий в WHERE

Новички часто забывают про приоритет логических операторов AND и OR, из-за чего запрос работает не так, как задумывалось.

Ошибочный запрос:

SELECT * FROM orders
WHERE status = 'completed' OR status = 'paid' AND delivery_date IS NOT NULL;

Этот запрос вернёт ВСЕ completed и только те paid, у которых указана дата доставки. Не то, что хотели.

Исправленный запрос:

SELECT * FROM orders
WHERE (status = 'completed' OR status = 'paid')
AND delivery_date IS NOT NULL;

Скобки меняют порядок выполнения и делают логику запроса правильной и понятной.

Неоправданное использование подзапросов

Подзапросы внутри SELECT могут выглядеть удобно, но часто создают лишнюю нагрузку. Каждый подзапрос выполняется отдельно для каждой строки — а это значит больше вычислений, больше времени и меньше масштабируемости. Там, где можно использовать JOIN, лучше так и сделать: это быстрее и понятнее.

Ошибочный запрос:

SELECT 
  order_id, 
  (SELECT customer_name FROM customers WHERE customer_id = orders.customer_id)
FROM orders;

Исправленный запрос:

SELECT o.order_id, c.customer_name
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id;

Ошибка производительности при выборе всех строк с SELECT *

Можно воспринимать эту ошибку как «принеси мне всё из холодильника, хотя я хотел только яблоко». Такой запрос тянет все колонки, включая те, которые не нужны. Это замедляет выполнение, особенно при работе с большими таблицами, и мешает оптимизатору строить эффективный план.

Ошибочный запрос:

SELECT * FROM customers;

Исправленный запрос:

SELECT id, name FROM customers;

Отсутствие WHERE и LIMIT при больших данных в таблице

Без WHERE и LIMIT вы загружаете всю таблицу — даже если вам нужно 5 строк. Это всё равно что выгружать весь архив почты за 10 лет, чтобы найти одно письмо. Такой запрос сильно нагружает базу, тормозит интерфейс и может привести к таймаутам или сбоям.

Ошибочный запрос:

SELECT * FROM large_table

Исправленный запрос:

SELECT * FROM large_table
WHERE created_at > NOW() - INTERVAL '7 days'
LIMIT 1000;

Неэффективное использование OR

Запросы с множеством OR выглядят безобидно, но могут мешать оптимизатору построить эффективный план выполнения. Особенно в больших таблицах это замедляет работу. Использование IN делает запрос компактнее, читаемее и зачастую быстрее.

Ошибочный запрос:

SELECT * 
FROM orders 
WHERE customer_id = 1 OR customer_id = 2;

Исправленный запрос:

SELECT * 
FROM orders 
WHERE customer_id IN (1, 2);

Заключение

Большинство ошибок новичков — это результат непонимания порядка выполнения SQL-запроса. Разбирайте, в какой момент работает WHERE, когда применяется GROUP BY, и когда можно использовать алиасы. Лучше медленно, но правильно, чем быстро и с багами. SQL прощает мало, но учит быстро — особенно если смотреть на результаты собственных запросов внимательно.

Не бойтесь экспериментировать, но всегда проверяйте себя: читайте, что именно возвращает ваш запрос, и задавайте себе вопрос: «А это точно то, что я хотел(а) получить?» Чем раньше вы привыкнете анализировать не только код, но и его поведение, тем быстрее исчезнет ощущение, что SQL — это какая-то магия. На самом деле, это просто строгое, но честное ремесло.

Еще больше полезных материалов в моем TG-канале. Подписывайтесь и читайте контент по ссылке.

Комментарии (11)


  1. kompilainenn2
    15.06.2025 08:29

    SELECT customer_id, product, amount FROM sales GROUP BY customer_id;

    а тут точно ошибка?


    1. pda0
      15.06.2025 08:29

      Да. То что некоторые базы данных разрешают такое - нарушение стандарта.


  1. Isiirk
    15.06.2025 08:29

    А аналитикам не доступно базовое понимание построения SQL запросов? Или там теперь без знаний вообще берут?


    1. Akina
      15.06.2025 08:29

      Недоступно, увы. Тут имеется не одна и не две статьи класса "SQL для аналитика", написанные аналитиками - все, как под копирку...

      По-моему, аналитики в SQL напоминают тех самых слепцов, которые на ощупь пытались понять, что есть слон.


    1. miksoft
      15.06.2025 08:29

      Не то чтобы берут, но пытаются придти. Наверное, три четверти собеседований с кандидатами заканчивается словами "не смог в SQL".


  1. Roman2dot0
    15.06.2025 08:29

    Записки джуна составленные из ответов гпт?


  1. miksoft
    15.06.2025 08:29

    Среди логических ошибок забыли еще один подкласс - недетерминированный результат. Это когда от запуска к запуску результат может различаться, хотя ни данные, ни запрос не изменились.

    Возникает обычно из-за отсутствия явно указанной сортировки или при указании неполного набора полей для сортировки.


  1. miksoft
    15.06.2025 08:29

    SELECT *FROM salesJOIN customers;

    Не во всех СУБД это ошибочный запрос, если реально нужно декартово произведение.


    1. asmm
      15.06.2025 08:29

      а кое где это будет соединение по ключу (если он есть)


      1. Akina
        15.06.2025 08:29

        Ну и то, и другое опять-таки расширение синтаксиса в конкретной СУБД, отклонение от стандарта. JOIN - это сокращённая форма INNER JOIN, и наличие ON clause по стандарту обязательно. Автоподстановка ON TRUE для получение декартова произведения или ON t1.pk1 = t2.pk2 для связывания по первичному ключу - это фичи конкретных диалектов.


  1. Akina
    15.06.2025 08:29

    На самом деле типов ошибок всего три.

    Алгоритмическая - неправильно выбран алгоритм получения конечного результата. Проблема, например, неправильно выбранного типа связывания - обычно именно отсюда. Экзотика, вроде неправильно выбранных исходных данных (и такое бывает) - тоже отсюда.

    Логическая - неправильно реализован правильно выбранный алгоритм. например, проблема неправильно выбранного типа связывания - может быть и отсюда.

    Синтаксическая - сформирован некорректный SQL-текст. Начиная от опечаток и кончая просто незнанием правильного синтаксиса в конкретном диалекте SQL.

    Проблемы с правами доступа и производительностью вообще не являются ошибками SQL.

    А "ошибки работы с NULL", использование некорректного общего синтаксиса и прочие проблемы того же рода - это просто незнание основ, ну или другими словами безграмотность в соответствующей области знаний.