Problem: Jak połączyć dwie tabele w SQL'u? Ma to być złączenie równościowe (INNER JOIN). Tylko rekordy, które spełniają warunek złączenia mają być wybrane. Nic więcej, nic mniej.

Jak zapisać złączenie między nimi? Jaka jest dobra praktyka? Jak stworzyć kod, który będzie łatwy w utrzymaniu, rozbudowie i czytelny dla innych członków zespołu?

Możliwości:

  1. Użyj klauzuli: INNER JOIN
    FROM dbo.Users usr
    INNER JOIN dbo.Posts post ON (post.OwnerUserId = usr.Id)
  2. Wymień tabele, które chcesz złączyć w zaraz za FROM i później wykonaj złączenia w WHERE
    FROM dbo.Users usr, dbo.Posts post
    WHERE post.OwnerUserId = usr.Id

Rozwiązanie: Użyj INNER JOIN - składnia jest bardziej czytelna a kod będzie łatwiejszy w utrzymaniu i debugowaniu. Argumenty znajdziesz poniżej.

Argument 1: Czytelność

Składnia INNER JOIN jest bardziej czytelna. Po nazwie tabeli występuje warunek złączenia. Nie musisz szukać miejsca w WHERE gdzie się ten warunek znajduje.

Popatrz na to z dwóch perspektyw:

  1. Piszesz kod
  2. Utrzymujesz kod

Kod, który dzisiaj piszesz będziesz musiał za chwilę utrzymywać. Znasz to uczycie, gdy musisz wracać do swojego kodu po jakimś czasie? Zadajesz pytanie: "Co ja tam chciałem napisać?", "Po co ja wstawiłem ten warunek?", "O co tu właściwie chodzi?".

Utrzymujesz kod? Widziałeś zapytania gdzie warunki są wpisane w WHERE?

SELECT ...
FROM dbo.Users usr, dbo.Posts post, dbo.PostTypes ptypes, dbo.Comments comm, dbo.Votes vts, dbo.VoteTypes vtypes
WHERE post.OwnerUserId = usr.Id AND ptypes.Id = post.PostTypeId
AND comm.PostId = post.Id AND vts.PostId = post.Id AND vts.VoteTypeId = vtypes.Id

To ciężka praca, żeby sobie ten kod wyobrazić i potem modyfikować. Zakładam też, że w ramach wprowadzania zmian nie chciałbyś modyfikować tego kodu i wprowadzać INNER JOIN.

Ten sam kod tylko napisany trochę inaczej:

SELECT …
FROM dbo.Users usr
INNER JOIN dbo.Posts post ON (post.OwnerUserId = usr.Id)
INNER JOIN dbo.PostTypes ptypes ON (ptypes.Id = post.PostTypeId)
INNER JOIN dbo.Comments comm ON (comm.PostId = post.Id)
INNER JOIN dbo.Votes vts ON (vts.PostId = post.Id)
INNER JOIN dbo.VoteTypes vtypes ON (vts.VoteTypeId = vtypes.Id)

Widać powiew świeżości. Widzisz co się z czym łączy, Twój kod zyskuje na przejrzystości.

Argument drugi: Podatność na błędy

Pisząc warunki w WHERE łatwo zapomnieć dodać złączenia. Wiesz co się wtedy dzieje?

Dostajesz wtedy iloczyn kartezjański (cartesian join), kiedy wszystkie rekordy w jednej tabeli połączone są ze wszystkimi rekordami z drugiej. Pół biedy kiedy to są małe tabele ale gdy łączysz dwie duże tabele możesz doprowadzić do katastrofy.

Tak wygląda zapytanie, gdy zapomnisz dodać warunku.

SELECT ...
FROM dbo.Users usr, dbo.Posts post, dbo.PostTypes ptypes, dbo.Comments comm, dbo.Votes vts, dbo.VoteTypes vtypes
WHERE ptypes.Id = post.PostTypeId
AND comm.PostId = post.Id AND vts.PostId = post.Id AND vts.VoteTypeId = vtypes.Id

