GROUP BY w SQL


GROUP BY służy do grupowania wierszy według jednej lub wielu kolumn, aby potem policzyć wyniki zbiorcze (agregaty), np. COUNT, SUM, AVG, MIN, MAX.

Przykład (ile zamówień ma każdy user):

SELECT user_id, COUNT(*) AS ile_zamowien
FROM orders
GROUP BY user_id;

Minimalny schemat zapytania z GROUP BY

SELECT
  kolumny_grupujace,
  agregaty(...)
FROM tabela
[WHERE ...]
GROUP BY kolumny_grupujace
[HAVING ...]
[ORDER BY ...]
[LIMIT ...];

 Ważne zasady:

W SELECT przy GROUP BY możesz mieć kolumny grupujące (te same, co w GROUP BY) oraz funkcje agregujące

Nie wolno (logicznie) wybierać „zwykłej kolumny” nieujętej w GROUP BY, bo z grupy jest wiele wartości i nie wiadomo którą pokazać.

MariaDB czasem pozwala na to zależnie od ustawień SQL mode, ale to daje nieokreślone/losowe wyniki — lepiej tego unikać.

 

Najważniejsze funkcje agregujące

 COUNT(*) / COUNT(kolumna) – liczenie

 COUNT(*) – liczy wiersze w grupie

COUNT(kolumna) – liczy tylko te, gdzie kolumna IS NOT NULL

SELECT user_id, COUNT(*) AS n
FROM orders
GROUP BY user_id; 

 SUM(kolumna) – suma wartości w grupie.

SELECT user_id, SUM(total) AS suma
FROM orders
GROUP BY user_id; 

 AVG(kolumna) – średnia.

SELECT user_id, ROUND(AVG(total), 2) AS srednia
FROM orders
GROUP BY user_id; 

 MIN(…), MAX(…) – minimum / maksimum.

SELECT user_id, MIN(total) AS min_total, MAX(total) AS max_total
FROM orders
GROUP BY user_id; 

 GROUP_CONCAT(…) – Skleja wartości tekstowe z grupy do jednej kolumny (bardzo przydatne).

SELECT user_id, GROUP_CONCAT(total ORDER BY total SEPARATOR ', ') AS totals
FROM orders
GROUP BY user_id; 

HAVING vs WHERE (kluczowe)

 WHERE – Filtruje wiersze przed grupowaniem.

SELECT user_id, COUNT(*) AS n
FROM orders
WHERE total >= 10
GROUP BY user_id;

 HAVING – Filtruje grupy po zliczeniu/agregacji.

SELECT user_id, COUNT(*) AS n
FROM orders
GROUP BY user_id
HAVING COUNT(*) >= 2;

Często używa się obu naraz:

SELECT user_id, COUNT(*) AS n, ROUND(AVG(total),2) AS avg_total
FROM orders
WHERE created_at >= '2026-02-01'
GROUP BY user_id
HAVING AVG(total) > 20;

Grupowanie po wielu kolumnach

Możesz grupować po więcej niż 1 kolumnie.

Przykład: użytkownik + data (zamówienia dziennie na użytkownika)

SELECT user_id, DATE(created_at) AS d, COUNT(*) AS n
FROM orders
GROUP BY user_id, DATE(created_at);

 

DISTINCT w agregatach

Możesz liczyć/sumować unikalne wartości w grupie, np. ile różnych dat zamówień ma user.

SELECT user_id, COUNT(DISTINCT DATE(created_at)) AS ile_dni_z_zamowieniami
FROM orders
GROUP BY user_id;

 

WITH ROLLUP (sumy częściowe i suma końcowa)

Daje wiersze „podsumowań” (subtotal/total). W kolumnach grupujących pojawiają się NULL.

SELECT user_id, COUNT(*) AS n, ROUND(SUM(total),2) AS suma
FROM orders
GROUP BY user_id WITH ROLLUP;

 

Sortowanie wyników grup

Sortujesz normalnie ORDER BY (po kolumnie grupującej albo agregacie).

SELECT user_id, ROUND(SUM(total),2) AS suma
FROM orders
GROUP BY user_id
ORDER BY suma DESC;

 

Typowe schematy z GROUP BY

„Policz ile … na …”

„Suma / średnia / min / max … na …”

„Pokaż tylko te grupy, gdzie COUNT/SUM/AVG …”

„Grupuj po dacie (DATE(created_at))”

„Sklej listę wartości w grupie (GROUP_CONCAT)”

„Zrób sumy końcowe (WITH ROLLUP)”

baza do ćwiczeń –pobierz plik txt

