NULL в агрегатах и объединениях
Узнайте, как NULL ведёт себя в COUNT, SUM и JOIN
«NULL в агрегатах и объединениях» — бесплатный урок SQL Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения SQL Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс SQL Academy содержит 4 уроков всего.
NULL меняет арифметику
Агрегатные функции и соединения по-особому работают со значением NULL. Если не знать этих правил, итоговые суммы и количества могут незаметно оказаться неверными.
В этом уроке показано, как COUNT, SUM, AVG, GROUP BY и внешние соединения взаимодействуют с отсутствующими значениями.
SELECT amount FROM payments;
-- amount
-- -------
-- 100
-- NULL <- missing
-- 200Агрегатные функции игнорируют NULL
Большинство агрегатных функций — SUM, AVG, MIN, MAX — просто пропускают значения NULL. Они обрабатывают только строки, содержащие данные.
Поэтому NULL в сумме не мешает работе SUM: такое значение просто не включается в итог.
-- Using amounts 100, NULL, 200
SELECT
SUM(amount) AS total, -- 300 (NULL skipped)
MIN(amount) AS lo, -- 100
MAX(amount) AS hi -- 200
FROM payments;AVG тоже пропускает NULL
AVG делит сумму значений, не равных NULL на количество значений, не равных NULL. NULL исключается из обеих частей вычисления.
Это важно: среднее для набора {100, NULL, 200} равно 150, а не 100 — NULL не считается нулём.
-- (100 + 200) / 2 = 150, the NULL row is ignored
SELECT AVG(amount) AS avg_amount FROM payments;
-- If you WANT NULLs counted as 0, COALESCE first:
SELECT AVG(COALESCE(amount, 0)) AS avg_with_zeros FROM payments; -- 100COUNT(*) и COUNT(column)
Это различие часто приводит к ошибкам:
COUNT(*)считает строки, включая строки со значениями NULL.COUNT(column)считает только строки, в которых этот столбец не равен NULL.
-- 3 rows total, but only 2 have a non-NULL amount
SELECT
COUNT(*) AS row_count, -- 3
COUNT(amount) AS has_amount -- 2
FROM payments;COUNT(DISTINCT) и NULL
COUNT(DISTINCT col) считает количество различных значений, не равных NULL. NULL полностью исключаются — они никогда не увеличивают количество различных значений.
Помните об этом, когда подсчитываете, «сколько существует уникальных X».
-- statuses: 'paid', NULL, 'paid', 'void'
SELECT COUNT(DISTINCT status) AS distinct_statuses
FROM payments;
-- 2 (paid, void) -- NULL not countedАгрегаты для пустого набора
Когда агрегатная функция работает с нулём строк, результат зависит от функции:
COUNT(...)возвращает0.SUM,AVG,MIN,MAXвозвращаютNULL.
Используйте COALESCE, чтобы при необходимости превратить NULL в сумме в 0.
-- No rows match -> SUM is NULL, not 0
SELECT COALESCE(SUM(amount), 0) AS total
FROM payments
WHERE status = 'refunded'; -- no such rowsGROUP BY объединяет NULL в одну группу
Хотя в других случаях NULL = NULL даёт неизвестный результат, GROUP BY помещает все NULL в одну группу.
Таким образом, категория NULL становится отдельной группой в результатах, и строки с отсутствующими данными можно суммировать вместе.
SELECT category, COUNT(*) AS n
FROM products
GROUP BY category;
-- category | n
-- ---------+---
-- books | 5
-- toys | 3
-- NULL | 2 <- all NULL categories in one groupNULL во внешних соединениях
Внешние соединения — один из главных источников NULL. LEFT JOIN сохраняет каждую строку из левой таблицы; если справа нет совпадения, столбцы правой таблицы становятся равны NULL.
Эти NULL означают «нет совпадающей строки», а не «сохранённое значение NULL».
SELECT c.name, o.id AS order_id
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id;
-- name | order_id
-- ------+---------
-- Alice | 10
-- Bob | NULL <- Bob has no ordersПодсчёт совпадений после LEFT JOIN
Чтобы после LEFT JOIN посчитать только реальные совпадения, подсчитывайте столбец правой таблицы, в котором нет NULL, а не COUNT(*).
COUNT(o.id) игнорирует строки со значением NULL, созданные для несовпавших строк левой таблицы, и возвращает настоящее количество заказов.
SELECT c.name, COUNT(o.id) AS order_count
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
GROUP BY c.name;
-- Bob shows 0, not 1×NULLФильтрация несовпадений
Важный нюанс: условие для правой таблицы в WHERE, добавленное после LEFT JOIN, превращает его во внутреннее соединение, поскольку NULL = value даёт неизвестный результат и такая строка отфильтровывается.
Если нужно сохранить несовпавшие строки, поместите условие в предложение ON или явно проверьте наличие NULL.
-- Accidentally drops Bob (his o.status is NULL)
SELECT c.name FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.status = 'open';
-- Keep unmatched rows: move the test into ON
SELECT c.name FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id AND o.status = 'open';Практические правила
Применяйте эти правила в каждом запросе с агрегатными функциями и соединениями:
- Агрегатные функции игнорируют NULL (кроме
COUNT(*)). COUNT(col)<COUNT(*), если в столбце col есть NULL.SUM/AVGдля пустого набора возвращают NULL — оборачивайте их вCOALESCE.- LEFT JOIN создаёт NULL для несовпадений; подсчитывайте ключ правой таблицы.
- Фильтры по правой таблице должны находиться в
ON, а не вWHERE.
SELECT c.name,
COALESCE(SUM(o.amount), 0) AS spent,
COUNT(o.id) AS orders
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
GROUP BY c.name;Быстрая проверка
В столбце amount в трёх строках находятся значения 100, NULL и 200. Что вернут COUNT(*) и COUNT(amount)?
Итоги
Вы узнали, как NULL распространяются при агрегации и соединениях: агрегатные функции пропускают NULL, COUNT(*) считает строки, а COUNT(col) — значения, не равные NULL; суммы для пустого набора равны NULL, а GROUP BY объединяет NULL в одну группу.
Вы также увидели, что внешние соединения создают NULL для несовпадений, и узнали, почему фильтры по правой таблице должны находиться в ON. На этом завершается курс Работа с NULL — теперь вы умеете уверенно обрабатывать отсутствующие данные.
-- A NULL-safe summary query
SELECT c.name,
COUNT(o.id) AS orders,
COALESCE(SUM(o.amount), 0) AS total_spent
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
GROUP BY c.name
ORDER BY total_spent DESC;Часто задаваемые вопросы
Урок «NULL в агрегатах и объединениях» бесплатный?
Да — полный текст урока «NULL в агрегатах и объединениях» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс SQL Academy, подпишись на CoddyKit PRO. Курс SQL Academy содержит 4 уроков всего.
Чему я научусь в уроке «NULL в агрегатах и объединениях»?
Узнайте, как NULL ведёт себя в COUNT, SUM и JOIN Ты практикуешь SQL Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать SQL Academy?
Предыдущий опыт не требуется. SQL Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «NULL в агрегатах и объединениях»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке SQL Academy?
Да. Каждый урок SQL Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Что на самом деле означает NULL
- IS NULL и IS NOT NULL
- COALESCE и NULLIF
- NULL в агрегатах и объединениях