SQL od podstaw · część 17

Funkcje agregujące: COUNT, SUM, AVG, MIN i MAX

Podsumuj wiele rekordów jednym wynikiem. Rozróżniaj COUNT(*), COUNT(kolumna), DISTINCT i wpływ NULL na średnią.

Część bazodanowa INF.03

Od wielu wierszy do jednej informacji

Funkcja agregująca podsumowuje zbiór wierszy. COUNT odpowiada „ile?”, SUM „jaka suma?”, AVG „jaka średnia?”, MIN i MAX „jaka najmniejsza i największa wartość?”. Bez GROUP BY cały zbiór po WHERE stanowi jedną grupę. To inna rola niż ROUND(cena,2), które oblicza wynik osobno dla wartości ceny.

120, 60, 800, 25cztery ceny→SUM(cena)agregacja→1005jeden wynik
SELECT COUNT(*) AS produkty,
       SUM(cena) AS suma_cen,
       AVG(cena) AS srednia_cena,
       MIN(cena) AS najtaniej,
       MAX(cena) AS najdrozej
FROM produkty;

Po resecie otrzymasz jeden wiersz: 4, 1005, 251.25, 25, 800. Suma cen katalogowych nie oznacza wartości sprzedaży: ta wymaga także liczby sprzedanych sztuk i właściwego złączenia. Nazwa aliasu powinna mówić, co faktycznie policzyłeś.

COUNT(*) i COUNT(kolumna) liczą co innego

SELECT COUNT(*) AS klienci,
       COUNT(email) AS z_emailem,
       COUNT(*) - COUNT(email) AS bez_emaila
FROM klienci;

Wynik: 5, 3, 2. COUNT(*) liczy wiersze; COUNT(email) tylko wiersze, w których email nie jest NULL. Pusty napis jest wartością tekstową w SQLite/MySQL/SQL Server, więc też zostaje policzony. Oracle traktuje pusty napis jako NULL. COUNT(1) również liczy wszystkie wiersze, bo stała 1 nigdy nie jest NULL; nie potrzebujesz tego zapisu zamiast czytelnego COUNT(*).

Wypróbuj zapytanie

SQLite — rzeczywisty silnik przeglądarki. MySQL / SQL Server — ograniczona emulacja składni. Wybierz tryb SQLite do natywnego LIKE. Pozostałe tryby nie są rzeczywistymi serwerami. Ścisłe typy, auto-ID i administrację ćwicz w prawdziwym SZBD.

Ładowanie edytora…

NULL, zero i pusty zbiór

SUM, AVG, MIN i MAX pomijają NULL w argumentach. AVG dla 10, NULL, 20 daje 15: dzieli sumę 30 przez dwie znane wartości. Zastąpienie NULL zerem przed AVG zmieniłoby średnią na 10 i oznaczałoby nowe założenie o danych. Nie wykonuj takiej zamiany mechanicznie.

SELECT COUNT(*) AS liczba, SUM(cena) AS suma, AVG(cena) AS srednia
FROM produkty WHERE cena < 0;

Po resecie żaden produkt nie spełnia warunku. Mimo tego agregacja bez GROUP BY zwraca jeden wiersz: 0, NULL, NULL. Gdy raport ma pokazywać sumę pustego zbioru jako 0, użyj COALESCE(SUM(cena),0). To świadoma reguła prezentacji. Przy grupowaniu pusty zbiór nie tworzy żadnej grupy.

DISTINCT wewnątrz agregacji

-- MySQL / MariaDB, SQL Server, Oracle, SQLite
SELECT COUNT(DISTINCT miasto) AS rozne_miasta FROM klienci;

Wynik to 3: Gdansk, Krakow i Torun. NULL nie jest liczony przez COUNT(kolumna). DISTINCT przed listą SELECT usuwa identyczne wiersze wyniku; wewnątrz COUNT usuwa powtórzone wartości argumentu przed liczeniem. To nie to samo miejsce działania.

Access nie obsługuje tej formy COUNT(DISTINCT …). Utwórz zapisaną kwerendę qMiasta:

SELECT DISTINCT miasto FROM klienci WHERE miasto IS NOT NULL;

Następnie policz jej rekordy: SELECT COUNT(*) AS rozne_miasta FROM qMiasta;. Zapisana kwerenda jest źródłem następnej kwerendy, nie nową kopią danych.

Te same funkcje, różne typy wyniku

Podstawowe zapytanie z COUNT/SUM/AVG/MIN/MAX działa w MySQL/MariaDB, SQL Server, Oracle i Access. Szczegóły typów oraz zakresy liczb nie są jednak identyczne. SQL Server AVG(int) zwraca int, więc średnia całkowitych 1 i 2 nie zachowa ułamka. Gdy ułamek jest potrzebny, rzutuj argument przed agregacją:

-- Rzeczywisty SQL Server
SELECT AVG(CAST(ilosc AS decimal(12,2))) AS srednia_ilosc
FROM pozycje;

SQLite AVG zwraca liczbę rzeczywistą. To jedna z przyczyn, dla których emulacja składni nie zastępuje testu typów serwera. W edytorze użyj natywnego SQLite do doświadczeń liczbowych; ceny są REAL. Dla cen DECIMAL w produkcyjnej bazie sprawdź precyzję, skalę i możliwe przepełnienie SUM. W SQL Server COUNT ma zakres int; dla ogromnych zbiorów istnieje COUNT_BIG.

Najczęstsza pułapka: nazwa przy MAX

SELECT nazwa, MAX(cena) FROM produkty; nie jest przenośnym sposobem znalezienia najdroższego produktu. Nieagregowana nazwa nie została pogrupowana. Niektóre silniki odrzucą zapis, a SQLite ma szczególne reguły dla takich kolumn, które nie są zasadą całego SQL. Użyj ORDER BY cena DESC, id i limitu z poprzedniej lekcji albo porównania ceny do podzapytania MAX, które poznamy później. Podzapytanie może zachować wszystkie remisy.

Ćwiczenia

  1. Policz zamówienia ze statusem opłacone (w SQL wpisz dokładnie 'oplacone').
  2. Znajdź średnią i sumę cen produktów kosztujących co najmniej 100.
  3. Policz klientów bez podanego miasta na dwa sposoby.
Rozwiązania
SELECT COUNT(*) AS oplacone FROM zamowienia WHERE status = 'oplacone';
SELECT AVG(cena) AS srednia, SUM(cena) AS suma FROM produkty WHERE cena >= 100;
SELECT COUNT(*) AS brak_miasta FROM klienci WHERE miasto IS NULL;
SELECT COUNT(*) - COUNT(miasto) AS brak_miasta FROM klienci;

Wyniki: 2 zamówienia; średnia 460 i suma 920; jeden klient bez miasta (Ewa). WHERE ogranicza wiersze przed obliczeniem agregacji.

Zapamiętaj: nazwij zbiór, który podsumowujesz, ustal znaczenie NULL i jednostkę wyniku. Dopiero potem wybierz funkcję.

Źródła: MySQL — agregacje, SQL Server AVG i typ wyniku, Oracle — agregacje, Access COUNT. INF.03.4: zestawienia danych. Dalej: GROUP BY i HAVING.

Przewijanie do góry