per – oznacza dla każdego / w podziale na

Ćwiczenia:

  1. Policz liczbę zamówień dla każdego user_id.
  2. Policz sumę wartości zamówień dla każdego user_id (zaokrąglij do 2 miejsc po przecinku).
  3. Policz średnią wartość zamówienia dla każdego user_id (zaokrąglij do 2 miejsc po przecinku).
  4. Dla każdego user_id podaj min i max total.
  5. Dla każdego user_id policz ile ma zamówień >= 10 (użyj WHERE).
  6. Pokaż tylko tych userów i liczbę ich zamówień, którzy mają co najmniej 2 zamówienia (tabela orders, wykorzystaj HAVING).
  7. Pokaż tylko tych userów i liczbę ich zamówień, którzy mają  tylko 1 zamówienie (tabela orders, wykorzystaj HAVING)
  8. Pokaż tylko tych userów, których suma zamówień > 30.
  9. Pokaż user_id i liczbę różnych dni, w których składali zamówienia.
  10. Policz liczbę zamówień per data (bez usera).
  11. Policz sumę total per data.
  12. Policz zamówienia per user per data.
  13. Pokaż per user per data: suma total (2 miejsca).
  14. Pokaż per user: GROUP_CONCAT wartości total (posortowane rosnąco).
  15. Pokaż per user: ile total jest NULL (u nas raczej nie ma, ale ćwiczenie: COUNT(*)-COUNT(total)).
  16. Pokaż user_id i sumę, posortuj malejąco po sumie.
  17. Pokaż top 2 userów po sumie zamówień (LIMIT 2).
  18. Pokaż user_id, średnią zamówień jako srednia_zamowien zaokrąglone do 2 miejsc po przecinku, ale tylko dla zamówień z lutego 2026.
  19. Pokaż user_id, sumę zamówień jako suma_zamowien zaokrągloną do 2 miejsc po przecinku, ale tylko dla zamówień złożonych przed 20 marca 2026.
  20. Pokaż per user: liczba zamówień z marca 2026.
  21. Pokaż per user: suma zamówień z marca 2026.
  22. Pokaż per user: max total, ale tylko grupy gdzie max >= 50.
  23. Produkty: policz ile produktów ma dany vat_rate.
  24. Produkty: suma price_net per vat_rate.
  25. Produkty: średnia price_net per vat_rate.
  26. Produkty: min/max price_net per vat_rate.
  27. Produkty: policz ile produktów ma cenę netto >= 10 (WHERE) per vat_rate.
  28. Produkty: policz sumę brutto per vat_rate (price_net*(1+vat_rate)).
  29. Produkty: tylko te stawki VAT, gdzie suma brutto > 30 (HAVING).
  30. Produkty: grupuj po dacie dodania (DATE(created_at)) i policz ile produktów dodano dziennie.
  31. Produkty: grupuj po miesiącu (YYYY-MM) i policz liczbę produktów.
  32. Produkty: GROUP_CONCAT nazw produktów per vat_rate.
  33. Users: policz ilu userów ma active=1 i active=0 (GROUP BY active).
  34. Users: policz ilu userów ma NULL w age (grupuj po IF(age IS NULL,’brak’,’jest’)).
  35. Users: policz ilu userów ma jaką długość imienia (CHAR_LENGTH(first_name)).
  36. Users: policz ilu userów ma jaką pierwszą literę emaila (LOWER(SUBSTRING(email,1,1))).
  37. Users: policz ilu userów ma jaki “display_name” (COALESCE(nickname, first_name)).
  38. Users: policz ilu userów ma jaki “phone_or_brak” (IFNULL(phone,’brak’)).
  39. Users: grupuj po dacie utworzenia konta i policz rejestracje dziennie.
  40. Users: grupuj po miesiącu rejestracji (YYYY-MM) i policz ile kont/miesiąc.
  41. Users: policz ilu userów jest adult/minor/unknown (CASE + GROUP BY).
  42. Users: tylko te grupy age_type, gdzie COUNT(*) >= 2 (HAVING).
  43. Employees: policz ilu pracowników ma/nie ma telefonu (IF(phone IS NULL,…)).
  44. Employees: średnia pensja (AVG) per “tel” (ma/nie ma).
  45. Employees: minimalna i maksymalna pensja.
  46. Employees: grupuj po dacie zatrudnienia (DATE(created_at)) i policz ilu zatrudniono dziennie.
  47. Employees: grupuj po miesiącu (YYYY-MM) i policz ilu zatrudniono.
  48. Tabela t: policz ile rekordów ma każdy status.
  49. Tabela t: policz statusy, ale pusty string potraktuj jako 'pusty’ (IF(status=”,’pusty’,status)).
  50. Tabela t: policz parzyste i nieparzyste id (CASE + MOD).
  51. Orders: zrób ROLLUP (sumy per user i suma końcowa).
  52. Products: ROLLUP per vat_rate (liczba + suma netto).
