• Bazy danych
  • COUNT w SQL - wiersze, NULL i pułapki JOIN

COUNT w SQL - wiersze, NULL i pułapki JOIN

Radosław Stępień • 31 października 2026
Znajdź rekordy bez powiązań w bazie danych za pomocą SQL. Tabela płatności pokazuje rekordy A-777, których nie ma w tabeli kont.

Spis treści

Raport pokazuje 240 zamówień, choć część z nich nie ma przypisanego klienta. Jeśli używasz w SQL funkcji COUNT, wynik zależy od tego, czy liczysz wiersze, czy niepuste wartości w konkretnej kolumnie, a po złączeniu tabel dochodzi jeszcze jeden haczyk.

Złote figury geometryczne (sześciany, kule, stożki, walce) poruszają się po taśmach. System sortuje je, jakby wykonywał skomplikowany `count sql`.

Poprawny COUNT zaczyna się od ustalenia, co ma być policzone

  • COUNT(*) zlicza wiersze w wyniku zapytania.
  • COUNT(kolumna) pomija wiersze, w których kolumna ma wartość NULL.
  • COUNT(DISTINCT kolumna) zlicza unikalne, niepuste wartości.
  • LEFT JOIN może zwrócić zero dla niepasującego rekordu po prawej stronie, jeśli liczysz jego kolumnę.
  • COUNT(*) OVER (...) podaje licznik przy każdym wierszu, zamiast scalać wynik w jeden wiersz.

COUNT(*) liczy wiersze, a COUNT(kolumna) pomija NULL

W tabeli zamówień każde zamówienie powinno zwiększać licznik, nawet jeśli pole customer_id jest puste. Do takiego pytania służy COUNT(*); zapis z nazwą kolumny odpowiada na inne pytanie.

SELECT
  COUNT(*) AS liczba_zamowien,
  COUNT(customer_id) AS zamowienia_z_klientem
FROM orders;

Jeśli tabela zawiera 240 wierszy, a w 12 z nich customer_id ma wartość NULL, pierwsza agregacja zwróci 240, a druga 228. Dokumentacja PostgreSQL opisuje tę samą zasadę: COUNT(*) zlicza wiersze wejściowe, zaś COUNT(expression) te, w których wyrażenie nie jest puste.

Nie podstawiaj nazwy kolumny tylko dlatego, że wygląda na identyfikator rekordu. Jeśli identyfikator może być pusty, wynik będzie niższy od liczby wierszy. Przy kolumnie z ograniczeniem NOT NULL wyniki mogą się zrównać, ale intencja zapytania nadal jest czytelniejsza przy COUNT(*).

LEFT JOIN potrafi zaniżyć liczbę rekordów albo zawyżyć liczbę zamówień

Przy złączeniu tabel wynik nie zawsze odpowiada liczbie rekordów z tabeli głównej. LEFT JOIN zachowuje zamówienie bez pasującego klienta, ale kolumny klienta w tym wierszu mają NULL, więc COUNT(customers.id) go nie uwzględni.

SELECT
  COUNT(*) AS wszystkie_zamowienia,
  COUNT(customers.id) AS zamowienia_z_dopasowanym_klientem
FROM orders
LEFT JOIN customers
  ON customers.id = orders.customer_id;

Jest też pułapka w drugą stronę. Jeśli jedno zamówienie ma kilka pozycji, złączenie z tabelą pozycji powieli wiersz zamówienia; wtedy COUNT(*) policzy pozycje, a nie zamówienia.

Sytuacja Co może oznaczać wynik Co zrobić
COUNT(p.id) po LEFT JOIN daje mniej niż COUNT(*) Brak dopasowanego rekordu lub wartość NULL w liczonej kolumnie. Wybierz COUNT(*), jeśli liczysz rekordy z tabeli głównej.
Wynik rośnie po dołączeniu tabeli szczegółów Jeden rekord główny łączy się z wieloma rekordami szczegółowymi. Policz unikalny identyfikator główny: COUNT(DISTINCT orders.id).
Potrzebujesz liczby powiązanych pozycji dla każdego zamówienia Liczone mają być wiersze tabeli szczegółów. Grupuj po zamówieniu i zliczaj identyfikator pozycji.

