Raport klienta ma pokazać numer telefonu, ale część rekordów zawiera `NULL` w kolumnie z numerem firmowym. W takim przypadku `COALESCE` zwróci pierwszą wartość, która nie jest `NULL`, choć przy wyrażeniach zależnych od danych wynik może zaskoczyć.
COALESCE wybiera wartość według kolejności argumentów
- Zwraca pierwszy argument różny od `NULL`, a gdy wszystkie są `NULL`, wynikiem też jest `NULL`.
- Kolejność ma znaczenie: `COALESCE(telefon_firmowy, telefon_prywatny, 'brak')` daje pierwszeństwo numerowi firmowemu.
- Pusty tekst i zero nie są `NULL`, więc funkcja ich nie zastąpi.
- W SQL Server typ wyniku zależy od hierarchii typów, a podzapytanie w argumentach może zostać wykonane więcej niż raz.
COALESCE wybiera pierwszą nie-NULL-ową wartość z listy
Gdy kilka kolumn może zawierać tę samą informację, ale tylko jedna jest wypełniona, funkcja pozwala określić kolejność źródeł zastępczych. Przykładowo w tabeli klientów numer służbowy może mieć pierwszeństwo przed prywatnym.
SELECT
COALESCE(telefon_firmowy, telefon_prywatny, 'brak telefonu') AS telefon
FROM Klienci;
Jeśli `telefon_firmowy` ma wartość, zostanie zwrócona właśnie ona. Gdy jest `NULL`, SQL sprawdzi następną kolumnę, a tekst `brak telefonu` trafi do wyniku dopiero wtedy, gdy oba numery będą `NULL`.
Podobnie można ustawić wartość domyślną dla danych prezentowanych w raporcie: `COALESCE(opis, opis_skrocony, 'Brak opisu')`. Prawdę mówiąc, sama zwięzłość nie powinna przesłonić pytania, czy pusta wartość faktycznie oznacza brak danych.
Reguła zastępowania zależy od tego, co naprawdę oznacza NULL
Najłatwiej wpaść w pułapkę, gdy `NULL` potraktuje się jak uniwersalne „brak”. Pusty tekst może oznaczać wprowadzoną, lecz pustą wartość, a zero może być prawidłowym wynikiem, na przykład saldem po rozliczeniu.
`COALESCE('', 'domyślny tekst')` zwróci pusty tekst, a `COALESCE(0, 100)` zwróci zero. Jeśli pusty ciąg również ma uruchamiać wartość domyślną, trzeba najpierw zamienić go na `NULL`, na przykład za pomocą `NULLIF`:
SELECT COALESCE(NULLIF(nazwa, ''), 'Brak nazwy') AS nazwa
FROM Produkty;
Przed dodaniem funkcji ustal więc regułę biznesową: czy zastępujesz wyłącznie brak wartości w bazie, czy również puste dane i określone wartości liczbowe. Te przypadki wymagają innych warunków, nawet jeśli w raporcie mają wyglądać podobnie.
W SQL Server COALESCE i ISNULL mogą dać inny typ wyniku
Dwa zapytania mogą wyświetlić podobny tekst, a mimo to zwrócić wynik o innym typie. W SQL Server `COALESCE` wybiera typ o najwyższym priorytecie spośród argumentów, natomiast `ISNULL` przyjmuje typ pierwszego argumentu; opisuje to dokumentacja Microsoft Learn.
DECLARE @kod varchar(10) = NULL;
SELECT COALESCE(@kod, N'brak') AS wynik_coalesce,
ISNULL(@kod, N'brak') AS wynik_isnull;
W tym przykładzie `COALESCE` może zwrócić typ `nvarchar`, bo tekst Unicode ma wyższy priorytet niż `varchar`. `ISNULL` opiera typ wyniku na pierwszym argumencie, więc warto sprawdzić, czy tekst zastępczy nie zostanie obcięty podczas konwersji.
Gdy typ ma być jednoznaczny, jawnie rzutuj argumenty do docelowego formatu, np. `COALESCE(CAST(@kod AS nvarchar(30)), N'brak')`. A jeśli przenosisz zapytanie między silnikami, nie zakładaj, że `ISNULL` znaczy to samo wszędzie: w MySQL nazwa ta służy do sprawdzania, czy argument jest `NULL`, a do podmiany wartości używa się `IFNULL` albo przenośnego `COALESCE`.
Podzapytanie w COALESCE może zostać wykonane więcej niż raz
Prosty przykład z kolumnami nie zdradza istotnego wyjątku. W SQL Server `COALESCE` jest przekształcane do konstrukcji podobnej do `CASE`, dlatego wyrażenie zawierające podzapytanie może zostać ocenione ponownie; Microsoft Learn zwraca uwagę, że przy `READ COMMITTED` odczyty w środowisku wieloużytkownikowym mogą wtedy dać różne wyniki.
SELECT COALESCE(
(SELECT Status
FROM Zamowienia
WHERE Id = @id),
'nieznany'
);
Gdy podzapytanie zależy od danych, które równolegle mogą się zmienić, nie traktuj go jak jednorazowego odczytu. Możesz wyciągnąć je do podzapytania w `FROM`, użyć odpowiedniego poziomu izolacji albo rozważyć `ISNULL`, jeśli zapytanie jest przeznaczone wyłącznie dla SQL Server i jego różnice w typowaniu oraz obsłudze `NULL` pasują do przypadku.
Dokumentacja PostgreSQL opisuje pomijanie argumentów po znalezieniu pierwszej nie-`NULL`-owej wartości. Ta intuicja dobrze opisuje typowe użycie, ale nie powinna zastępować sprawdzenia zachowania konkretnego silnika, zwłaszcza gdy argumentem jest kosztowne lub zmienne podzapytanie.
COALESCE w WHERE może zmienić sens filtra
Umieszczenie `COALESCE` w warunku bywa krótsze, ale warto sprawdzić, czy podstawiona wartość nie zmieni zestawu wyników ani sposobu użycia indeksu. Gdy szukasz rekordu o konkretnym statusie albo porównujesz kolumnę z wartością domyślną, funkcja może zlać dwa różne przypadki w jeden.
| Sytuacja | Możliwy skutek | Co zrobić |
|---|---|---|
| Filtr `WHERE COALESCE(status, 'nowy') = 'nowy'` | Pasują zarówno rekordy z `status = 'nowy'`, jak i te z `status IS NULL`. | Jeśli brak statusu ma być osobnym przypadkiem, użyj jawnego warunku `status = 'nowy' OR status IS NULL` albo rozdziel logikę. |
| W argumencie jest podzapytanie zależne od zmiennych danych | W SQL Server ponowna ewaluacja może odczytać inny stan danych. | Przenieś podzapytanie do osobnego poziomu zapytania lub dobierz izolację odpowiednią do wymaganego odczytu. |
| Argumenty mają różne typy tekstowe lub liczbowe | Silnik może skonwertować wynik do typu o wyższym priorytecie, a w innych układach konwersja może zakończyć się błędem. | Jawnie rzutuj wartości do oczekiwanego typu przed wywołaniem funkcji. |
| Brak danych ma być odróżniony od pustego tekstu lub zera | `COALESCE` zachowa pusty tekst i zero jako prawidłowe wartości. | Dodaj warunek, np. `NULLIF(kolumna, '')`, jeśli pusty tekst ma być traktowany jak brak. |
Przy filtrach wybór jest prosty: jeśli brak wartości powinien zachowywać się identycznie jak domyślny status, `COALESCE` może oddać tę regułę zwięźle. Jeśli oba stany mają prowadzić do różnych decyzji, zapisz je osobno, bo skrót ukryje różnicę.
Zostaw COALESCE tam, gdzie kolejność ma sens
Najczytelniejsze zastosowanie tej funkcji to uporządkowana lista równorzędnych źródeł, na przykład kilka numerów kontaktowych z jasno ustalonym priorytetem. Wtedy intencja jest widoczna od razu, a wartość końcowa nie zależy od przypadkowego sortowania rekordów.
Uważam, że przed użyciem `COALESCE` w filtrze lub przy podzapytaniu lepiej rozpisać sobie znaczenie każdego argumentu: co ma pierwszeństwo, co oznacza `NULL` i jaki typ ma wyjść. Jeśli odpowiedź na te trzy pytania jest jasna, funkcja zwykle pozostaje prostym narzędziem zamiast maskować niejednoznaczność danych.