Odpowiedzi
  1. SELECT user_id, COUNT(*) AS n FROM orders GROUP BY user_id;
  2. SELECT user_id, ROUND(SUM(total), 2) AS suma FROM orders GROUP BY user_id;
  3. SELECT user_id, ROUND(AVG(total), 2) AS avg_total FROM orders GROUP BY user_id;
  4. SELECT user_id, MIN(total) AS min_total, MAX(total) AS max_total FROM orders GROUP BY user_id;
  5. SELECT user_id, COUNT(*) AS n_ge_10 FROM orders WHERE total >= 10 GROUP BY user_id;
  6. SELECT user_id, COUNT(*) AS n FROM orders GROUP BY user_id HAVING COUNT(*) >= 2;
  7. SELECT user_id, COUNT(*) AS n FROM orders GROUP BY user_id HAVING COUNT(*)1;
  8. SELECT user_id, ROUND(SUM(total), 2) AS suma FROM orders GROUP BY user_id HAVING SUM(total) > 30;
  9. SELECT user_id, COUNT(DISTINCT DATE(created_at)) AS dni FROM orders GROUP BY user_id;
  10. SELECT DATE(created_at) AS d, COUNT(*) AS n FROM orders GROUP BY DATE(created_at);
  11. SELECT DATE(created_at) AS d, ROUND(SUM(total), 2) AS suma FROM orders GROUP BY DATE(created_at);
  12. SELECT user_id, DATE(created_at) AS d, COUNT(*) AS n FROM orders GROUP BY user_id, DATE(created_at);
  13. SELECT user_id, DATE(created_at) AS d, ROUND(SUM(total), 2) AS suma FROM orders GROUP BY user_id, DATE(created_at);
  14. SELECT user_id, GROUP_CONCAT(ROUND(total,2) ORDER BY total SEPARATOR ’, ’) AS totals FROM orders GROUP BY user_id;
  15. SELECT user_id, (COUNT(*) – COUNT(total)) AS null_totals FROM orders GROUP BY user_id;
  16. SELECT user_id, ROUND(SUM(total), 2) AS suma FROM orders GROUP BY user_id ORDER BY suma DESC;
  17. SELECT user_id, ROUND(SUM(total), 2) AS suma FROM orders GROUP BY user_id ORDER BY suma DESC LIMIT 2;
  18. SELECT user_id, ROUND(AVG(total), 2) AS srednia_zamowien FROM orders WHERE created_at >= ’2026-02-01′ AND created_at < ’2026-03-01′ GROUP BY user_id;
  19. SELECT user_id, ROUND(SUM(total), 2) AS suma_zamowien FROM orders WHERE created_at < ’2026-03-20′ GROUP BY user_id;
  20. SELECT user_id, COUNT(*) AS n FROM orders WHERE created_at >= '2026-03-01′ AND created_at < '2026-04-01′ GROUP BY user_id;
  21. SELECT user_id, ROUND(SUM(total), 2) AS suma FROM orders WHERE created_at >= '2026-03-01′ AND created_at < '2026-04-01′ GROUP BY user_id;
  22. SELECT user_id, MAX(total) AS max_total FROM orders GROUP BY user_id HAVING MAX(total) >= 50;
  23. SELECT vat_rate, COUNT(*) AS n FROM products GROUP BY vat_rate;
  24. SELECT vat_rate, ROUND(SUM(price_net), 2) AS suma_net FROM products GROUP BY vat_rate;
  25. SELECT vat_rate, ROUND(AVG(price_net), 2) AS avg_net FROM products GROUP BY vat_rate;
  26. SELECT vat_rate, MIN(price_net) AS min_net, MAX(price_net) AS max_net FROM products GROUP BY vat_rate;
  27. SELECT vat_rate, COUNT(*) AS n_ge_10 FROM products WHERE price_net >= 10 GROUP BY vat_rate;
  28. SELECT vat_rate, ROUND(SUM(price_net * (1 + vat_rate)), 2) AS suma_gross FROM products GROUP BY vat_rate;
  29. SELECT vat_rate, ROUND(SUM(price_net * (1 + vat_rate)), 2) AS suma_gross FROM products GROUP BY vat_rate HAVING SUM(price_net * (1 + vat_rate)) > 30;
  30. SELECT DATE(created_at) AS d, COUNT(*) AS n FROM products GROUP BY DATE(created_at);
  31. SELECT DATE_FORMAT(created_at, '%Y-%m’) AS ym, COUNT(*) AS n FROM products GROUP BY DATE_FORMAT(created_at, '%Y-%m’);
  32. SELECT vat_rate, GROUP_CONCAT(name ORDER BY name SEPARATOR ’, ’) AS nazwy FROM products GROUP BY vat_rate;
  33. SELECT active, COUNT(*) AS n FROM users GROUP BY active;
  34. SELECT IF(age IS NULL, 'brak’, 'jest’) AS age_info, COUNT(*) AS n FROM users GROUP BY IF(age IS NULL, 'brak’, 'jest’);
  35. SELECT CHAR_LENGTH(first_name) AS len_first, COUNT(*) AS n FROM users GROUP BY HAR_LENGTH(first_name) ORDER BY len_first;
  36. SELECT LOWER(SUBSTRING(email, 1, 1)) AS first_letter, COUNT(*) AS n FROM users GROUP BY LOWER(SUBSTRING(email, 1, 1)) ORDER BY first_letter;
  37. SELECT COALESCE(nickname, first_name) AS display_name, COUNT(*) AS n FROM users GROUP BY COALESCE(nickname, first_name) ORDER BY n DESC;
  38. SELECT IFNULL(phone, 'brak’) AS phone_or_brak, COUNT(*) AS n FROM users GROUP BY IFNULL(phone, 'brak’) ORDER BY n DESC;
  39. SELECT DATE(created_at) AS d, COUNT(*) AS n FROM users GROUP BY DATE(created_at) ORDER BY d;
  40. SELECT DATE_FORMAT(created_at, '%Y-%m’) AS ym, COUNT(*) AS n FROM users GROUP BY DATE_FORMAT(created_at, '%Y-%m’) ORDER BY ym;
  41. SELECT CASE     WHEN age IS NULL THEN 'unknown’ WHEN age >= 18 THEN 'adult’ ELSE 'minor’ END AS age_type, COUNT(*) AS n FROM users GROUP BY CASE WHEN age IS NULL THEN 'unknown’ WHEN age >= 18 THEN 'adult’ ELSE 'minor’ END;
  42. SELECT CASE WHEN age IS NULL THEN 'unknown’ WHEN age >= 18 THEN 'adult’ ELSE 'minor’ END AS age_type, COUNT(*) AS n FROM users GROUP BY CASE WHEN age IS NULL THEN 'unknown’ WHEN age >= 18 THEN 'adult’ ELSE 'minor’ END HAVING COUNT(*) >= 2;
  43. SELECT IF(phone IS NULL, 'brak’, 'jest’) AS tel, COUNT(*) AS n FROM employees GROUP BY IF(phone IS NULL, 'brak’, 'jest’);
  44. SELECT IF(phone IS NULL, 'brak’, 'jest’) AS tel, ROUND(AVG(salary), 2) AS avg_salary FROM employees GROUP BY IF(phone IS NULL, 'brak’, 'jest’);
  45. SELECT MIN(salary) AS min_salary, MAX(salary) AS max_salary FROM employees;
  46. SELECT DATE(created_at) AS d, COUNT(*) AS n FROM employees GROUP BY DATE(created_at);
  47. SELECT DATE_FORMAT(created_at, '%Y-%m’) AS ym, COUNT(*) AS n FROM employees GROUP BY DATE_FORMAT(created_at, '%Y-%m’);
  48. SELECT status, COUNT(*) AS n FROM t GROUP BY status;
  49. SELECT IF(status = ”, 'pusty’, status) AS status2, COUNT(*) AS n FROM t GROUP BY IF(status = ”, 'pusty’, status);
  50. SELECT CASE WHEN MOD(id,2)=0 THEN 'parzyste’ ELSE 'nieparzyste’ END AS parzystosc, COUNT(*) AS n FROM t GROUP BY CASE WHEN MOD(id,2)=0 THEN 'parzyste’ ELSE 'nieparzyste’ END;
  51. SELECT user_id, COUNT(*) AS n, ROUND(SUM(total), 2) AS suma FROM orders GROUP BY user_id WITH ROLLUP;
  52. SELECT vat_rate, COUNT(*) AS n, ROUND(SUM(price_net), 2) AS suma_net FROM products GROUP BY vat_rate WITH ROLLUP;