Analityk Systemowy Analiza systemowa, wymagania, UML, API i integracje Poznaj tematy

JOIN w SQL — jak analityk łączy dane i rozumie relacje systemu

Jedno zamówienie nie zawsze jest jednym rekordem. W systemie informatycznym klient, nagłówek zamówienia, pozycje, płatność i historia są osobnymi obiektami, a JOIN pozwala zadać pytanie obejmujące więcej niż jedną tabelę.

Wspólny model używany w tym klastrze rozdziela aktualny stan od historii i zdarzeń. Dzięki temu można analizować nie tylko „co jest teraz”, ale również „jak system doszedł do tego stanu”.

Jedno zamówienie nie zawsze jest jednym rekordem

Biznes mówi o jednym zamówieniu, lecz odpowiedź może wymagać danych klienta, nagłówka zamówienia, jego pozycji, produktu, płatności, historii statusów i zdarzeń integracyjnych. Zanim powstanie JOIN, trzeba ustalić, które źródło opisuje stan aktualny, które historię, a które próbę komunikacji z innym systemem.

Klucz główny i klucz obcy

Klucz główny identyfikuje rekord w tabeli. Klucz obcy wskazuje rekord powiązany w innej tabeli. W modelu zamówień jeden klient ma wiele zamówień, jedno zamówienie ma wiele pozycji, płatności, wpisów historii i zdarzeń, a każda pozycja wskazuje produkt.

Relacje między tabelami modelu zamówień

INNER JOIN — pokaż tylko dopasowane dane

SELECT
    o.order_id,
    o.order_status,
    c.customer_id,
    c.email
FROM orders AS o
INNER JOIN customers AS c
    ON c.customer_id = o.customer_id
WHERE o.order_id = 1002;
Przykładowy wynik: 1002 | CANCELLED | 17 | anna@example.test. Wynik potwierdza dopasowanie klienta, ale nie wyjaśnia przyczyny anulowania.

Najpierw wskazujemy zamówienia, potem tabelę klientów, a w ON opisujemy sposób dopasowania. INNER JOIN zwróci tylko zamówienia, dla których istnieje pasujący klient. Jeżeli relacja jest uszkodzona, rekord zniknie z wyniku — co może być ważnym sygnałem, ale może też ukryć brak danych.

LEFT JOIN — zachowaj dane z lewej strony

SELECT
    o.order_id,
    o.order_status
FROM orders AS o
LEFT JOIN order_items AS oi
    ON oi.order_id = o.order_id
WHERE oi.order_item_id IS NULL;
Przykładowy wynik: zamówienia 1003 i 1004. Są kandydatami do weryfikacji reguły, ponieważ system może dopuszczać pusty koszyk tylko w stanie roboczym.

To zapytanie wybiera zamówienia bez pozycji. LEFT JOIN zachowuje każdy rekord z lewej tabeli, a brak dopasowania po prawej stronie reprezentuje wartościami NULL. W analizie braków jest często ważniejszy niż INNER JOIN.

Warunek w ON a warunek w WHERE

-- Zachowaj wszystkie zamówienia, ale pokaż tylko płatności PAID
SELECT o.order_id, p.payment_status
FROM orders AS o
LEFT JOIN payments AS p
    ON p.order_id = o.order_id
   AND p.payment_status = 'PAID';

-- Odfiltruj zamówienia bez płatności PAID
SELECT o.order_id, p.payment_status
FROM orders AS o
LEFT JOIN payments AS p
    ON p.order_id = o.order_id
WHERE p.payment_status = 'PAID';
Różnica wyniku fixture: wariant z filtrem w ON zachowuje cztery zamówienia; wariant z filtrem w WHERE zwraca dwa zamówienia posiadające płatność PAID.

W pierwszym wariancie filtr działa podczas budowania złączenia, więc zamówienie bez płatności nadal może pojawić się z wartością NULL. W drugim filtr działa na gotowym wyniku i odrzuca brak dopasowania. Ten szczegół często decyduje o tym, czy analiza wykryje brak, czy go ukryje.

Dlaczego po JOIN pojawia się więcej wierszy

Jeśli jedno zamówienie ma trzy pozycje i dwie płatności, bezpośrednie połączenie obu tabel może utworzyć sześć wierszy. To nie musi oznaczać sześciu płatności ani sześciu problemów — jest skutkiem kombinacji relacji 1:N.

SELECT
    o.order_id,
    COUNT(DISTINCT oi.order_item_id) AS item_count,
    COUNT(DISTINCT p.payment_id) AS payment_count
FROM orders AS o
LEFT JOIN order_items AS oi ON oi.order_id = o.order_id
LEFT JOIN payments AS p ON p.order_id = o.order_id
WHERE o.order_id = 1001
GROUP BY o.order_id;
Przykładowy wynik: 1001 | 3 | 2. Surowe złączenie tworzy sześć wierszy, ponieważ każda z trzech pozycji łączy się z każdą z dwóch płatności.

Jeżeli liczymy obiekty po złączeniu, warto rozważyć COUNT(DISTINCT ...), osobne agregacje albo wcześniejsze zgrupowanie danych. Inaczej raport może zawyżyć liczbę pozycji, kwotę lub liczbę zdarzeń.

Liczność relacji jest częścią wymagania

Analityk powinien zapytać, czy zamówienie może mieć kilka płatności, czy status może wystąpić wiele razy, czy identyfikator zewnętrzny ma być unikalny i czy ponowienie zdarzenia tworzy nowy rekord. Odpowiedź nie wynika wyłącznie z nazwy kolumny. Jest częścią reguły biznesowej i kontraktu integracji.

Jak wykrywać brakujące i wielokrotne relacje

SELECT order_id, COUNT(*) AS payment_count
FROM payments
GROUP BY order_id
HAVING COUNT(*) > 1;

SELECT external_id, COUNT(*) AS event_count
FROM integration_events
WHERE external_id IS NOT NULL
GROUP BY external_id
HAVING COUNT(*) > 1;

Oba wyniki są sygnałami do analizy. Kilka płatności może oznaczać korektę lub ponowienie, a duplikat identyfikatora może być poprawnym śladem retry albo naruszeniem idempotencji.

Wynik fixture: dwa zamówienia mają więcej niż jedną płatność, a identyfikator EXT-1002 występuje przy dwóch zdarzeniach. Pierwszy wynik wymaga sprawdzenia typów i kwot płatności, drugi — kontraktu ponowień. Sama liczba rekordów nie przesądza o błędzie.

RIGHT JOIN i FULL JOIN

W podstawowych analizach zwykle wystarczą INNER JOIN i LEFT JOIN. RIGHT JOIN można często zastąpić zmianą kolejności tabel. FULL JOIN jest przydatny przy porównywaniu dwóch zbiorów, ale nie jest dostępny w identycznej formie w każdym silniku. Najpierw trzeba wiedzieć, jaki brak lub nadmiar chcemy wykazać.

Lista kontrolna przed zaakceptowaniem wyniku JOIN

Następny krok

Poprawne złączenie danych nie kończy analizy. Aktualny stan, historia, płatność i zdarzenia pozwalają dopiero odtworzyć przebieg problemu.