Przy odrobinie szczęścia możesz takim "zapomnieniem" dostarczyć kilku gorących chwil administratorowi baz danych. Dostaniesz za dużo rekordów i baza danych może nie dać sobie z tym rady.

Gdy używasz INNER JOIN, nie tak łatwo zapomnieć o warunku. Nawet jeżeli zapomnisz Twój edytor Ci przypomni. Oczywiście wciąż może się zdarzyć, że wprowadzisz warunek błędny ale wtedy łatwiej dojdziesz, gdzie go poprawić.

Powód trzeci: Debugowanie i testowanie

Wyobraź sobie, że utrzymujesz kod i wydajność odziedziczonego rozwiązania pozostawia wiele do życzenia. Musisz sprawdzić, która część jest nie wydajna i wymyślić rozwiązanie.

Możesz to zrobić na przykład komentując części kodu SQL. Gdy Twoje zapytanie ma złączenia INNER JOIN wtedy łatwo je debugować.

Na przykład chcesz wyłączyć część z tego zapytania:

SELECT ….
FROM dbo.Users usr
INNER JOIN dbo.Posts post ON (post.OwnerUserId = usr.Id)
INNER JOIN dbo.PostTypes ptypes ON (ptypes.Id = post.PostTypeId)
INNER JOIN dbo.Comments comm ON (comm.PostId = post.Id)
INNER JOIN dbo.Votes vts ON (vts.PostId = post.Id)
INNER JOIN dbo.VoteTypes vtypes ON (vts.VoteTypeId = vtypes.Id);

Możesz zrobić to w ten sposób

SELECT ...
FROM dbo.Users usr
INNER JOIN dbo.Posts post ON (post.OwnerUserId = usr.Id)
INNER JOIN dbo.PostTypes ptypes ON (ptypes.Id = post.PostTypeId)
-- INNER JOIN dbo.Comments comm ON (comm.PostId = post.Id)
-- INNER JOIN dbo.Votes vts ON (vts.PostId = post.Id)
-- INNER JOIN dbo.VoteTypes vtypes ON (vts.VoteTypeId = vtypes.Id)

Komentując części zapytania łatwiej to robić, gdy masz INNER JOIN.

Podsumowanie

Teraz wyobraź sobie. Piszesz duże zapytanie, które łączy tabele faktów z wieloma wymiarami. Zawierasz w kodzie część odpowiedzialną za Row Level Security. To zapytanie jest specyficzne dla jednego departamentu. Wyliczasz specjalnie na jego potrzeby segmentacje klientów.

Masz zbudowane piękne zapytanie: wydajność jest dobra, wszystkie wymagania są spełnione. Nawet biznes dostarcza sign-off dla Twojego kodu. Możesz już dodawać go do releasu.

Wiesz, że na podstawie zapytania zostanie stworzony raport w Power BI.

Jak myślisz, kiedy pojawią się prośby o modyfikację zapytania z powodu:

  1. Potrzebujemy jeszcze kilku wymiarów?
  2. Tego wymiaru jednak nie potrzebujemy
  3. Tutaj brakuje nam kilku miar, możesz je dodać?
  4. Segmentacja klientów? Tak, kiedyś to była dobra formuła, jednak ze względu na reorganizację, musimy ją zmodyfikować.

Tworzenie czytelnego kodu sprzyja późniejszemu utrzymaniu, debudowaniu i zarządzaniu zmianą. Nawet jeżeli nie Ty będziesz potem ten kod utrzymywał.

Inwestycja chwili czasu w czytelny kod zwraca się potem kilkukrotnie. Dzięki wybraniu lepszej strategii na tworzenie kodu Twoja praca staje się przyjemniejsza i skuteczniejsza. Ten artykuł opisywał jak taką strategię wybrać.

Aktywne uczenie

  1. W jaki sposób można zaimplementować złączenia równościowe w SQL?
  2. Jakie zalety ma użycie INNER JOIN przy złączeniach równościowych?
  3. Jaka dla Ciebie jest najważniejsza korzyść użycia INNER JOIN w złączeniach równościowych?

Ps. Inną strategią, która sprzyja tworzeniu czytelnego kodu jest użycie CTE