Przykład: zamówienie o identyfikatorze 501 ma trzy pozycje. Po złączeniu pojawią się trzy wiersze z tym samym ID, zatem zwykły licznik zwróci 3 dla tego zamówienia, a COUNT(DISTINCT orders.id) zachowa wynik 1 dla całego zestawu. Uważam, że przy raportach obejmujących kilka tabel najpierw trzeba nazwać liczony obiekt, a dopiero potem wybierać wariant funkcji.

GROUP BY daje osobny licznik dla każdej grupy

Gdy potrzebujesz liczby zamówień na klienta, GROUP BY dzieli wiersze na grupy, a agregacja liczy każdą z nich osobno. To zmienia kształt odpowiedzi: zamiast jednego wyniku dostajesz wiersz dla każdej wartości grupującej.

SELECT customer_id, COUNT(*) AS liczba_zamowien
FROM orders
GROUP BY customer_id
ORDER BY liczba_zamowien DESC;

Klienci bez zamówień nie pojawią się w takim wyniku, bo zapytanie zaczyna od tabeli orders. Jeśli raport ma pokazać również klientów z zerową liczbą zamówień, zacznij od tabeli klientów i użyj LEFT JOIN; wtedy zliczaj identyfikator zamówienia, nie *.

SELECT c.id, COUNT(o.id) AS liczba_zamowien
FROM customers AS c
LEFT JOIN orders AS o ON o.customer_id = c.id
GROUP BY c.id;

Dla klienta bez zamówień złączenie tworzy jeden wiersz z pustymi polami tabeli orders. Dlatego COUNT(o.id) zwraca zero, a COUNT(*) zwróciłby jeden. Ta drobna różnica często decyduje o tym, czy zestawienie pokazuje prawdziwe zera.

COUNT(DISTINCT) usuwa duplikaty, ale nie naprawia każdego złączenia

Raport z tabeli połączonej z pozycjami potrafi policzyć klienta wielokrotnie. COUNT(DISTINCT customer_id) zlicza unikalne, niepuste identyfikatory, więc bywa dobrym wyborem przy pytaniu „ilu różnych klientów złożyło zamówienie?”.

SELECT COUNT(DISTINCT customer_id) AS aktywni_klienci
FROM orders
WHERE status = 'paid';

Jednak DISTINCT nie jest plastrem na każdą niepoprawną agregację. Jeśli chcesz zsumować wartość zamówień po złączeniu z kilkoma tabelami szczegółowymi, deduplikowanie sumy może ukryć problem zamiast go rozwiązać. W takiej sytuacji policz lub zagreguj tabele szczegółowe osobno, a potem połącz gotowe wyniki.

Jeśli potrzebujesz tylko licznika przy każdym wierszu, bez zwijania szczegółów, użyj funkcji okna. Na przykład COUNT(*) OVER (PARTITION BY customer_id) dopisze liczbę zamówień klienta do każdego wiersza, zachowując szczegółowe rekordy. Obsługa funkcji agregujących jako okiennych zależy od systemu bazodanowego i składni konkretnego silnika, więc sprawdź dokumentację używanej bazy.

COUNT(1) nie jest magicznym sposobem na szybsze liczenie

W sieci wraca rada, by zamienić COUNT(*) na COUNT(1), bo gwiazdka rzekomo każe bazie odczytać wszystkie kolumny. To mylący skrót myślowy: COUNT(*) oznacza liczenie wierszy, a nie zwracanie ich wszystkich. Nie zakładaj, że inny zapis przyspieszy zapytanie; plan wykonania i zachowanie optymalizatora zależą od silnika oraz warunków zapytania.

Dokładne policzenie całej tabeli może wymagać pracy proporcjonalnej do jej rozmiaru. Dokumentacja MySQL 8.4 wyjaśnia, że w InnoDB wynik zależy od wierszy widocznych dla bieżącej transakcji, a silnik odczytuje dostępny indeks wtórny albo indeks klastrowany. Dla dużej tabeli regularne odpytywanie o dokładny licznik może więc kosztować więcej, niż sugeruje krótki zapis SQL.

