Wartości NULL w agregatach, złączeniach i DISTINCT
Różne zachowanie NULL podczas grupowania, łączenia i ustalania unikatowości
Wartości NULL w agregatach, złączeniach i DISTINCT to bezpłatna lekcja SQL Interview Prep na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej SQL Interview Prep, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs SQL Interview Prep zawiera 4 lekcji w sumie.
NULL w trzech zaskakujących miejscach
NULL nie zachowuje się wszędzie tak samo. Ostatnia lekcja obejmuje trzy konteksty, w których jego zachowanie najbardziej zaskakuje kandydatów: funkcje agregujące, złączenia oraz DISTINCT / GROUP BY.
Powtarzający się schemat jest taki, że funkcje agregujące i filtrowanie traktują NULL jako „pomiń mnie”, natomiast grupowanie i DISTINCT traktują NULL jako „wartość równą innym wartościom NULL”. Ta niespójność jest dokładnie tym, co sprawdzają rekruterzy.
Opanowanie tych zagadnień pozwala zamknąć temat najczęstszych pytań dotyczących NULL podczas rozmów technicznych z SQL.
Funkcje agregujące pomijają NULL
Najważniejsza zasada: funkcje agregujące pomijają wartości NULL. SUM, AVG, MIN, MAX i COUNT(column) całkowicie ignorują dane wejściowe NULL, zamiast traktować je jak zero.
Dlatego AVG może zwrócić inną liczbę, niż można się spodziewać. Dzieli sumę wartości różnych od NULL przez liczbę wartości różnych od NULL, a nie przez całkowitą liczbę wierszy.
-- bonus values: 100, 200, NULL
SELECT
SUM(bonus) AS total, -- 300 (NULL ignored)
AVG(bonus) AS average, -- 150 = 300 / 2, not / 3
COUNT(bonus) AS cnt -- 2 (NULL not counted)
FROM employees;COUNT(*) a COUNT(column)
To najczęściej zadawane pytanie dotyczące NULL w funkcjach agregujących. COUNT(*) zlicza wiersze, także te zawierające NULL. COUNT(column) zlicza tylko wiersze, w których dana kolumna jest różna od NULL.
Różnica między nimi jest więc dokładnie równa liczbie wartości NULL w tej kolumnie. COUNT(DISTINCT column) idzie o krok dalej: również ignoruje NULL i usuwa duplikaty.
SELECT
COUNT(*) AS rows_total, -- all rows
COUNT(bonus) AS non_null_bonus, -- excludes NULLs
COUNT(DISTINCT bonus) AS distinct_bonus, -- excludes NULLs + dups
COUNT(*) - COUNT(bonus) AS null_bonus
FROM employees;AVG a SUM/COUNT(*): klasyczna pułapka
Rekruterzy pytają: „Czy AVG(x) jest tym samym co SUM(x) / COUNT(*)?” Odpowiedź brzmi nie, gdy występują wartości NULL.
AVG(x) jest równe SUM(x) / COUNT(x), ponieważ dzieli przez liczbę wartości różnych od NULL. Dzielenie przez COUNT(*) zamiast tego traktuje wartości NULL tak, jakby były zerami, zaniżając średnią.
Jeśli rzeczywiście wartości NULL mają być liczone jako zero, należy wyraźnie określić to za pomocą COALESCE.
-- These differ when bonus has NULLs:
SELECT
AVG(bonus) AS avg_ignoring_nulls,
SUM(bonus) * 1.0 / COUNT(*) AS avg_nulls_as_zero,
AVG(COALESCE(bonus, 0)) AS explicit_nulls_as_zero
FROM employees;Przypadek brzegowy agregacji z samymi wartościami NULL
Co zwraca funkcja agregująca, gdy każde wejście ma wartość NULL albo nie ma żadnych wierszy? To precyzyjne rozróżnienie, które rekruterzy lubią sprawdzać:
SUM,AVG,MINiMAXdla samych wartości NULL (lub zerowej liczby wierszy) zwracają NULL.COUNTzawsze zwraca 0, nigdy NULL.
Jeśli w raporcie sumy są puste, prawdopodobną przyczyną jest SUM z samymi wartościami NULL. Należy opakować ją w COALESCE, aby wyświetlała 0.
-- No matching rows or all bonuses NULL:
SELECT SUM(bonus) FROM employees WHERE 1 = 0; -- NULL
SELECT COUNT(bonus) FROM employees WHERE 1 = 0; -- 0
-- Present a clean zero:
SELECT COALESCE(SUM(bonus), 0) FROM employees;NULL w warunkach JOIN
W klauzuli ON złączenia NULL = NULL nadal ma wartość UNKNOWN, dlatego klucze zawierające NULL nigdy nie pasują w złączeniu równościowym. Dwa wiersze, które mają NULL jako klucz złączenia, nie zostaną połączone.
To często zaskakuje przy złączaniu po opcjonalnych kluczach obcych. Jeśli zamierzone jest dopasowywanie NULL do NULL, potrzebny jest operator bezpieczny dla NULL (IS NOT DISTINCT FROM lub <=>) omówiony we wcześniejszej lekcji.
-- Rows with region IS NULL on both sides do NOT match
SELECT *
FROM a JOIN b ON a.region = b.region;
-- To match NULL-to-NULL (ANSI):
SELECT *
FROM a JOIN b ON a.region IS NOT DISTINCT FROM b.region;Wartości NULL tworzone przez złączenia zewnętrzne
Złączenia zewnętrzne generują wartości NULL dla niedopasowanych wierszy. Po wykonaniu LEFT JOIN każda kolumna po prawej stronie ma wartość NULL dla wierszy z lewej strony, dla których nie znaleziono dopasowania.
Na tym opiera się wzorzec anti-join: należy użyć filtra WHERE right_table.key IS NULL, aby znaleźć wiersze bez dopasowania, na przykład klientów bez zamówień.
Należy jednak zachować ostrożność: filtrowanie kolumny ze złączenia zewnętrznego w WHERE może przypadkowo zmienić je z powrotem w złączenie wewnętrzne — jest to temat następnej sceny.
-- Find customers who have never ordered (anti-join)
SELECT c.id, c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;Pułapka NULL przy WHERE w złączeniu zewnętrznym
To częsta pułapka. Wykonywane jest LEFT JOIN z tabelą zamówień, a następnie dodawane jest WHERE o.status = 'shipped'. Nagle klienci bez zamówień znikają, przez co złączenie zewnętrzne staje się w praktyce złączeniem wewnętrznym.
Dlaczego? W niedopasowanych wierszach o.status ma wartość NULL, a NULL = 'shipped' ma wartość UNKNOWN, więc WHERE odrzuca te wiersze. Aby zachować niedopasowane wiersze, należy przenieść warunek do klauzuli ON.
-- Accidental inner join: drops customers with no orders
SELECT c.name, o.status
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.status = 'shipped';
-- Correct: keep unmatched customers
SELECT c.name, o.status
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.id AND o.status = 'shipped';DISTINCT traktuje wszystkie wartości NULL jako równe
Oto niespójność, która zaskakuje wszystkich. Funkcje agregujące pomijają NULL, ale DISTINCT zachowuje dokładnie jedną wartość NULL, traktując wszystkie wartości NULL jako swoje duplikaty.
Dlatego SELECT DISTINCT bonus dla wartości 100, 100, NULL, NULL zwraca trzy wiersze: 100, NULL i nic więcej. Dwie wartości NULL zostają połączone w jedną, mimo że w innych miejscach NULL = NULL daje wynik UNKNOWN.
-- bonus: 100, 100, NULL, NULL, 200
SELECT DISTINCT bonus FROM employees;
-- Returns: 100, 200, NULL (the two NULLs become one row)GROUP BY łączy wartości NULL w jedną grupę
GROUP BY stosuje tę samą zasadę co DISTINCT: wszystkie klucze NULL są gromadzone w jednej grupie. Jest to przeciwieństwo logiki porównań, w której wartości NULL nigdy nie są sobie równe.
Grupowanie według kolumny dopuszczającej wartości NULL daje więc jeden wiersz reprezentujący wszystkie rekordy z kluczem NULL, co zazwyczaj jest pożądane w raportach. Warto wspomnieć o tym kontraście (grupowanie a porównywanie), aby pokazać pełne zrozumienie tematu.
-- All employees with NULL department form ONE group
SELECT department, COUNT(*) AS headcount
FROM employees
GROUP BY department;
-- A single row where department is NULL totals all of themNajważniejsze punkty na rozmowie kwalifikacyjnej
Podsumowanie, które robi wrażenie na osobach prowadzących rozmowę:
- Funkcje agregujące ignorują NULL; AVG dzieli przez COUNT(kolumny), a nie COUNT(*).
- COUNT(*) zlicza wiersze; COUNT(col) i COUNT(DISTINCT col) pomijają NULL.
- SUM/AVG/MIN/MAX dla braku wierszy zwracają NULL; COUNT zwraca 0.
- W złączeniach klucze NULL nigdy nie pasują do siebie; filtrowanie kolumny pochodzącej ze złączenia zewnętrznego w WHERE powoduje, że złączenie niejawnie staje się złączeniem wewnętrznym.
- DISTINCT i GROUP BY traktują wszystkie wartości NULL jako równe, czyli odwrotnie niż logika porównań.
Jedno zdanie, które warto zapamiętać: „NULL jest ignorowane podczas agregowania i porównywania, ale podczas usuwania duplikatów wartości NULL są grupowane razem”.
Szybkie sprawdzenie
Sprawdź kontrast między grupowaniem a agregowaniem.
Podsumowanie
Opanowali Państwo obsługę NULL na rozmowach kwalifikacyjnych:
- Funkcje agregujące pomijają NULL; AVG dzieli przez liczbę wartości innych niż NULL, a SUM dla samych wartości NULL zwraca NULL, podczas gdy COUNT zwraca 0.
COUNT(*)uwzględnia wiersze zawierające NULL;COUNT(col)ich nie uwzględnia, a różnica między tymi wynikami odpowiada liczbie wartości NULL.- Klucze złączeń będące NULL nigdy nie pasują do siebie; filtrowanie kolumn pochodzących ze złączenia zewnętrznego w WHERE może zmienić je w złączenie wewnętrzne.
- DISTINCT i GROUP BY łączą wszystkie wartości NULL w jedną grupę, odwrotnie niż logika porównań.
Proszę zapamiętać: NULL jest ignorowane podczas agregowania i porównywania, ale podczas usuwania duplikatów wartości NULL są grupowane razem. Ta jedna obserwacja pozwala odpowiedzieć na większość pytań o NULL podczas rozmów kwalifikacyjnych.
Często zadawane pytania
Czy lekcja „Wartości NULL w agregatach, złączeniach i DISTINCT” jest bezpłatna?
Tak — pełny tekst „Wartości NULL w agregatach, złączeniach i DISTINCT” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu SQL Interview Prep, przejdź na CoddyKit PRO. Kurs SQL Interview Prep zawiera 4 lekcji w sumie.
Co nauczysz się w „Wartości NULL w agregatach, złączeniach i DISTINCT”?
Różne zachowanie NULL podczas grupowania, łączenia i ustalania unikatowości Ćwiczysz SQL Interview Prep z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć SQL Interview Prep?
Nie wymagamy żadnego doświadczenia. SQL Interview Prep w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.
Ile czasu zajmuje lekcja „Wartości NULL w agregatach, złączeniach i DISTINCT”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji SQL Interview Prep?
Tak. Każda lekcja SQL Interview Prep zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Logika trójwartościowa i UNKNOWN
- IS NULL, IS NOT NULL i porównywanie bezpieczne dla NULL
- COALESCE, NULLIF i ISNULL
- Wartości NULL w agregatach, złączeniach i DISTINCT