Funkcje agregujące: liczenie, sumowanie i znajdowanie przydatnych liczb

Witaj z powrotem.
W poprzedniej lekcji poznałeś JOIN.
Nauczyłeś się czytać dane z powiązanych tabel.
Bardzo ważna sprawa.
Teraz możemy zadawać PostgreSQL pytania typu:
Pokaż produkty z nazwami kategorii.
Pokaż zamówienia z nazwami klientów.
Pokaż posty z nazwami autorów.
Świetnie.
Ale czasem nie chcemy widzieć każdego wiersza.
Czasem chcemy dostać odpowiedź.
Na przykład:
Ile mamy produktów?
Jaka jest całkowita wartość wszystkich produktów?
Jaka jest średnia cena?
Który produkt jest najtańszy?
Który produkt jest najdroższy?
Ile produktów jest w każdej kategorii?
Tutaj pomagają funkcje agregujące.
Funkcje agregujące biorą wiele wierszy i zwracają jeden użyteczny wynik.
Bardzo grzecznie.
Bardzo produktywnie.
Jak zmęczony księgowy z supermocami.
Czego się nauczysz
W tej lekcji nauczysz się:
- czym są funkcje agregujące;
- jak używać
COUNT; - jak używać
SUM; - jak używać
AVG; - jak używać
MIN; - jak używać
MAX; - jaka jest różnica między
COUNT(*)iCOUNT(column); - jak
NULLwpływa na funkcje agregujące; - jak używać
GROUP BY; - jak liczyć wiersze w grupach;
- jak agregować dane razem z
JOIN; - jak używać
HAVING; - jakie są typowe błędy w zapytaniach agregujących.
Pod koniec tej lekcji będziesz potrafił zadawać PostgreSQL praktyczne pytania biznesowe.
Nie tylko:
Pokaż mi wszystko.
Ale:
Podsumuj ten chaos i powiedz mi, co jest ważne.
To duży krok naprzód.
Baza danych przestaje być tylko pudełkiem na dane.
Zaczyna być asystentem, który umie liczyć.
A to jest przydatne.
Bo ludzie nie zawsze są dobrzy w liczeniu.
Szczególnie po czwartej kawie.
Przygotowanie bazy danych
Otwórz PostgreSQL:
sudo -iu postgres psql
Połącz się ze swoją bazą danych:
\c learning_postgresql
Jeśli baza nie istnieje, utwórz ją:
CREATE DATABASE learning_postgresql;
Potem połącz się z nią:
\c learning_postgresql
Teraz usuń stare tabele, jeśli istnieją:
DROP TABLE IF EXISTS products;
DROP TABLE IF EXISTS categories;
Usuwamy najpierw products, ponieważ ta tabela zależy od categories.
PostgreSQL pamięta relacje.
Czasem lepiej niż ludzie.
Tworzenie tabel
Utwórz tabelę categories:
CREATE TABLE categories (
id SERIAL PRIMARY KEY,
name VARCHAR(100) UNIQUE NOT NULL
);
Utwórz tabelę products:
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price NUMERIC(10, 2) CHECK (price >= 0),
quantity INTEGER CHECK (quantity >= 0),
category_id INTEGER REFERENCES categories(id)
);
Ta tabela ma:
- nazwę produktu;
- cenę;
- ilość;
- kategorię.
To już wygląda całkiem realistycznie.
Jeszcze nie pełny system sklepu.
Ale wystarczy, żeby PostgreSQL trochę popracował.
Zdrowo.
Edukacyjnie.
Bez narzekania na nadgodziny.
Dodawanie danych
Dodaj kategorie:
INSERT INTO categories (name)
VALUES
('Electronics'),
('Furniture'),
('Office'),
('Kitchen');
Dodaj produkty:
INSERT INTO products (name, price, quantity, category_id)
VALUES
('Laptop', 900.00, 5, 1),
('Mouse', 25.00, 30, 1),
('Keyboard', 70.00, 15, 1),
('Desk Chair', 150.00, 8, 2),
('Bookshelf', 120.00, 4, 2),
('Notebook', 5.00, 100, 3),
('Pen', 2.00, 200, 3),
('Whiteboard', 80.00, 3, 3);
Sprawdź dane:
SELECT * FROM categories;
SELECT * FROM products;
Zauważ jedną rzecz.
Kategoria Kitchen nie ma produktów.
To przyda się później.
Biedna Kitchen.
Czeka na łyżkę.
Czym są funkcje agregujące?
Funkcje agregujące obliczają jeden wynik na podstawie wielu wierszy.
Na przykład ta tabela ma wiele produktów.
Ale możemy zapytać:
Ile jest produktów?
PostgreSQL może zwrócić jedną liczbę.
To właśnie agregacja.
Najczęstsze funkcje agregujące to:
COUNT
SUM
AVG
MIN
MAX
Odpowiadają na pytania typu:
Ile?
Ile razem?
Jaka jest średnia?
Jaka jest najmniejsza wartość?
Jaka jest największa wartość?
To bardzo przydatne w dashboardach, raportach, panelach administracyjnych, API, fakturach, statystykach i narzędziach biznesowych.
Czyli wszędzie tam, gdzie ludzie chcą liczb.
A potem kłócą się o nie na spotkaniach.
Klasyka.
COUNT
COUNT liczy wiersze.
Wykonaj:
SELECT COUNT(*)
FROM products;
Wynik:
count
-----
8
COUNT(*) oznacza:
Policz wszystkie wiersze.
Nie interesuje go konkretna kolumna.
Po prostu liczy wiersze.
Prosto.
Mocno.
Bez dramatu.
COUNT z aliasem
Kolumna wyniku nazywa się count.
Może być.
Ale możemy zrobić to czytelniej:
SELECT COUNT(*) AS total_products
FROM products;
Wynik:
total_products
--------------
8
Dużo lepiej.
Aliasy sprawiają, że wyniki są łatwiejsze do zrozumienia.
Przyszły ty będzie wdzięczny.
Przyszły ty już jest zmęczony.
Pomóż mu.
COUNT dla kolumny
Możesz też policzyć konkretną kolumnę:
SELECT COUNT(category_id) AS products_with_category
FROM products;
To liczy wiersze, w których category_id nie jest NULL.
W naszych danych każdy produkt ma kategorię.
Więc wynik to:
products_with_category
----------------------
8
Ale jest tutaj ważna różnica.
COUNT(*) liczy wszystkie wiersze.
COUNT(column) liczy tylko te wiersze, w których dana kolumna nie jest NULL.
To ma ogromne znaczenie.
Bo NULL to nie zero.
NULL oznacza brak wartości albo wartość nieznaną.
PostgreSQL traktuje to ostrożnie.
I bardzo dobrze.
NULL nie jest liczbą.
To mały duch bazy danych.
Przykład z NULL
Dodaj produkt bez kategorii:
INSERT INTO products (name, price, quantity, category_id)
VALUES ('Mystery Box', 50.00, 2, NULL);
Teraz wykonaj:
SELECT COUNT(*) AS total_products
FROM products;
Otrzymasz:
total_products
--------------
9
Teraz wykonaj:
SELECT COUNT(category_id) AS products_with_category
FROM products;
Otrzymasz:
products_with_category
----------------------
8
Dlaczego?
Bo Mystery Box ma category_id = NULL.
Więc COUNT(category_id) go nie liczy.
To ważne.
Bardzo ważne.
Ważne jak baza danych.
Nie jak “kup mleko”.
Bardziej jak “dlaczego mój raport jest zły?” ważne.
SUM
SUM sumuje wartości.
Na przykład możemy obliczyć całkowitą liczbę sztuk w magazynie:
SELECT SUM(quantity) AS total_items
FROM products;
To sumuje wszystkie ilości.
Przykładowy wynik:
total_items
-----------
367
Możesz też obliczyć całkowitą wartość magazynu.
Dla każdego produktu potrzebujemy:
price * quantity
Potem sumujemy wynik:
SELECT SUM(price * quantity) AS total_stock_value
FROM products;
Przykładowy wynik:
total_stock_value
-----------------
8945.00
To jest użyteczne.
Teraz PostgreSQL nie tylko przechowuje dane.
On oblicza przydatne informacje.
Bardzo profesjonalnie.
Bardzo “chyba ktoś powinien wystawić fakturę”.
AVG
AVG oblicza średnią wartość.
Przykład:
SELECT AVG(price) AS average_price
FROM products;
PostgreSQL może zwrócić dużo cyfr po przecinku.
Żeby wynik był ładniejszy, użyj ROUND:
SELECT ROUND(AVG(price), 2) AS average_price
FROM products;
Przykładowy wynik:
average_price
-------------
155.78
Dokładna liczba zależy od twoich danych.
AVG jest przydatne przy pytaniach typu:
Jaka jest średnia cena produktu?
Jaka jest średnia wartość zamówienia?
Jaka jest średnia pensja?
Jaki jest średni poziom chaosu w tym projekcie?
PostgreSQL odpowie na pierwsze trzy.
Do czwartego potrzeba wsparcia emocjonalnego.
MIN
MIN znajduje najmniejszą wartość.
Przykład:
SELECT MIN(price) AS cheapest_price
FROM products;
Wynik:
cheapest_price
--------------
2.00
To mówi nam, jaka jest najniższa cena.
Ale nie mówi, który produkt ma tę cenę.
Do tego możemy użyć ORDER BY i LIMIT:
SELECT name, price
FROM products
ORDER BY price ASC
LIMIT 1;
Wynik:
name | price
-----|------
Pen | 2.00
MIN daje minimalną wartość.
ORDER BY z LIMIT może pokazać cały wiersz.
Oba narzędzia są użyteczne.
Różne narzędzia.
Ta sama skrzynka.
MAX
MAX znajduje największą wartość.
Przykład:
SELECT MAX(price) AS highest_price
FROM products;
Wynik:
highest_price
-------------
900.00
Aby znaleźć najdroższy produkt:
SELECT name, price
FROM products
ORDER BY price DESC
LIMIT 1;
Wynik:
name | price
-------|------
Laptop | 900.00
Bardzo dobrze.
PostgreSQL znalazł drogą bestię.
Prawdopodobnie z podświetleniem RGB.
Kilka agregacji w jednym zapytaniu
Możesz użyć kilku funkcji agregujących w jednym zapytaniu.
Przykład:
SELECT
COUNT(*) AS total_products,
SUM(quantity) AS total_items,
ROUND(AVG(price), 2) AS average_price,
MIN(price) AS cheapest_price,
MAX(price) AS highest_price
FROM products;
To daje jeden wiersz podsumowania.
Przykład:
total_products | total_items | average_price | cheapest_price | highest_price
---------------|-------------|---------------|----------------|--------------
9 | 367 | 155.78 | 2.00 | 900.00
Idealne do dashboardów.
Jedno zapytanie.
Wiele przydatnych liczb.
PostgreSQL robi pracę biurową bez proszenia o krzesło.
Szacunek.
GROUP BY
Do tej pory funkcje agregujące dawały jeden wynik dla całej tabeli.
Ale często chcemy wyniki dla grup.
Na przykład:
Ile produktów jest w każdej kategorii?
Do tego używamy GROUP BY.
Przykład:
SELECT
category_id,
COUNT(*) AS products_count
FROM products
GROUP BY category_id;
Przykładowy wynik:
category_id | products_count
------------|---------------
1 | 3
2 | 2
3 | 3
| 1
Pusta kategoria oznacza NULL.
To nasz Mystery Box.
Nie ma kategorii.
Bardzo tajemniczy.
Bardzo box.
GROUP BY z JOIN
category_id jest przydatne dla PostgreSQL.
Ale ludzie chcą nazw kategorii.
Więc używamy JOIN.
SELECT
c.name AS category_name,
COUNT(p.id) AS products_count
FROM categories AS c
JOIN products AS p
ON c.id = p.category_id
GROUP BY c.name;
Wynik:
category_name | products_count
--------------|---------------
Electronics | 3
Furniture | 2
Office | 3
Dużo ładniej.
Ale zauważ jedną rzecz.
Kitchen zniknęła.
Dlaczego?
Bo JOIN oznacza tutaj INNER JOIN.
Pojawiają się tylko pasujące wiersze.
Kitchen nie ma produktów.
Więc się nie pojawia.
Biedna Kitchen znowu.
GROUP BY z LEFT JOIN
Jeżeli chcemy pokazać wszystkie kategorie, nawet te bez produktów, używamy LEFT JOIN.
SELECT
c.name AS category_name,
COUNT(p.id) AS products_count
FROM categories AS c
LEFT JOIN products AS p
ON c.id = p.category_id
GROUP BY c.name
ORDER BY c.name;
Wynik:
category_name | products_count
--------------|---------------
Electronics | 3
Furniture | 2
Kitchen | 0
Office | 3
Teraz Kitchen się pojawia.
Ważny szczegół:
Użyliśmy:
COUNT(p.id)
A nie:
COUNT(*)
Dlaczego?
Bo przy LEFT JOIN, COUNT(*) policzyłoby także wiersz kategorii, nawet jeśli nie ma produktu.
Dla kategorii bez produktów COUNT(*) mogłoby dać 1.
To byłoby błędne.
COUNT(p.id) liczy tylko prawdziwe produkty.
Ten szczegół ratuje raporty.
A czasem też pracę.
SUM z GROUP BY
Możemy obliczyć wartość magazynu dla każdej kategorii.
SELECT
c.name AS category_name,
SUM(p.price * p.quantity) AS stock_value
FROM categories AS c
LEFT JOIN products AS p
ON c.id = p.category_id
GROUP BY c.name
ORDER BY stock_value DESC;
Przykładowy wynik:
category_name | stock_value
--------------|------------
Electronics | 6750.00
Furniture | 1680.00
Office | 1140.00
Kitchen |
Kitchen nie ma produktów.
Więc suma to NULL.
Jeśli chcemy pokazać 0 zamiast NULL, możemy użyć COALESCE.
SELECT
c.name AS category_name,
COALESCE(SUM(p.price * p.quantity), 0) AS stock_value
FROM categories AS c
LEFT JOIN products AS p
ON c.id = p.category_id
GROUP BY c.name
ORDER BY stock_value DESC;
Teraz Kitchen pokazuje 0.
COALESCE zwraca pierwszą wartość, która nie jest NULL.
Czyli to:
COALESCE(SUM(p.price * p.quantity), 0)
oznacza:
Jeśli suma istnieje, pokaż ją.
Jeśli suma jest NULL, pokaż 0.
Bardzo użyteczne.
Bardzo praktyczne.
Bardzo “proszę nie zepsuć mojego dashboardu”.
AVG z GROUP BY
Możemy obliczyć średnią cenę produktu w każdej kategorii:
SELECT
c.name AS category_name,
ROUND(AVG(p.price), 2) AS average_price
FROM categories AS c
LEFT JOIN products AS p
ON c.id = p.category_id
GROUP BY c.name
ORDER BY average_price DESC;
Przykładowy wynik:
category_name | average_price
--------------|---------------
Electronics | 331.67
Furniture | 135.00
Office | 29.00
Kitchen |
Znowu Kitchen nie ma produktów.
Więc średnia cena to NULL.
I to jest poprawne.
Nie można obliczyć średniej z braku wartości.
PostgreSQL odmawia wymyślania liczb.
Dobry database.
HAVING
WHERE filtruje wiersze przed grupowaniem.
HAVING filtruje grupy po grupowaniu.
To ważne.
Załóżmy, że chcemy zobaczyć kategorie z więcej niż dwoma produktami.
Źle:
SELECT
category_id,
COUNT(*) AS products_count
FROM products
WHERE COUNT(*) > 2
GROUP BY category_id;
To nie zadziała.
Dlaczego?
Bo WHERE nie może używać wyników agregacji.
Użyj HAVING:
SELECT
category_id,
COUNT(*) AS products_count
FROM products
GROUP BY category_id
HAVING COUNT(*) > 2;
Wynik:
category_id | products_count
------------|---------------
1 | 3
3 | 3
HAVING filtruje grupy.
Prosta idea.
Bardzo częste źródło błędów.
SQL lubi porządek.
Ludzie lubią zgadywać.
SQL wygrywa.
HAVING z JOIN
Teraz pokażmy nazwy kategorii.
SELECT
c.name AS category_name,
COUNT(p.id) AS products_count
FROM categories AS c
JOIN products AS p
ON c.id = p.category_id
GROUP BY c.name
HAVING COUNT(p.id) > 2
ORDER BY products_count DESC;
Wynik:
category_name | products_count
--------------|---------------
Electronics | 3
Office | 3
To znaczy:
Pokaż tylko kategorie, które mają więcej niż dwa produkty.
To jest przydatne w raportach.
Na przykład:
Pokaż aktywne kategorie.
Pokaż klientów z więcej niż pięcioma zamówieniami.
Pokaż autorów z więcej niż dziesięcioma postami.
Pokaż produkty sprzedane więcej niż sto razy.
HAVING służy do filtrowania wyników agregacji.
Nie zapominaj o nim.
Czeka spokojnie.
Jak poważny bibliotekarz SQL.
WHERE i HAVING razem
Możesz użyć WHERE i HAVING w jednym zapytaniu.
Przykład:
SELECT
c.name AS category_name,
COUNT(p.id) AS products_count
FROM categories AS c
JOIN products AS p
ON c.id = p.category_id
WHERE p.price >= 10
GROUP BY c.name
HAVING COUNT(p.id) >= 2
ORDER BY products_count DESC;
To oznacza:
Najpierw zostaw tylko produkty z ceną co najmniej 10.
Potem pogrupuj je według kategorii.
Potem pokaż tylko kategorie z co najmniej dwoma produktami.
Kolejność ma znaczenie.
WHERE działa przed grupowaniem.
HAVING działa po grupowaniu.
Zapamiętaj to, a SQL będzie bolał mniej.
Nie nigdy.
Mniej.
Typowe błędy
Używanie WHERE zamiast HAVING
Źle:
SELECT category_id, COUNT(*)
FROM products
WHERE COUNT(*) > 2
GROUP BY category_id;
Dobrze:
SELECT category_id, COUNT(*)
FROM products
GROUP BY category_id
HAVING COUNT(*) > 2;
Używaj WHERE dla zwykłych wierszy.
Używaj HAVING dla grup.
Zapomnienie o GROUP BY
Źle:
SELECT category_id, COUNT(*)
FROM products;
PostgreSQL będzie narzekał, bo category_id nie jest ani agregowane, ani grupowane.
Dobrze:
SELECT category_id, COUNT(*)
FROM products
GROUP BY category_id;
Jeśli wybierasz zwykłą kolumnę razem z funkcją agregującą, zwykle potrzebujesz GROUP BY.
PostgreSQL jest surowy.
Ale sprawiedliwy.
Przeważnie.
Liczenie złej rzeczy przy LEFT JOIN
Źle dla liczenia produktów w kategorii:
SELECT
c.name,
COUNT(*) AS products_count
FROM categories AS c
LEFT JOIN products AS p
ON c.id = p.category_id
GROUP BY c.name;
To może policzyć kategorię bez produktów jako 1.
Lepiej:
SELECT
c.name,
COUNT(p.id) AS products_count
FROM categories AS c
LEFT JOIN products AS p
ON c.id = p.category_id
GROUP BY c.name;
Licz kolumnę z tej tabeli, którą naprawdę chcesz policzyć.
Mały szczegół.
Duża różnica.
Klasyczna pułapka baz danych.
Praktyka
Policz wszystkie produkty:
SELECT COUNT(*) AS total_products
FROM products;
Oblicz całkowitą liczbę sztuk:
SELECT SUM(quantity) AS total_items
FROM products;
Oblicz całkowitą wartość magazynu:
SELECT SUM(price * quantity) AS total_stock_value
FROM products;
Znajdź średnią cenę:
SELECT ROUND(AVG(price), 2) AS average_price
FROM products;
Znajdź najniższą i najwyższą cenę:
SELECT
MIN(price) AS cheapest_price,
MAX(price) AS highest_price
FROM products;
Policz produkty według kategorii:
SELECT
c.name AS category_name,
COUNT(p.id) AS products_count
FROM categories AS c
LEFT JOIN products AS p
ON c.id = p.category_id
GROUP BY c.name
ORDER BY c.name;
Oblicz wartość magazynu według kategorii:
SELECT
c.name AS category_name,
COALESCE(SUM(p.price * p.quantity), 0) AS stock_value
FROM categories AS c
LEFT JOIN products AS p
ON c.id = p.category_id
GROUP BY c.name
ORDER BY stock_value DESC;
Pokaż tylko kategorie z więcej niż dwoma produktami:
SELECT
c.name AS category_name,
COUNT(p.id) AS products_count
FROM categories AS c
JOIN products AS p
ON c.id = p.category_id
GROUP BY c.name
HAVING COUNT(p.id) > 2;
Uruchamiaj zapytania.
Zmieniaj liczby.
Dodawaj produkty.
Usuwaj produkty.
Patrz, jak zmieniają się wyniki.
To jest ta zabawna część.
No dobrze.
Zabawna jak na bazę danych.
Inny gatunek zabawy.
Mini wyzwanie
Utwórz dwie tabele:
customers
orders
Zasady:
- jeden klient może mieć wiele zamówień;
- każde zamówienie należy do jednego klienta;
- nazwa klienta jest wymagana;
- wartość zamówienia musi być równa zero lub większa;
- niektórzy klienci mogą nie mieć zamówień.
Utwórz tabele:
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS customers;
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
total NUMERIC(10, 2) CHECK (total >= 0),
customer_id INTEGER REFERENCES customers(id)
);
Dodaj dane:
INSERT INTO customers (name)
VALUES
('Anna'),
('Marco'),
('Sofia'),
('Luca');
INSERT INTO orders (total, customer_id)
VALUES
(49.99, 1),
(120.00, 1),
(35.50, 2),
(200.00, 2),
(15.00, 2),
(80.00, 3);
Teraz napisz zapytania, które:
- policzą wszystkie zamówienia;
- obliczą całkowitą sprzedaż;
- obliczą średnią wartość zamówienia;
- znajdą najmniejsze zamówienie;
- znajdą największe zamówienie;
- pokażą, ile wydał każdy klient;
- pokażą liczbę zamówień każdego klienta;
- pokażą klientów bez zamówień;
- pokażą tylko klientów, którzy wydali więcej niż 100.
Podpowiedź dla klientów bez zamówień:
SELECT
c.name AS customer_name,
COUNT(o.id) AS orders_count
FROM customers AS c
LEFT JOIN orders AS o
ON c.id = o.customer_id
GROUP BY c.name
HAVING COUNT(o.id) = 0;
To jest SQL z prawdziwego świata.
Klienci.
Zamówienia.
Sumy.
Raporty.
Dokładnie takie rzeczy, do których bazy danych zostały stworzone.
Jak arkusze kalkulacyjne.
Ale z większą dyscypliną.
I bez tajemniczych scalonych komórek.
Podsumowanie
Dzisiaj nauczyłeś się:
- funkcje agregujące obliczają wyniki z wielu wierszy;
COUNT(*)liczy wszystkie wiersze;COUNT(column)liczy tylko wartości, które nie sąNULL;SUMsumuje wartości;AVGoblicza średnią;MINznajduje najmniejszą wartość;MAXznajduje największą wartość;GROUP BYtworzy grupy;- agregacji można używać razem z
JOIN; LEFT JOINjest przydatny, gdy chcesz pokazać grupy z zerową liczbą powiązanych wierszy;COUNT(p.id)jest bezpieczniejsze niżCOUNT(*)przy liczeniu powiązanych wierszy;COALESCEmoże zastąpićNULLlepszą wartością;WHEREfiltruje wiersze przed grupowaniem;HAVINGfiltruje grupy po grupowaniu.
To bardzo ważna lekcja.
Teraz potrafisz analizować dane.
Nie tylko je czytać.
Możesz tworzyć raporty.
Możesz tworzyć dashboardy.
Możesz odpowiadać na pytania biznesowe.
Możesz sprawić, że PostgreSQL będzie liczył rzeczy, podczas gdy ty pijesz kawę.
To jest cywilizacja.
Następna lekcja
W następnej lekcji poznamy indeksy i podstawy wydajności.
Zobaczymy, dlaczego niektóre zapytania stają się wolne.
Nauczymy się, jak indeksy pomagają PostgreSQL szybciej znajdować dane.
Bo kiedy baza danych ma dziesięć wierszy, wszystko jest szybkie.
Kiedy ma dziesięć milionów wierszy, PostgreSQL zaczyna zadawać poważne pytania.
I ty też powinieneś.