Porównujesz raport sprzedaży z eksportem z systemu płatności i część zamówień znika, bo występuje tylko w jednym źródle. FULL OUTER JOIN zachowa wiersze z obu tabel, a brakujące dane uzupełni wartościami NULL, choć wynik może zaskoczyć, gdy klucz łączenia się powtarza.
Pełne złączenie zachowuje dopasowane i niedopasowane wiersze
- FULL OUTER JOIN zwraca dopasowania oraz wiersze bez pary z obu tabel.
- Powtórzone klucze mogą zwielokrotnić wynik, nawet gdy każda tabela ma niewiele rekordów.
- Warunek w WHERE może odrzucić wiersze z wartościami NULL i zmienić efekt złączenia.
- MySQL nie obsługuje składni FULL OUTER JOIN; da się ją odtworzyć przez LEFT JOIN i UNION ALL.
FULL OUTER JOIN zachowuje rekordy z obu stron
Wyobraź sobie dwa pliki z identyfikatorami zamówień: jeden z aplikacji sklepu, drugi od operatora płatności. FULL OUTER JOIN łączy rekordy, które spełniają warunek złączenia, ale pozostawia też te bez odpowiednika po dowolnej stronie. Brakujące kolumny z drugiej tabeli przyjmują wtedy wartość NULL.
W prostym przykładzie możesz zestawić zamówienia po ich identyfikatorze:
SELECT
COALESCE(s.order_id, p.order_id) AS order_id,
s.status AS shop_status,
p.status AS payment_status
FROM shop_orders AS s
FULL OUTER JOIN payment_orders AS p
ON s.order_id = p.order_id;COALESCE zwraca pierwszą wartość różną od NULL, więc kolumna order_id pozostaje widoczna również dla wiersza obecnego tylko w jednej tabeli. Według dokumentacji PostgreSQL pełne złączenie zwraca dopasowane wiersze oraz niedopasowane rekordy z lewej i prawej tabeli. Ten opis brzmi prosto, ale nie gwarantuje jednego wyniku na każdy identyfikator.
Powtórzony klucz może zamienić dwa rekordy w cztery
Przy złączeniu wiele-do-wielu każdy pasujący rekord z jednej tabeli łączy się z każdym pasującym rekordem z drugiej. Jeśli tabela sklepu zawiera dwa wpisy z identyfikatorem 42, a tabela płatności trzy, wynik dla tego identyfikatora będzie miał sześć wierszy, a nie jeden. To często wygląda jak duplikaty wygenerowane przez bazę, choć silnik poprawnie wykonał warunek.
Przed dodaniem agregacji sprawdź, czy kolumna łączenia jest rzeczywiście unikalna po obu stronach. Jeśli płatności mają kilka zdarzeń dla jednego zamówienia, zestawienie warto najpierw sprowadzić do poziomu zamówienia, na przykład wybierając ostatni status albo sumując kwoty. Inaczej suma z raportu może urosnąć przez kombinacje wierszy.
Moim zdaniem najbezpieczniej najpierw ustalić oczekiwaną ziarnistość wyniku: czy jeden wiersz ma oznaczać zamówienie, płatność, czy pojedyncze zdarzenie. Bez tej decyzji łatwo pomylić poprawne mnożenie rekordów z błędem w danych.
Warunek w WHERE potrafi ukryć wiersze bez pary
Załóżmy, że chcesz zachować wszystkie zamówienia, ale uwzględnić tylko płatności o statusie „rozliczona”. Umieszczenie filtra po stronie płatności w WHERE może usunąć wiersze, dla których płatności nie ma, ponieważ ich status jest NULL. W efekcie pełne złączenie przestaje w praktyce zachowywać część niedopasowanych rekordów.
Jeżeli filtr ma ograniczać dopasowywane płatności, przenieś go do warunku ON:
SELECT s.order_id, s.status, p.status
FROM shop_orders AS s
FULL OUTER JOIN payment_orders AS p
ON s.order_id = p.order_id
AND p.status = 'rozliczona';Przy takim zapisie wiersz płatności z innym statusem nie spełni warunku dopasowania, ale nadal może pojawić się jako rekord bez pary po prawej stronie. Jeśli filtr ma celowo odrzucić takie rekordy, WHERE jest właściwym miejscem. Różnica polega na tym, czy warunek definiuje parę, czy filtruje już gotowy wynik.
Objaw w wyniku podpowiada, gdzie szukać błędu
Pełne złączenie często służy do uzgadniania danych, ale samo pojawienie się NULL nie mówi jeszcze, czy rekord nie ma odpowiednika, czy brakowało wartości już w tabeli źródłowej. Tabela pomaga rozdzielić typowe przypadki:
| Objaw | Możliwa przyczyna | Co zrobić |
|---|---|---|
| Kolumny jednej tabeli są puste | Brak dopasowania albo klucz po tej stronie ma NULL | Dodaj wskaźnik obecności rekordu i sprawdź klucz w źródle |
| Jeden identyfikator występuje w wielu wierszach wyniku | Powtarzające się klucze po jednej lub obu stronach | Policz wystąpienia przed złączeniem i ustal poziom agregacji |
| Znikają rekordy bez odpowiednika | Warunek w WHERE odrzuca wiersze z NULL | Przenieś warunek dopasowania do ON albo zmień filtr |
| NULL trudno odróżnić od braku dopasowania | Kolumna biznesowa może sama zawierać NULL | Sprawdź osobną kolumnę oznaczającą obecność rekordu |
Najpewniejszy test to nie sprawdzanie dowolnej kolumny biznesowej, lecz stałego identyfikatora, który nie może być pusty, albo dodatkowej flagi obecności. Dokumentacja Microsoft Learn zwraca uwagę, że wartości NULL pochodzące z tabeli mogą być trudne do odróżnienia od tych wstawionych przez złączenie zewnętrzne. W praktyce właśnie dlatego jawny znacznik źródła ułatwia kontrolę raportu.
MySQL wymaga emulacji pełnego złączenia
Składnia z przykładu nie zadziała w każdym silniku. MySQL nie obsługuje FULL OUTER JOIN, co potwierdza jego dokumentacja klauzuli JOIN. W tym systemie można połączyć lewostronne złączenie z rekordami, które występują wyłącznie po prawej stronie:
SELECT
s.order_id,
s.status AS shop_status,
p.status AS payment_status
FROM shop_orders AS s
LEFT JOIN payment_orders AS p
ON s.order_id = p.order_id
UNION ALL
SELECT
p.order_id,
NULL AS shop_status,
p.status AS payment_status
FROM payment_orders AS p
LEFT JOIN shop_orders AS s
ON s.order_id = p.order_id
WHERE s.order_id IS NULL;Druga część wybiera tylko te rekordy płatności, dla których nie znaleziono zamówienia. UNION ALL zachowuje powtórzenia, co odpowiada zachowaniu złączenia, natomiast zwykłe UNION usuwa identyczne wiersze i może po cichu zmienić wynik. Warunek s.order_id IS NULL zakłada, że klucz zamówienia nie jest pusty w dopasowanym rekordzie; jeśli nie możesz tego zagwarantować, użyj innej kolumny, która identyfikuje obecność wiersza.
Wybierz złączenie według pytania, na które odpowiadasz
Jeśli analizujesz kompletność dwóch źródeł, pełne złączenie pokaże także braki po obu stronach. Gdy raport ma obejmować wyłącznie zamówienia ze sklepu, prostszy i czytelniejszy będzie LEFT JOIN; gdy potrzebujesz tylko wspólnych rekordów, wybierz INNER JOIN. Dla samej kontroli różnic często wystarczy pełne złączenie z osobnym wskaźnikiem źródła.
Traktuj NULL jako sygnał do interpretacji, nie jako automatyczny dowód błędu. Najpierw potwierdź warunek łączenia i unikalność kluczy, a dopiero potem poprawiaj dane lub filtr. Dzięki temu w raporcie rozdzielisz brak pary od powielonego dopasowania, zamiast naprawiać efekt uboczny zapytania.
