Mówią, że się nie da, ale czasem da się.
Przytrafiło mi się jakiś czas temu w pracy, że znów - jak za starych, dobrych czasów - piszę dużo kodu SQL. Kod ów działa lepiej lub gorzej, ale ja dziś nie o tym.
Dziś - o formatowaniu.
Szkół jest kilka. W odróżnieniu od takiego, dajmy na to, Pythona, Javy czy C#, gdzie format kodu został zestandaryzowany wieki temu i wszyscy się tego standardu (pi x oko) trzymają, o tyle w przypadku SQL programiści nie mogą się dogadać i każdy ciągnie w swoją stronę.

Pociągnę więc i ja.
Jakiś czas temu odkryłem stronę SQL Style Guide, która pokazuje pewien konkretny styl formatowania zapytań SQL, zaproponowany przez Simona Holywella. Bardziej z nudów i dla hecy niż z przekonania zacząłem przerabiać niektóre ze swoich zapytań na ten właśnie styl - i kliknęło!
Tajemnica polega na tym, że tworzymy rzeki.
Rzeki w tym konkretnym kontekście nie są wprawdzie tak piękne, jak w powyższej piosence - ale jednak są całkiem niebrzydkie.
Przykład:
SELECT tytuł , autor , rok_wydania FROM książki WHERE tytuł = 'Cyberiada';
Widać? No przecież, że widać. Po słowie kluczowym pojawia się pionowa "rzeka", wszystko na lewym brzegu jest wyrównane do prawej i vice versa.
Weźmy odrobinę bardziej skomplikowany przypadek:
SELECT B.title AS BookTitle , A.name AS AuthorName , L.language AS Language FROM books AS B INNER JOIN authors AS A ON B.author_id = A.id INNER JOIN languages AS L ON B.language_id = L.id WHERE A.active = 1 ORDER BY B.title;

Oczywiście czym dłuższe zapytanie, tym trudniej utrzymać "rzekę" w jednym kawałku, dlatego czasem trzeba podjąć decyzję o "złamaniu" rzeki:
WITH UpdatedBooks AS ( SELECT B.id AS BookID , B.title AS NewTitle FROM books AS B INNER JOIN authors AS A ON B.author_id = A.id WHERE A.active = 0 AND B.updated_at < DATEADD(year, -1, GETDATE()) UNION ALL SELECT B.id , B.title FROM books AS B INNER JOIN languages AS L ON B.language_id = L.id WHERE L.language = 'English' AND B.pages > 300 ) UPDATE B SET B.title = U.NewTitle FROM books AS B INNER JOIN UpdatedBooks AS U ON B.id = U.BookID WHERE B.title != U.NewTitle;

W ostatnim przykładzie mamy dwie "rzeki": jedna wewnątrz podzapytania CTE, druga na końcu. No i sama linia otwierająca CTE również nie jest częścią "rzeki". Niektórzy idą w skrajność i przesuwają słówko "WITH" żeby "pasowało", ale osobiście uważam powyższy wariant za bardziej czytelny.
WITH UpdatedBooks AS ( -- tu WITH kończy się na wysokości SELECT SELECT B.id AS BookID , B.title AS NewTitle FROM books AS B ...
Gwoli ścisłości, podlinkowana na początku artykułu strona o formatowaniu kodu SQL mówi nie tylko o "rzekach", ale również wielu innych elementach, które jednak postanowiłem zignorować, bo mi się tak podoba.
A Ty, Czytelniku, jak formatujesz swój kod SQL?
Nie formatuję w ogóle, bo nie umiem w bazodanowe relacje, ale chcę się wkrótce nauczyć. Na start polecasz eksperymenty z MySQL czy od razu PostgreSQL?
Na start polecam próbę rozwiązania jakiegoś konkretnego problemu. Uczenie się samego SQL-a (czy dowolnej innej technologii komputerowej) „na sucho” tj. bez konkretnej potrzeby na ogół nie zdaje egzaminu.
Rzekłszy jednakowoż powyższe oraz zerknąwszy we własną przeszłość, gdybym dziś startował w te klocki i miałbym wybrać między tymi dwoma silnikami, postawiłbym na PG.
Miej też na uwadze, że poszczególne dialekty SQL w warstwie podstawowej nie różnią się między sobą aż tak bardzo. Jeżeli załapiesz podstawy na PGSQL, powinieneś w miarę bezproblemowo przesiąść się na MySQL, SQLite czy cokolwiek innego. Oczywiście jak się zaczniesz zagłębiać w bardziej zaawansowane kawałki, prędzej czy później odkryjesz spore różnice, ale podstawy są tu i tam te same.
Sam rzeźbię od lat w MSSQL, gdzieś tam po drodze miałem krótką przygodę z MySQL oraz nieco dłuższą z Oracle, ale MSSQL zawsze do mnie wraca, jak bumerang.
Formatowanie SQL dla mnie powinno być takie aby:
– widać jasno boki
– widać zagnieżdżenia bloków (co jest wewnątrz czego)
– widać części warunków – odpowiednie ułożenie zagnieżdżonych warunków np. AND na początek
Do tego dochodzą kwestie estetyczne jak pokazałeś:
– równać do lewej czy do środka.
To są już rzeczy jak komu pasuje. Mam doczynienia zazwyczaj z kilkudziesięcioma/kilkuset linijkowymi wykwitami także chyba mam doświadczenie.
… Jednak przecinki przed wg mnie są jakimś antywzorcem (mimo, że jak dopisujemy coś to ruszamy tylko właściwą linie, a nie linie przed). Moje serce roni łzy na sam widok.
Niezbyt mi się to widzi. Po pierwsze, wyrównywanie słów na początku do prawej jest niewygodne, bo trzeba każdorazowo brać pod uwagę ile liter ma dane słowo. W ogóle zresztą wszelkie wyrównywanie słów ma ten feler, że jak kiedyś dodasz nowe, dłuższe od któregokolwiek z obecnych, to trzeba poprawiać całość. Po drugie, przecinki na początku linijek to zło i niekonsekwencja, skoro pierwsza kolumna jest w linii z SELECT – już lepiej mieć każdą kolumnę w nowej linijce i przecinek na końcu (fakt, wtedy dla odmiany jest niekonsekwencja z brakiem przecinka po ostatniej kolumnie, bo SQL nie ogarnia nadmiarowych przecinków, niemiło z jego strony). Po trzecie, logika jest zaburzona przez przypadki typu INNER JOIN czy UNION ALL, które powinny być w całości po lewej, a nie przedzielone „rzeką” pośrodku. Po czwarte i najpoważniejsze, w tym układzie całkowicie nieczytelna jest hierarchia elementów, np. UNION ALL zaczyna się gdzieś pośrodku i w ogóle nie rzuca się w oczy. Zalet za bardzo nie widzę, poza tym że można sobie narysować ładną pionową kreskę na screenshocie – co chyba jednak wad nie równoważy.
Wyrównanie „do środka” spotkałem produkcyjnie prawie 20 lat temu i czuję, że to było odziedziczone z poprzednich języków przed SQL.
Wygląda to ładnie, gdyby zapytanie się nie zmieniało w czasie (lub gdybyśmy pracowali bez systemu kontroli wersji, co pewnie kiedyś było czymś częstym, i mielibyśmy czas na taką rearanżację całości). Byłoby jak w książce.
No i gdyby edytor to wspierał i sam wyrównywał w ten sposób.
Większość nowych osób w zespole wyrównywało do lewej.