Jeśli działasz na SQL Serverze i licznik może przekroczyć zakres typu int, sprawdź typ wyniku. Według dokumentacji Microsoftu dla COUNT_BIG, COUNT zwraca int, a COUNT_BIG zwraca bigint. W aplikacji raportującej miliardy zdarzeń wybór COUNT_BIG(*) usuwa ryzyko przepełnienia po stronie tej funkcji.

Najpierw ustal, co ma oznaczać liczba

Przed uruchomieniem zapytania dokończ zdanie: „chcę policzyć liczbę…”. Jeśli odpowiedzią są wiersze, zacznij od COUNT(*); jeśli niepuste pola, użyj kolumny; jeśli różne osoby lub obiekty, rozważ DISTINCT. Przy złączeniach sprawdź, czy jeden rekord źródłowy nie rozbija się na kilka wynikowych.

To proste doprecyzowanie oszczędza poprawiania raportu po fakcie. Licznik w SQL jest krótki, ale jego znaczenie ustala wybór wiersza, kolumny, grupy i relacji między tabelami.

FAQ - Najczęstsze pytania

COUNT(*) zlicza wszystkie wiersze, natomiast COUNT(customer_id) pomija te, w których customer_id ma wartość NULL. Jeśli tabela zawiera 240 zamówień, a 12 z nich nie ma klienta, wyniki wyniosą odpowiednio 240 i 228.

Po LEFT JOIN COUNT(*) uwzględni także zamówienia bez dopasowanego klienta, ale COUNT(customers.id) je pominie, ponieważ kolumna klienta będzie miała wartość NULL. Jeśli złączenie z tabelą pozycji powiela zamówienie na kilka wierszy, użyj COUNT(DISTINCT orders.id), aby policzyć każde zamówienie tylko raz.

Zacznij zapytanie od tabeli customers, użyj LEFT JOIN z tabelą orders i grupuj po identyfikatorze klienta. Zliczaj COUNT(o.id), ponieważ dla klienta bez zamówień zwróci ono zero, podczas gdy COUNT(*) zwróciłby jeden.

Użyj COUNT(DISTINCT customer_id), gdy chcesz policzyć różne, niepuste identyfikatory, na przykład liczbę klientów, którzy złożyli opłacone zamówienie. DISTINCT pomaga usunąć powtórzenia klientów po złączeniu, ale nie naprawia błędnych sum wynikających z połączenia kilku tabel szczegółowych.

Nie należy zakładać, że COUNT(1) przyspieszy zapytanie. COUNT(*) oznacza liczenie wierszy, a rzeczywisty koszt zależy między innymi od silnika bazy, planu wykonania i rozmiaru tabeli.

Oceń artykuł

Ocena: 4.50 Liczba głosów: 6

Tagi

count
złączenia
null
grupowanie
funkcje okna
Autor Radosław Stępień
Radosław Stępień
Dziedzina programowania i technologii rozwija się w zawrotnym tempie, co sprawia, że ciągłe śledzenie nowinek i głębokie zrozumienie procesów są kluczowe. Nazywam się Radosław Stępień i od 13 lat aktywnie działam w branży IT, dzieląc się swoją wiedzą i doświadczeniem. Moje zainteresowania skupiają się na praktycznych aspektach tworzenia oprogramowania, analizie trendów technologicznych oraz budowaniu satysfakcjonującej ścieżki kariery w tym dynamicznym sektorze. Staram się przedstawiać złożone zagadnienia w sposób przystępny, opierając się na sprawdzonych informacjach i własnych obserwacjach z wieloletniej pracy. Zależy mi na tym, aby treści, które tworzę, były nie tylko merytoryczne i aktualne, ale przede wszystkim pomocne dla każdego, kto chce zgłębić tajniki IT lub rozwijać się zawodowo.

Udostępnij artykuł

Napisz komentarz