SQL Format – Czyli jak zawsze pisać dobry SQL?

23 grudnia 2020

SQL Format - Czyli jak zawsze pisać dobry SQL?

SQL Format – Czyli jak zawsze pisać dobry SQL i dlaczego to jest ważne?

SQL Format, SQL Styleguide, styl SQL – to nie są pojęcia, o które najczęściej pyta się na początku nauki języka SQL. A jednak poprawne formatowanie i SQLa, pisanie w sposób logiczny i konsekwentny potrafi oszczędzić dziesiątki (serio) godzin.

Godzin, które można stracić na szukanie zaginionego przecinka, mogą przepaść bo nie chciało nam się napisać dobrego komentarza i po raz 30ty rozkminiamy co robi dane window function. Godzin, które mogłeś poświęcić na odpoczynek lub rozwój. Gdybyś tylko zadbał o swój SQL Format.

Jeżeli dopiero zaczynasz przygodę ze światem SQL, to niektóre tematy mogą Ci się wydać zbyt zaawansowane. Jeżeli tak jest, spróbuj zacząć od tego artykułu.

➡️ SQL Format: styl – ogólne wskazówki

Zacznę od najważniejszej porady jeśli chodzi o styl SQL. Konsekwencja. Jeszcze raz powtórzę. Konsekwencja. Nawet jeżeli twój SQL jest trudniejszy do zrozumienia niż kodeks podstępowania administracyjnego, to jeżeli będziesz pisał w ten sam sposób, to za tydzień zrozumiesz to co napisałeś dziś. Chcesz być dobrym analitykiem, chcesz być królem produktywności i hrabią automatyzacji, a jednocześnie patrzysz na swój SQL i nie rozumiesz o co Ci wczoraj chodziło? No właśnie.

Podstawy

  1. Bądź konsekwentny.
  2. Używaj czytelnych nazw.
  3. Pisząc, stosuj snake_case, a nie CamelCase.

Spójrz na poniższy przykład:

-- BAD
 SELECT state 
     ,count(distinct order_id)       as NrOfOrders
     ,count(distinct customer_id)    as cus 
 From superstore.orders
 group by 1;

Niezrozumiałe „cus” jeszcze jakoś można przeżyć, jeśli nie sam sobie wyznaczasz styleguide, ale to „NrOfOrders” boli w oczy.

Poprawiony SQL format wygląda tak:

-- GOOD
 select     
     state 
     ,count(distinct order_id)       as cnt_orders
     ,count(distinct customer_id)    as cnt_customers 
 from superstore.orders
 group by 1;

Cudzysłów zawsze pojedynczy

String w WHERE clause? Kilka wyrazów w IN()? W świecie SQL zawsze pojedynczy cudzysłów. Podwójnego używamy dla kolumn. Poza tym, jeżeli stosujemy snake_case to nie będzie nam to potrzebne, ponieważ w nazwach kolumn nie powinno być spacji.

-- BAD 
 select 
     order_id
 from superstore.orders 
 where customer_name = "Seth Vernon";
 -- GOOD 
 select 
     order_id
 from superstore.orders 
 where customer_name = 'Seth Vernon';

Żadnych spacji w kolumnach

Tak, było o tym przed chwilą, ale tu warto się powtórzyć. To jest jedna z podstawowych spraw, nie tylko gdy pytamy jak pisać sql, ale gdy w ogóle używamy tabel. Rozumiem, że w Excelu się stosuje spacje w nagłówkach. Ale co, jeśli nasza tabelka będzie stanie się na tyle ważna, że trafi do bazy danych? No właśnie.

-- BAD
 select     
     state 
     ,count(distinct order_id)       as "cnt orders"
     ,count(distinct customer_id)    as "cnt customers" 
 from superstore.orders
 group by 1;
 -- GOOD
 select     
     state 
     ,count(distinct order_id)       as cnt_orders
     ,count(distinct customer_id)    as cnt_customers 
 from superstore.orders
 group by 1;

