🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

JOIN w SQL dla .NET developera — łączenie tabel bez błędów

JOIN w SQL — łączenie tabel Klienci i Zamowienia po kluczu obcym, typy INNER/LEFT/RIGHT/FULL

Jedna tabela to rzadkość. W realnej bazie dane są rozbite: tabela Zamowienia trzyma KlientId, a nie imię klienta — bo powtarzanie imienia w każdym wierszu byłoby marnotrawstwem i źródłem niespójności. Żeby złożyć je z powrotem, używasz JOIN. To serce SQL-a i najczęstsze pytanie na rozmowie o bazę danych. Po opanowaniu podstaw zapytań to następny krok, bez którego nie napiszesz nic sensownego.

Schemat i klucze obce

-- Klienci(Id, Imie, Miasto)
-- Produkty(Id, Nazwa, Cena)
-- Zamowienia(Id, KlientId, ProduktId, Ilosc)

Zamowienia.KlientId to klucz obcy — wskazuje na Klienci.Id. JOIN wykorzystuje tę relację, żeby połączyć wiersze z dwóch tabel tam, gdzie klucze się zgadzają.

INNER JOIN — tylko pasujące pary

Najczęstszy JOIN. Zwraca wiersze, dla których istnieje dopasowanie po obu stronach:

SELECT z.Id, k.Imie, p.Nazwa, z.Ilosc
FROM Zamowienia z
INNER JOIN Klienci k ON z.KlientId = k.Id
INNER JOIN Produkty p ON z.ProduktId = p.Id;

Warunek po ON mówi, jak dopasować wiersze. Aliasy (z, k, p) skracają zapis i są obowiązkowe, gdy kolumny mają te same nazwy w różnych tabelach. Zamówienie bez pasującego klienta (osierocone) nie pojawi się w wyniku — to istota INNER.

LEFT JOIN — wszystko z lewej, dopasowania z prawej

LEFT JOIN zwraca wszystkie wiersze z lewej tabeli, a z prawej dokłada dopasowania albo NULL, gdy ich nie ma:

SELECT k.Imie, z.Id AS Zamowienie
FROM Klienci k
LEFT JOIN Zamowienia z ON z.KlientId = k.Id;

To zapytanie pokaże także klientów bez żadnego zamówienia — z NULL w kolumnie Zamowienie. INNER JOIN by ich pominął. Stąd typowe zastosowanie: „znajdź klientów, którzy nic nie kupili”:

SELECT k.Imie
FROM Klienci k
LEFT JOIN Zamowienia z ON z.KlientId = k.Id
WHERE z.Id IS NULL;

RIGHT i FULL JOIN

RIGHT JOIN to lustro LEFT — wszystkie wiersze z prawej tabeli. W praktyce prawie nikt go nie używa (zamiast tego odwraca się kolejność tabel i pisze LEFT). FULL JOIN zwraca wszystko z obu stron, uzupełniając brakujące dopasowania NULL-ami — przydatny do wykrywania rozbieżności między tabelami.

JOINZwraca
INNERtylko wiersze z dopasowaniem po obu stronach
LEFTwszystkie z lewej + dopasowania z prawej (lub NULL)
RIGHTwszystkie z prawej + dopasowania z lewej (lub NULL)
FULLwszystkie z obu stron, NULL gdzie brak dopasowania

Self-join — tabela złączona sama ze sobą

Gdy wiersze odwołują się do innych wierszy tej samej tabeli (pracownik ma przełożonego, też pracownika), łączysz tabelę ze sobą pod dwoma aliasami:

SELECT prac.Imie AS Pracownik, szef.Imie AS Przelozony
FROM Pracownicy prac
LEFT JOIN Pracownicy szef ON prac.SzefId = szef.Id;

Most do .NET — Include zamiast ręcznego JOIN

W Entity Framework Core rzadko piszesz JOIN ręcznie — relacje modelujesz właściwościami nawigacyjnymi, a EF generuje JOIN za Ciebie:

