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
- Bądź konsekwentny.
- Używaj czytelnych nazw.
- 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
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:
- Co to jest CTE?
- WHERE czy GROUP BY?
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.