Korzystaj z != (zamiast <>)

Clean code reads like well-written prose” mawiają programiści i tę samą zasadę powinniśmy próbować stosować w SQL. Nie jesteś komputerem, Ty musisz kod przeczytać, żeby go zrozumieć. Dlatego, stosuj operator != a nie <>. Ponieważ != czyta się do słownie „nie jest równy”. To może drobna rzecz, ale poprawi twój styl SQL, trust me.

-- BAD
 select * from superstore.orders 
 where state <>'Texas';
 -- GOOD
 select * from superstore.orders 
 where state != 'Texas';

⚠️ Uwaga – kontrowersyjne! Przecinki na początku linii, nie na końcu.

Prawie wszystkie strony typu „SQL style” namawiają do stawiania przecinka na końcu linii. Kiedyś sam tak robiłem. Natomiast, widzę tu pewien problem. Jeżeli kolejne linie są różnej długości, to szukając brakującego przecinka trochę się namęczysz. O ile łatwiej byłoby, gdyby twój wzrok mógł po prostu iść z góry na dół. To też jest dla mnie kwintesencja tego czym jest doby SQL format – jak najwięcej rzeczy tak samo.

-- BAD
 select 
     customer_id,
     order_date,
     order_id,
     category,
     sub_category,
     category, 
     postal_code
 from superstore.orders ;
 -- GOOD
 select 
     customer_id
     ,order_date
     ,order_id
     ,category
     ,sub_category
     ,category 
     ,postal_code
 from superstore.orders ;

White space i indentacja

Często obserwuję, taką manierę, że ludzie boją się długiego kodu. Im krótszy SQL, tym lepszy. Nie zawsze. A szczególnie, nie wtedy kiedy skracasz SQL bo usuwasz wolną przestrzeń. White space i związane z nią stosowanie spacji oraz wcięć jest bardzo kluczowe. Pamiętaj, że twój SQL format ma wspierać łatwość czytania. Nie chodzi o to że wymiatasz i niczym w matriksie widzisz w zbitce słów wysokie blondynki lub brunetów. Chodzi o to, że każdy analityk się łatwo Cię zrozumie. W jakimś stopniu styl twojej komunikacji, to jest styl SQL, który stosujesz.

-- BAD
 with
 georgia_orders as
 (select date_format(order_date, "%Y")    as order_year
 ,count(distinct order_id)         as cnt_orders 
 from superstore.orders 
 where state ='Georgia'
 group by 1
 ),        
 all_orders as
 (select date_format(order_date, "%Y")    as order_year
 ,count(distinct order_id)         as cnt_orders 
 from superstore.orders 
 group by 1
 )
 select
     all_orders.order_year       as order_year
     ,all_orders.cnt_orders      as total_orders
     ,georgia_orders.cnt_orders  as georgia_orders       
 from all_orders
 left join georgia_orders on  all_orders.order_year = georgia_orders.order_year;

 -- GOOD
 with
 georgia_orders as
     (
     select
         date_format(order_date, "%Y")   as order_year
         ,count(distinct order_id)       as cnt_orders 
     from superstore.orders 
     where state ='Georgia'
     group by 1
     ),

 all_orders as
     (
     select
         date_format(order_date, "%Y")   as order_year
         ,count(distinct order_id)       as cnt_orders 
     from superstore.orders 
     group by 1
     )

 select
     all_orders.order_year       as order_year
     ,all_orders.cnt_orders      as total_orders
     ,georgia_orders.cnt_orders  as georgia_orders       
 from all_orders
 left join georgia_orders on  all_orders.order_year = georgia_orders.order_year;

➡️ SQL Format: JOIN & alias

Im bardziej skomplikowany SQL tym ważniejszy porządny SQL format.

Nie używaj aliasów / nazw tabel gdy nie ma joinów…

