Вспоминаем, что агрегатные функции — это те, которые работают сразу с несколькими строками данных и возвращают один результат. В PostgreSQL вы часто будете использовать следующие агрегатные функции:
SUM()— суммирование данных.AVG()— нахождение среднего значения.MIN()— поиск минимального значения.MAX()— поиск максимального значения.COUNT()— подсчёт строк.
На первый взгляд, всё просто: передаёшь в функцию столбец или выражение, и получаешь результат. Но что происходит, если в столбце попадается NULL?
Поведение NULL в агрегатах: краткий обзор
Вот здесь начинается самое интересное:
SUM()иAVG()игнорируютNULL. Если хотя бы одна запись имеет значениеNULL, она просто не учтется в вычислениях. Вроде бы справедливое поведение, ведь как вырастет сумма, если кто-то "не пришёл на вечеринку"? Или как считать среднее, если одного значения не хватает?MIN()иMAX()также пропускаютNULL. Они находят минимальное или максимальное значение только из тех данных, которые не являютсяNULL. Так что, когда вы ищете самого младшего сотрудника, пропустившего заполнение даты рождения,NULLне станет победителем.COUNT(*)учитывает все строки, даже те, где естьNULL. А вотCOUNT(column)будет считать только строки, где в указанном столбце есть значение, то естьNULLигнорируется.
Давайте разберем это на примерах.
Примеры использования агрегатных функций с NULL
Вот таблица students_scores, содержащая оценки студентов за тест:
| student_id | name | score |
|---|---|---|
| 1 | Алиса | 85 |
| 2 | Боб | NULL |
| 3 | Чарли | 92 |
| 4 | Дана | NULL |
| 5 | Елена | 74 |
Теперь зададим несколько запросов и разберём их результаты:
- Сумма всех оценок:
SUM()
SELECT SUM(score) AS total_score
FROM students_scores;
Результат:
| total_score |
|---|
| 251 |
Как видите, пропущенные NULL значения просто не участвовали в суммировании. Для Алисы (85), Чарли (92) и Елены (74) сумма составила 251. Боб и Дана остались за бортом.
- Средняя оценка:
AVG()
SELECT AVG(score) AS average_score
FROM students_scores;
Результат:
| average_score |
|---|
| 83.67 |
Опять же, NULL были проигнорированы, и среднее значение рассчитывалось только для тех, у кого есть оценки: (85 + 92 + 74) / 3 = 83.67.
- Минимальная и максимальная оценка:
MIN()иMAX()
SELECT
MIN(score) AS min_score,
MAX(score) AS max_score
FROM students_scores;
Результат:
| min_score | max_score |
|---|---|
| 74 | 92 |
Здесь тоже всё просто: NULL значения снова проигнорированы, и минимальной стала оценка 74, а максимальной — 92.
- Подсчёт строк:
COUNT(*)vsCOUNT(column)
SELECT
COUNT(*) AS total_rows,
COUNT(score) AS non_null_scores
FROM students_scores;
Результат:
| total_rows | non_null_scores |
|---|---|
| 5 | 3 |
COUNT(*)учёл все строки, включая те, гдеscoreравенNULL.COUNT(score)посчитал только те строки, у которых в столбцеscoreесть значение.
Практические кейсы
Приведём несколько практических примеров.
Пример 1: Подсчёт сотрудников с указанной и неуказанной зарплатой
Допустим, у нас есть таблица employees с зарплатами.
| id | name | salary |
|---|---|---|
| 1 | Alex Lin | 50000 |
| 2 | Maria Chi | NULL |
| 3 | Anna Song | 60000 |
| 4 | Otto Art | NULL |
| 5 | Liam Park | 55000 |
Мы хотим узнать, сколько сотрудников указали свою зарплату, а сколько — нет.
SELECT
COUNT(*) AS total_employees,
COUNT(salary) AS employees_with_salary,
COUNT(*) - COUNT(salary) AS employees_without_salary
FROM employees;
Здесь:
COUNT(*)вернёт общее количество сотрудников.COUNT(salary)посчитает, сколько сотрудников указали зарплату.- Для вычисления числа сотрудников без зарплаты, мы просто вычитаем одно значение из другого.
Результат
| total_employees | employees_with_salary | employees_without_salary |
|---|---|---|
| 5 | 3 | 2 |
Пример 2: Расчёт средней цены товаров с учётом отсутствующих данных
Вы владелец волшебного магазина, и в таблице products есть колонка price, но у некоторых товаров цена пока не указана.
| id | name | price |
|---|---|---|
| 1 | Magic Wand | 150 |
| 2 | Enchanted Cloak | NULL |
| 3 | Potion Bottle | 75 |
| 4 | Spell Book | 200 |
| 5 | Crystal Ball | NULL |
Вам нужно узнать среднюю цену только для тех товаров, где она задана.
SELECT AVG(price) AS average_price
FROM products;
Результат:
| average_price |
|---|
| 141.6667 |
Если вы хотите указать цену по умолчанию для товаров без цены (например, сделать её равной 0), можно использовать функцию COALESCE() из следующей лекции.
Пример 3: Нахождение минимального и максимального возраста студентов
В таблице students хранится возраст учащихся, но для некоторых из них возраст неизвестен (NULL).
| id | name | age |
|---|---|---|
| 1 | Alex Lin | 20 |
| 2 | Maria Chi | NULL |
| 3 | Anna Song | 19 |
| 4 | Otto Art | 22 |
| 5 | Liam Park | NULL |
Мы хотим узнать, самого младшего и старшего студента.
SELECT
MIN(age) AS youngest_student,
MAX(age) AS eldest_student
FROM students;
Результат:
| youngest_student | eldest_student |
|---|---|
| 19 | 22 |
Этот запрос вернёт минимальный и максимальный возраст только для тех студентов, чей возраст указан. NULL снова будет опущен.
Особенности и подводные камни
Когда работаете с NULL в агрегатах, важно помнить про следующие моменты:
- В сумме
SUM()и среднемAVG()NULLне учитываются. Это можно использовать, чтобы не добавлять "пустые" значения в вычисления. - Если вам нужно учитывать строки с
NULLв столбце, можно использоватьCOUNT(*). - При использовании
MIN()илиMAX(),NULLне влияет на результат. Однако, если весь столбец заполнен толькоNULL, результатом будет тожеNULL.
Советы для работы с NULL
- Учитывайте специфику задачи. Важно понимать, нужно ли учитывать
NULLв вашем запросе. Иногда, как в случае сAVG(), их игнорирование — это то, что нужно. А иногда, как при подсчёте общего количества, важно учесть и строки сNULL. - Используйте
COALESCE()при необходимости. Если нужно заменитьNULLзначением по умолчанию в расчётах, функцияCOALESCE()станет вашим другом (но это уже тема следующей лекции). - Не путайте
COUNT(*)иCOUNT(column). Это классическая ошибка новичков. Первый считает строки, второй — только строки с ненулевыми значениями.
Теперь вы знаете, как хитро умалчивающий NULL может повлиять на агрегаты. Это знание позволит вам избежать неприятных сюрпризов и использовать NULL в своих интересах. В следующей лекции мы изучим мощный инструмент COALESCE(), чтобы справляться с NULL ещё эффективнее.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