Pamiętamy, że funkcje agregujące to takie, które działają od razu na wielu wierszach danych i zwracają jeden wynik. W PostgreSQL bardzo często będziesz używać następujących funkcji agregujących:
SUM()— sumowanie danych.AVG()— obliczanie średniej wartości.MIN()— szukanie minimalnej wartości.MAX()— szukanie maksymalnej wartości.COUNT()— liczenie wierszy.
Na pierwszy rzut oka wszystko jest proste: przekazujesz do funkcji kolumnę lub wyrażenie i dostajesz wynik. Ale co się dzieje, jeśli w kolumnie pojawi się NULL?
Zachowanie NULL w agregatach: szybki przegląd
I tu zaczyna się robić ciekawie:
SUM()iAVG()ignorująNULL. Jeśli choć jeden rekord ma wartośćNULL, po prostu nie będzie brany pod uwagę w obliczeniach. W sumie to logiczne — jak suma ma wzrosnąć, jeśli ktoś „nie przyszedł na imprezę”? Albo jak liczyć średnią, jeśli brakuje jednej wartości?MIN()iMAX()też pomijająNULL. Szukają minimum lub maksimum tylko wśród tych danych, które nie sąNULL. Więc jeśli szukasz najmłodszego pracownika, który nie podał daty urodzenia,NULLnie wygra.COUNT(*)liczy wszystkie wiersze, nawet te, gdzie jestNULL. AleCOUNT(column)policzy tylko te wiersze, gdzie w danej kolumnie jest jakaś wartość, czyliNULLjest ignorowany.
Rozłóżmy to na przykładach.
Przykłady użycia funkcji agregujących z NULL
Oto tabela students_scores, która zawiera oceny studentów za test:
| student_id | name | score |
|---|---|---|
| 1 | Alicja | 85 |
| 2 | Bob | NULL |
| 3 | Czarek | 92 |
| 4 | Dana | NULL |
| 5 | Elena | 74 |
Teraz zadamy kilka zapytań i przeanalizujemy wyniki:
- Suma wszystkich ocen:
SUM()
SELECT SUM(score) AS total_score
FROM students_scores;
Wynik:
| total_score |
|---|
| 251 |
Jak widzisz, brakujące wartości NULL po prostu nie zostały uwzględnione w sumowaniu. Dla Alicji (85), Czarka (92) i Eleny (74) suma to 251. Bob i Dana zostali pominięci.
- Średnia ocena:
AVG()
SELECT AVG(score) AS average_score
FROM students_scores;
Wynik:
| average_score |
|---|
| 83.67 |
Znowu, NULL zostały zignorowane i średnia liczona była tylko dla tych, którzy mają ocenę: (85 + 92 + 74) / 3 = 83.67.
- Minimalna i maksymalna ocena:
MIN()iMAX()
SELECT
MIN(score) AS min_score,
MAX(score) AS max_score
FROM students_scores;
Wynik:
| min_score | max_score |
|---|---|
| 74 | 92 |
Tu też wszystko jasne: NULL znowu zostały pominięte, a minimalna ocena to 74, maksymalna — 92.
- Liczba wierszy:
COUNT(*)vsCOUNT(column)
SELECT
COUNT(*) AS total_rows,
COUNT(score) AS non_null_scores
FROM students_scores;
Wynik:
| total_rows | non_null_scores |
|---|---|
| 5 | 3 |
COUNT(*)policzył wszystkie wiersze, także te, gdziescoretoNULL.COUNT(score)policzył tylko te wiersze, gdzie w kolumniescorejest jakaś wartość.
Praktyczne przypadki
Podajmy kilka praktycznych przykładów.
Przykład 1: Liczenie pracowników z podaną i niepodaną pensją
Załóżmy, że mamy tabelę employees z pensjami.
| id | name | salary |
|---|---|---|
| 1 | Alex Lin | 50000 |
| 2 | Maria Chi | NULL |
| 3 | Anna Song | 60000 |
| 4 | Otto Art | NULL |
| 5 | Liam Park | 55000 |
Chcemy wiedzieć, ilu pracowników podało swoją pensję, a ilu nie.
SELECT
COUNT(*) AS total_employees,
COUNT(salary) AS employees_with_salary,
COUNT(*) - COUNT(salary) AS employees_without_salary
FROM employees;
Tutaj:
COUNT(*)zwróci całkowitą liczbę pracowników.COUNT(salary)policzy, ilu pracowników podało pensję.- Aby policzyć pracowników bez pensji, po prostu odejmujemy jedno od drugiego.
Wynik
| total_employees | employees_with_salary | employees_without_salary |
|---|---|---|
| 5 | 3 | 2 |
Przykład 2: Liczenie średniej ceny produktów z uwzględnieniem brakujących danych
Jesteś właścicielem magicznego sklepu i w tabeli products jest kolumna price, ale dla niektórych produktów cena nie jest jeszcze podana.
| id | name | price |
|---|---|---|
| 1 | Magic Wand | 150 |
| 2 | Enchanted Cloak | NULL |
| 3 | Potion Bottle | 75 |
| 4 | Spell Book | 200 |
| 5 | Crystal Ball | NULL |
Chcesz poznać średnią cenę tylko dla tych produktów, gdzie jest ona podana.
SELECT AVG(price) AS average_price
FROM products;
Wynik:
| average_price |
|---|
| 141.6667 |
Jeśli chcesz ustawić domyślną cenę dla produktów bez ceny (np. 0), możesz użyć funkcji COALESCE() z następnego wykładu.
Przykład 3: Znalezienie minimalnego i maksymalnego wieku studentów
W tabeli students przechowywany jest wiek uczniów, ale dla niektórych z nich wiek jest nieznany (NULL).
| id | name | age |
|---|---|---|
| 1 | Alex Lin | 20 |
| 2 | Maria Chi | NULL |
| 3 | Anna Song | 19 |
| 4 | Otto Art | 22 |
| 5 | Liam Park | NULL |
Chcemy poznać najmłodszego i najstarszego studenta.
SELECT
MIN(age) AS youngest_student,
MAX(age) AS eldest_student
FROM students;
Wynik:
| youngest_student | eldest_student |
|---|---|
| 19 | 22 |
To zapytanie zwróci minimalny i maksymalny wiek tylko dla tych studentów, których wiek jest podany. NULL znowu zostanie pominięty.
Specyfika i pułapki
Gdy pracujesz z NULL w agregatach, warto pamiętać o kilku rzeczach:
- W sumie
SUM()i średniejAVG()NULLnie są brane pod uwagę. Możesz to wykorzystać, żeby nie dodawać „pustych” wartości do obliczeń. - Jeśli chcesz uwzględnić wiersze z
NULLw kolumnie, użyjCOUNT(*). - Przy użyciu
MIN()lubMAX(),NULLnie wpływa na wynik. Ale jeśli cała kolumna to tylkoNULL, wynikiem też będzieNULL.
Wskazówki do pracy z NULL
- Zwracaj uwagę na specyfikę zadania. Ważne, czy musisz uwzględniać
NULLw swoim zapytaniu. Czasem, jak przyAVG(), ich ignorowanie jest OK. A czasem, jak przy liczeniu wszystkich rekordów, ważne jest, by uwzględnić też wiersze zNULL. - Używaj
COALESCE()w razie potrzeby. Jeśli musisz zastąpićNULLwartością domyślną w obliczeniach, funkcjaCOALESCE()będzie twoim kumplem (ale to już temat na kolejny wykład). - Nie myl
COUNT(*)iCOUNT(column). To klasyczny błąd początkujących. Pierwszy liczy wiersze, drugi — tylko te z niepustymi wartościami.
Teraz już wiesz, jak sprytnie milczący NULL może wpłynąć na agregaty. Ta wiedza pozwoli ci uniknąć niemiłych niespodzianek i wykorzystać NULL na swoją korzyść. W następnym wykładzie poznamy potężne narzędzie COALESCE(), żeby radzić sobie z NULL jeszcze skuteczniej.
GO TO FULL VERSION