Szczególnie na początku nauki SQLa, może istnieć taka potrzeba, byś był super porządny. I zawsze piszesz tabela.kolumna. Niepotrzebnie. Jeżeli bierzesz dane tylko z jednej tabeli nie ma takiej potrzeby, bo wiadomo skąd pochodzą kolumny.

-- BAD
 select
     orders.order_id 
 from superstore.orders;

 -- GOOD
 select
     orders.order_id 
 from superstore.orders;

…Ale korzystaj z aliasów / nazw tabel gdy są joiny

Powody są dwa. Po pierwsze, tak samo nazwana kolumna może istnieć w kilku tabelach. Wtedy dostaniesz błąd o „ambiguous column”. Po drugie, dobrze od razu widzieć skąd ostatecznie pochodzą wybrane kolumny. Dzięki temu, łatwo będziesz mógł zmienić kwerendę. Dobry SQL format, powinien Ci ułatwiać dokonywanie zmian.

-- BAD (Błąd "Column 'region' in on clause is ambiguous")
 select
     person                          as responsible_manager  
     ,count(distinct customer_id)    as cnt_of_customers
 from superstore.people
 left join superstore.orders on region = region 
 group by 1;

 -- GOOD
 select
     people.person                       as responsible_manager  
     ,count(distinct orders.customer_id) as cnt_of_customers
 from superstore.people
 left join superstore.orders on people.region = orders.region 
 group by 1;

Zawsze pisz jaki to JOIN

Domyślny join to INNER JOIN, ale… może Ty tak często robisz LEFT JOIN, że o tym zapomniałeś. Z tego powodu z rozpędu napisałeś po prostu JOIN. Kwerenda działa? Działa. Tylko jest problem. Wyciąłeś kilka rekordów i nawet o tym nie wiesz. Dlatego dbaj o swój styl SQL, pisz zawsze jakiego joina używasz.

-- BAD
 select
     people.person                       as responsible_manager  
     ,count(distinct orders.customer_id) as cnt_of_customers
 from superstore.people
 join superstore.orders on people.region = orders.region 
 group by 1;

 -- GOOD
 select
     people.person                       as responsible_manager  
     ,count(distinct orders.customer_id) as cnt_of_customers
 from superstore.people
 left join superstore.orders on people.region = orders.region 
 group by 1;

FROM vs JOIN czyli co jest źródłem a co dodatkiem

Trochę wracam do poprzedniego punktu. Ponownie, może chodzić o kilka cennych rekordów. Dobry analityk boi się niedbałości. A na tym polega właśnie gubienie kilku rekordów. I tu właśnie warto się zastanowić którą tabele umieszczasz w FROM, a z którą robisz JOIN. Jeżeli szukasz odpowiedzi na pytanie jak pisać SQL, to pamiętaj, że chodzi nie tylko o poprawność składni. Chodzi także o to, żeby dokładnie wiedzieć co taki SQL robi. Które rekordy wycina, a których nie.

-- BAD
 select
     orders.order_id 
     ,returned_orders.returned 
 from superstore.returns returned_orders
 left join superstore.orders  on returned_orders.order_id = orders.order_id;

 -- GOOD
 select
     orders.order_id
     ,returned_orders.returned 
 from superstore.orders 
 left join superstore.returns returned_orders on returned_orders.order_id = orders.order_id;

➡️ SQL Format: CTE (WITH vs Subquery)

Często zadawane pytanie na stackoverflow, to jaka jest różnica między Common Table Expresion (CTE), a subquerry. Z punktu widzenia stylu SQL CTE po prostu łatwiej się czyta. Używasz tylko słowa WITH tworzysz sobie małe selecty, jeden po drugim. Możesz się bez problemu odwołać do wcześniejszego, wiesz co skąd się wywodzi. Performance, jest podobny, choć z mojego doświadczenia mogę powiedzieć, że CTE działa minimalnie szybciej. Natomiast, twój styl SQL będzie miał się o wiele lepiej, bo CTE jest po prostu czytelniejsze.

