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.