W SQL istnieje możliwość grupowania wierszy. Przykładowo jeżeli chcemy przedstawić zbiorcze statystyki zwierzaków rozdzielając je podług gatunku, musimy użyć konstrukcji GROUP BY.
Grupujemy wybierając kolumny do grupowania (zwykle jedną), w naszym przypadku Gatunek. Grupować można także po kilku kolumnach. Frazę GROUP BY umieszcza się po warunkach wybierających wiersze.
Po grupowaniu wierszy, we frazie SELECT nie możemy odnosić się do kolumn po których nie grupujemy, musimy zamiast tego używać funkcji agregujących. Jest to dość oczywiste, przykładowo kiedy pogrupujemy tabelę reprezentującą zwierzaki po gatunku, to odwoływanie się do imienia nie ma sensu - imiona dla danego gatunku mogą być przecież różne.
SELECT gatunek, COUNT(*) AS ile FROM Zwierzaki GROUP BY gatunek ORDER BY gatunek; SELECT gatunek, AVG(waga), MAX(wiek) FROM Zwierzaki WHERE gatunek IS NOT NULL GROUP BY gatunek ORDER BY gatunek;Istnieje 5 funkcji agregujących:
Funkcji tych można używać także kiedy tabela nie jest grupowana, odnosić się będą wówczas do wszystkich wybranych wierszy.
Warunki wybierające wiersze, które grupujemy, możemy umieścić we frazie WHERE. Natomiast warunki dotyczące grup (na przykład ,,wybrać grupy które mają więcej niż 3 elementy'') umieszczamy we frazie HAVING.
Ponieważ wiersze z wartościami NULL utworzą osobną grupę, należy się ich
wstępnie pozbyć we frazie WHERE
SELECT COUNT(*), gatunek, MIN(waga), MAX(waga) FROM Zwierzaki WHERE gatunek IS NOT NULL GROUP BY gatunek HAVING COUNT(*) > 3 ORDER BY gatunek;Pomijając frazę SELECT, kolejność logiczna wykonywanych operacji jest taka jak w tekście
Korzystając tylko z danych w tabeli Zwierzaki:
Czasem grupując wiersze po pewnej kolumnie wiemy, że w danej grupie wszystkie wartości w niektórych innych kolumnach też są sobie równe. Na przykład jeżeli grupujemy zadania studentów po indeksie, to imię i nazwisko też będą ustalone w ramach grupy. Jak umieścić takie kolumny w SELECT?
Po prostu dołączyć je do GROUP BY.
SELECT indeks, imie, nazwisko, AVG(ocena) FROM Klasowki GROUP BY indeks, imie, nazwisko;
Można również użyć funkcji agregujacej MIN lub MAX (działają na wszystkich typach danych), ale wyglada to nienaturalnie.
SELECT indeks, MIN(imie), MIN(nazwisko), AVG(ocena) FROM Klasowki GROUP BY indeks;
Inną częstą sytuacją jest potrzeba wypisania danych z wiersza, który zawiera największą/najmniejszą wartość w jakiejś kolumnie. Na przykład szukamy najcięższego zwierzaka. Standardowe rozwiązanie to
SELECT * FROM Zwierzaki WHERE waga = (SELECT MAX(waga) FROM Zwierzaki);
Rozwiązanie trikowe to użycie złączenia zewnętrznego tabeli z nią samą.
SELECT A.* FROM Zwierzaki A LEFT JOIN Zwierzaki B ON A.waga < B.waga WHERE B.id IS NULL;
Jeszcze inny sposób to
SELECT * FROM Zwierzaki Z1 WHERE NOT EXISTS (SELECT * FROM Zwierzaki Z2 where Z2.waga > Z1.waga);
Wiemy jak wypisać najcięższego zwierzaka, a jak wypisać siedem albo siedemnaście najcięższych?
Umiemy posortować wybierane wiersze w odpowiedniej kolejności. Pierwszy
pomysł to użyć kontrukcji ORDER BY
z niestandardowym dodatkiem
LIMIT
, podającym liczbę wybieranych wierszy:
SELECT imie FROM Zwierzaki WHERE waga IS NOT NULL ORDER BY waga DESC LIMIT 7; -- trochę źle!!
Ale co się stanie, jeśli niektóre zwierzaki (na przykład siódmy i ósmy) będa ważyły tyle samo? SQL niedeterministycznie przypisze któremuś z nich niższą pozycję!
Trzeba znaleźć wagę siódmego zwierzaka i wybrać wszystkie te zwierzaki, które ważą co najmniej tyle.
SELECT imie FROM Zwierzaki WHERE waga IN (SELECT waga FROM Zwierzaki WHERE waga IS NOT NULL ORDER BY waga DESC LIMIT 7);
Można też to zrobić tak
SELECT imie FROM Zwierzaki z1 WHERE 7 > (SELECT COUNT(*) FROM Zwierzaki z2 WHERE z2.waga > z1.waga);
select gatunek, count(*), avg(waga) from Zwierzaki group by gatunek;
select gatunek, count(*) from Zwierzaki where waga > 20 group by gatunek;
select gatunek, max(waga) from Zwierzaki where waga > 20 group by gatunek having count(*) > 1;
select gatunek from Zwierzaki group by gatunek having max(waga) - min(waga) > 20;
select count(*) as "Nieznane" from Zwierzaki where gatunek is null;