Spójrz:

-- BAD (usually)
 select
     all_orders.order_year       as order_year
     ,all_orders.cnt_orders      as total_orders
     ,georgia_orders.cnt_orders  as georgia_orders       
 from (
     select
         date_format(order_date, "%Y")   as order_year
         ,count(distinct order_id)       as cnt_orders 
     from superstore.orders 
     group by 1
     ) all_orders
 left join (
     select
         date_format(order_date, "%Y")   as order_year
         ,count(distinct order_id)       as cnt_orders 
     from superstore.orders 
     where state ='Georgia'
     group by 1
     ) georgia_orders on  all_orders.order_year = georgia_orders.order_year;
 
-- GOOD
 with
 georgia_orders as
     (
     select
         date_format(order_date, "%Y")   as order_year
         ,count(distinct order_id)       as cnt_orders 
     from superstore.orders 
     where state ='Georgia'
     group by 1
     ),

 all_orders as
     (
     select
         date_format(order_date, "%Y")   as order_year
         ,count(distinct order_id)       as cnt_orders 
     from superstore.orders 
     group by 1
     )
 select
     all_orders.order_year       as order_year
     ,all_orders.cnt_orders      as total_orders
     ,georgia_orders.cnt_orders  as georgia_orders       
 from all_orders
 left join georgia_orders on  all_orders.order_year = georgia_orders.order_year;

Wszystko jest po kolei. Zamówienia z Georgii, wszystkie zamówienia, a na końcu zebranie tego do kupy. Tak działa CTE. Natomiast, w przypadku subquery ma się wrażenie że zaczynamy od podsumowania, i trzeba dopiero się dowiedzieć, co stoi za każdym wyrażeniem. A to jest tylko jeden poziom subquery. Wyobraź sobie 3 zagnieżdżone. No, także, korzystaj raczej z CTE.

➡️ SQL Format: jak pisać dobre komentarze w kodzie

Dobry komentarz w kodzie SQL pełni kilka ról.

Nazywanie rzeczy po imieniu

Po pierwsze, nazywa po ludzku to co czasem jest niewiadomą. Mogłeś nie mieć wpływu na to jak ktoś nazwał tabelę i nie chcesz co chwilę grzebać w dokumentacji źródłowej. Przyda się komentarz.

Opisywanie skomplikowanych obliczeń

Po drugie, komentarze pozwalają sobie poradzić ze skomplikowanymi obliczeniami. Jeżeli twoja WINDOW FUNCTION ciągnie się przez kilka linijek, to może nie być jasne co tak naprawdę liczysz. Pamiętaj, że najczęściej jako analityk nie pracujesz sam. Więcej, niemalże zawsze, analityk analizuje coś dla kogoś. Podobnie jest z SQLem. Pomóż innym zrozumieć co liczysz, nawet jeżeli sądzisz, że twój styl SQL jest bardzo czytelny.

Pokazywanie logiki kalkulacji

Po trzecie, jeżeli używamy wielu selectów pod rząd, a może niektóre są zagnieżdżone, to przyda się nam krótka informacja informująca o logice kalkulacji. Nie ma potrzeby dodatkowo obciążać głowy. I tak już ciężko pracuje.

Poniżej znajdziesz przykład użycia komentarzy.

with
 georgia_orders as
     (
     /* yearly order count in Georgia */
     select
         date_format(order_date, "%Y")   as order_year
         ,count(distinct order_id)       as cnt_orders 
     from superstore.orders 
     where state ='Georgia'
     group by 1
     ),

 all_orders as
     (
     /* yearly order count for all states */
     select
         date_format(order_date, "%Y")   as order_year
         ,count(distinct order_id)       as cnt_orders 
     from superstore.orders 
     group by 1
     )

 select
     /* previously calculated */
     all_orders.order_year        as order_year
     ,all_orders.cnt_orders       as total_orders
     ,georgia_orders.cnt_orders   as georgia_orders
    /* georgia's percentage share in all orders */
    ,round(((georgia_orders.cnt_orders/all_orders.cnt_orders)*100),2) as georgia_share
 from all_orders
 left join georgia_orders on  all_orders.order_year = georgia_orders.order_year;