var zamowienia = db.Zamowienia
    .Include(z => z.Klient)     // EF Core dołączy dane klienta (JOIN pod spodem)
    .Include(z => z.Produkt)
    .ToList();

Include odpowiada INNER/LEFT JOIN w wygenerowanym SQL. To wygoda, ale — jak każda abstrakcja — ma pułapkę, jeśli zapomnisz jej użyć.

Include to nie ozdobnik. To jawna decyzja „pobierz te powiązane dane jednym zapytaniem”. Jej brak nie daje błędu — daje pułapkę N+1, o której niżej.

Pułapki rekrutacyjne

Pułapka #1 — N+1 zapytań. Najważniejsza pułapka .NET developera z bazą. Pętla po zamówieniach, która dla każdego z osobna sięga po klienta, generuje 1 zapytanie o listę + N zapytań o klientów = N+1 odwołań do bazy zamiast jednego JOIN-a:

foreach (var z in db.Zamowienia.ToList())      // 1 zapytanie
    Console.WriteLine(z.Klient.Imie);          // +1 zapytanie na KAŻDĄ iterację

Rozwiązanie: Include(z => z.Klient) — jeden JOIN zamiast setek zapytań. To niemal pewne pytanie na rozmowie i realna przyczyna wolnych aplikacji.

Pułapka #2 — WHERE na kolumnie z LEFT JOIN zamienia go w INNER. Warunek WHERE z.Ilosc > 5 na tabeli dołączonej przez LEFT JOIN odrzuci wiersze z NULL (bo NULL > 5 to nie prawda) — czyli właśnie te, które LEFT JOIN miał zachować. Jeśli warunek ma dotyczyć tylko dopasowanych wierszy, przenieś go do ON, nie do WHERE.

Pułapka #3 — iloczyn kartezjański. JOIN bez warunku ON (albo z błędnym) łączy każdy wiersz z każdym — 1000 × 1000 = milion wierszy. Objaw: zapytanie zwraca absurdalnie dużo danych i zawiesza się. Zawsze sprawdź, czy ON łączy po właściwych kluczach.

Pułapka #4 — duplikaty przy agregacji. JOIN „jeden do wielu” zwielokrotnia wiersze strony „jeden”. SUM(p.Cena) po złączeniu produktów z zamówieniami policzy cenę tyle razy, w ilu zamówieniach produkt wystąpił. Uważaj, co agregujesz po JOIN-ie — czasem trzeba DISTINCT albo podzapytania.

Podsumowanie

JOIN składa dane rozbite między tabele według kluczy obcych. INNER JOIN zwraca tylko pasujące pary, LEFT JOIN zachowuje wszystkie wiersze z lewej strony (z NULL tam, gdzie brak dopasowania), RIGHT i FULL to warianty rzadziej używane. W .NET zwykle nie piszesz JOIN-a ręcznie — robi to EF Core przez Include — ale musisz rozumieć, co generuje, bo brak Include to pułapka N+1: N+1 zapytań zamiast jednego złączenia. Reszta klasycznych błędów (warunek WHERE psujący LEFT JOIN, iloczyn kartezjański, zdublowane agregaty) sprowadza się do jednej zasady: wiedz, które wiersze JOIN zachowuje, a które odrzuca.

Co dalej

JOIN-y na dużych tabelach potrafią być wolne — i tu wchodzą indeksy i wydajność SQL, które decydują, czy złączenie trwa milisekundy czy sekundy. Zanim wyślesz zapytanie z aplikacji, poznaj SQL injection i parametryzację w .NET. A jak EF Core zamienia właściwości nawigacyjne na JOIN-y — Entity Framework Core od podstaw i budowanie API na Dapperze, gdzie JOIN piszesz już ręcznie.

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak NFZ, Kamsoft, Diagnostyka, Hermes Reply Polska czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

🚀 Co dalej?

Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.

1 comment

Dodaj komentarz

czytanie to początek

Zamień wiedzę w umiejętności

Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.

Pobieram roadmapę →