⚠️ Przez komentarz twój SQL nie działa?

A, jeszcze jedna ważna rzecz. Dlaczego używam komentarzy w stylu /* comment */ a nie — comment? Dlatego, że pierwszy styl zamyka komentarz, a drugi komentuje całą linijkę do końca. Jest to o tyle ważne, że jeżeli używasz jakiegoś programu typu SQL parser, to może się okazać, że twój SQL jest na początku spłaszczany do jednej linijki.

I co wtedy?

Wtedy jeżeli użyjesz komentarza typu — to wytniesz resztę SQLa. Natomiast, jeżeli użyjesz stylu /* comment */, to wiadomo gdzie komentarz się zaczyna, a gdzie kończy. Pół biedy, jeśli twój komentarz spowoduje, że SQL przestanie działać. O wiele gorszy scenariusz, to taki, gdzie SQL działa ale Ty nie wiesz (do czasu), że np. wyciąłeś ostatni warunek w WHERE.

Dlatego warto stosować komentarz /* comment */.

SQL Format: najlepsze blogi – inspiracje i podziękowania

Źródłem tego artykułu są głównie moje doświadczenia, ale przyznaje, że przeglądałem SQL styleguide innych analityków / stron.

O to te, które mogę polecić:

 Pobierz plik z gotowymi przykładami

Jeżeli zapisać sobie templatkę jak powinien wyglądać dobry styl, to poniżej przygotowałem dla Ciebie plik, z omówionymi wcześniej przykładami:

↪️ Podsumowanie

SQL Format, SQL Styleguide, styl SQL, czy jakkolwiek nazwiemy to pojęcie, to sposób w jaki piszesz SQL. Ważne w nim jest nie to tylko to, że działa. Liczy się to, że jesteś konsekwentny i nie tracisz czasu (swojego i innych).

Przypomnijmy:

  • czytelne nazwy
  • pojedynczy cudzysłów
  • żadnych spacji w kolumnach
  • != zamiast <>
  • przecinki na początku linii
  • white space i indentacja
  • poprawne aliasowanie
  • poprawne i świadome konstruowanie joinów
  • głównie CTE zamiast subquery
  • dobre stosowanie komentarzy

To tyle w tym temacie. Analizujcie w pokoju!  

Inne ciekawe artykuły:

Podobał Ci się ten artykuł?
Podziel się nim w Social Mediach:
>>> udostępnij go na LinkedIn i pokaż, że codziennie uczysz się czegoś nowego 
>>> wrzuć go na Facebooka, to się może przydać któremuś z Twoich znajomych 
>>> Przypnij sobie tą stronkę to zakładek, może się przydać w przyszłości

Wolisz oglądać niż czytać – nie ma problemu
>>> Obserwuj i oglądaj KajoData na YouTube

A jeśli chcesz się dowiedzieć czegoś więcej o SQL – zajrzyj tutaj.

Ja Ci ją z przyjemnością wyślę. Za darmo. Bez spamu.

Poradnik Początkującego Analityka

Video - jak szukać pracy w IT

Regularne dawki darmowej wiedzy, bez spamu.

Zgadzam się na przetwarzanie moich danych osobowych przez KajoData Kajo Rudziński w celu realizacji usługi newsletter, a tym samym wysyłania mi informacji o produktach blogowych, usługach, lub nowościach, zgodnie z polityką prywatności. Wiem, że zgodę tę mogę w każdej chwili cofnąć.

Please wait...

Dziękujemy za zapis!