10 najczęstszych błędów w SQL i jak ich unikać

Microsoft Office
SQL
Analityka danych

Spis treści:

W świecie baz danych język SQL (Structured Query Language) to podstawowe narzędzie do pracy z danymi – zarządzania, sortowania, filtrowania i analizowania na przeróżne sposoby. Praca nawet z bardzo prostymi zapytaniami wymaga wbrew pozorom dużej dokładności. Kilkulinijkowy kod potrafi prowadzić do błędów, a przy dużych lub krytycznych systemach nawet drobna pomyłka może skutkować poważną utratą wydajności, niewłaściwymi wynikami sprzedażowymi lub nieoczekiwanymi skutkami ubocznymi takie jak straty finansowe lub utracone korzyści.

Poniżej opisujemy 10 najczęściej popełnianych błędów – od trywialnych i – na pierwszy rzut oka – bardzo prostych, aż po głębsze problemy logiczne. Jednocześnie chcemy podpowiedzieć, jak ich unikać. Dzięki temu materiałowi będziesz w stanie łatwiej diagnozować swoje zapytania SQL, budować lepszy kod i – mamy nadzieję – podzielić się dobrymi praktykami z kolegami i koleżankami z pracy aby również unikali podobnych błędów. Zależy nam, aby Twoja praca z językiem SQL była możliwie przyjemna i efektywna, zatem do dzieła 😊

1. Używanie SELECT * zamiast listy kolumn

Popularny błąd, zwłaszcza przy prototypowaniu – używamy SELECT *, bo jest to szybsze i wygodniejsze niż wypisywanie kolumn. Jednak w praktyce:

 

  • Pobieramy dużo więcej danych niż potrzebujemy – i w ten sposób dodatkowo obciążamy bazę danych zużywając na wykonanie zapytania dużo więcej zasobów, niż w rzeczywistości jest potrzebne.
  • Jeśli struktura tabeli się zmieni (dodamy lub usuniemy kolumny), zapytanie przestaje być przewidywalne. Wyobraź sobie sytuację, kiedy komenda SELECT * jest wcześniej wykorzystywana w klauzuli INSERT, innymi słowy – wyniki tego zapytania lądują w tabeli w bazie danych. W najlepszym wypadku takie zapytanie zwróci błąd, w najgorszym –dane trafią do nieodpowiednich kolumn a skutki tego mogą być opłakane.
  • Trudniej też wyfiltrować konkretne kolumny lub ustawić aliasy.

 

Jak zapobiegać? Poświęćmy dodatkowo te kilka sekund i zawsze wypiszmy kolumny, które faktycznie są nam potrzebne. Jeśli używasz SELECT * przy szybkim testowym zapytaniu – pamiętaj, by usunąć go podczas wgrywania zapytania na wyższe środowiska – zwłaszcza produkcję. W zespołach warto ustalić politykę w stylu: „nie używamy SELECT * nigdzie poza testami”.

2. Błędy składni

Pozornie trywialne – brakujący przecinek lub o jeden za dużo, średnik lub przecinek w złym miejscu, średnik zamiast przecinka, literówka w nazwie tabeli lub kolumny, brak nawiasu lub niepoprawny nawias – to wszystko może generować błędy kompilacji albo prowadzić do logicznie niepoprawnych zapytań.

Jak zapobiegać?

 

  • Korzystaj z formatowania i kolorowania w SQL Server Management Studio.
  • Używaj autouzupełniania IntelliSense – to bardzo pomocny mechanizm niezależnie od tego na jakim poziomie zaawansowania jesteś.

 

Ponadto, wybierając zakładkę Tools i przechodząc do sekcji Options można zdefiniować własne kolory czcionek, a nawet ustawić kolor tła.

Dowiedz się więcej na temat MS SQL z naszych kursów:
  • Szkolenie Język SQL (MS SQL)

    Szkolenie dedykujemy wszystkim rozpoczynającym pracę z językiem SQL. Dzięki szkoleniu poznasz jego fundamenty oraz zależności rząd...
    Dowiedz się więcej
  • Język SQL -poziom zaawansowany...

    Szkolenie SQL w SQL Server poziom zaawansowany dedykowane jest osobom znającym podstawy zapytania SQL oraz pragnącym poszerzyć wie...
    Dowiedz się więcej
  • SQL w Oracle – poziom podstawo...

    Szukasz pracy w korporacji? A może jesteś pracodawcą lub pracownikiem działu HR i szukasz szkolenia rozwijającego kompetencje prac...
    Dowiedz się więcej
  • SQL w Oracle – poziom zaawanso...

    Szkolenie SQL w Oracle na poziomie zaawansowanym dedykowane jest osobom znającym już podstawy języka SQL i pragnącym poszerzyć wie...
    Dowiedz się więcej

3. Nadmiernie skomplikowane zapytania zamiast podzielenia ich na mniejsze kroki

Na czym polega ten problem? Załóżmy że masz do przygotowania zapytanie SELECT pobierające 50 kolumn z 20 różnych tabel. Ponadto, Twój kod będzie składał się z kilku JOIN‑ów, podzapytań, złożonych agregacji i zaawansowanych funkcji. Zamiast zacząć krok po kroku i co jakiś czas testować zapytanie, od razu skaczesz na głęboką wodę i piszesz cały kod od A do Z pomijając wspomniane testy po drodze. A później okazuje się, że w złym miejscu postawiłeś przecinek (spójrz na błąd numer 2) i znalezienie błędu zajmuje Ci kolejną godzinę, dwie lub dłużej.

Ponadto takie zapytanie, szczególnie jeśli nie stosujesz komentarzy (więcej o tym poniżej) będzie trudne do zrozumienia. Łatwo stracić kontrolę nad logiką i nie zauważyć błędnych filtrów, duplikacji wyników albo pobrania niewłaściwych kolumn, które w dalszej części zapytania nie są wykorzystywane (spójrz na błąd numer 1). Złożoność utrudnia też debugowanie kodu podczas gdy mniejsze fragmenty łatwiej można testować, porównywać i debugować.

Jak zapobiegać?

 

  • Bardzo często łatwiej wykonywać i testować takie złożone zapytania etapowo – z CTE (Common Table Expressions) i klauzulą WITH lub przy użyciu tabel tymczasowych. Rozbijaj skomplikowane zapytania właśnie w taki sposób.
  • Stosuj zrozumiałe aliasy które będą pomocne przy ewentualnych analizach.
  • Używaj zapytań pośrednich zamiast gigantycznych SELECT‑ów.
  • Jeśli coś zajmuje więcej niż 15-20 linijek, zrób tabelę tymczasową lub etapową analizę.

 

Ponadto, jeśli zdecydujesz się na skorzystanie z tabel tymczasowych to nie zapomnij ich skasować w momencie jak już nie będą potrzebne.

4. Niewłaściwe operaty logiczne w JOIN i brak warunków ON

To klasyka – zapomnienie warunku w INNER JOIN i w rezultacie użycie CROSS JOIN:

SELECT *

FROM Employees e

JOIN Departments d; 

 

Nie trudno zauważyć, że w powyższym kodzie brakuje warunku ON. Rezultat? Zapytanie mnoży rekordy kartezjańsko – każdy pracownik łączy się z każdym działem. Dla uproszczenia założmy, że tabela Employees liczy 5 unikalnych pracowników, a tabela Departments tyle samo unikalnych działów. Załóżmy też, że poprzez takie złączenie chcieliśmy przypisać wspomnianym pracownikom nazwę działu. Przez brak warunku ON, zamiast otrzymać 5 wierszy w zapytaniu wynikowym, będzie ich 5 * 5 = 25. To dlatego że każdy pracownik zostanie połączony z każdym działem. Podany przykład dotyczy bardzo małych zbiorów danych, a co w sytuacji jak pracowników byłoby np. 20000-30000 lub więcej? 😊

Jak zapobiegać?

 

  • Zawsze używaj aliasów i warunku ON. Dzięki temu nie tylko unikniesz generowania nadmiarowych i niepotrzebnych wyników, ale Twój kod będzie też bardziej czytelny i zrozumiały:

 

SELECT

e.Name AS EmployeeName

,d.Name AS DepartmentName

FROM Employees AS e

JOIN Departments AS d

ON e.DepartmentID = d.ID;
  • Praktykuj kodowanie – niech to będzie jedna z dobrych praktyk, jakie zastosujesz jak najszybciej po przeczytaniu tego fragmentu 😊

5. Brak aliasów, nie tylko w JOIN-ach

O tym już co nieco wspominaliśmy omawiając poprzednie błędy. Rozwijając ten wątek – odwoływanie się do tabel i kolumn bez aliasów łatwo prowadzi do nieczytelnego, długiego i zbędnego kodu:

 

SELECT

Employees.Name

,Departments.Name

FROM Employees

JOIN Departments

ON Employees.DepartmentID = Departments.ID;

 

Aliasy pozwalają skrócić i uporządkować kod, zdecydowanie poprawiają też jego czytelność. Zwróć uwagę, że stosujemy je zarówno przy nazwach kolumn jak i tabel:

SELECT

e.Name AS EmployeeName

,d.Name AS DepartmentName

FROM Employees AS e

JOIN Departments AS d

ON e.DepartmentID = d.ID;

 

Taki zabieg w kodzie ma szereg korzyści:

 

  • Poprawia czytelność zapytań.
  • Zapobiega konfliktom przy JOINach i wielotabelowych zapytaniach – zwłaszcza w sytuacji, kiedy w kilku tabelach występuje kolumna o tej samej nazwie.

6. Używanie DELETE lub UPDATE bez klauzuli WHERE

Pewnie się domyślasz jakie mogą być skutki takich operacji. Jeden nieuważny ruch, brak filtra i nadpisujesz lub tracisz wszystkie dane:

UPDATE Employees SET Salary = 0;

Inny przykład:

DELETE FROM Orders;

To otwarta droga do katastrofy bo wpływ operacji jest całkowity i odwracalny często dopiero z kopii zapasowej bazy danych (backupu) o ile w ogóle nią dysponujemy.

Jak zapobiegać?

 

  • Zawsze wymagaj WHERE przy operacjach DML (Data Manipulation Language) obejmujących takie komendy jak INSERT, UPDATE, DELETE.
  • Przed wykonaniem operacji DML wykonaj wstępne testy, np. przez SELECT … FROM … WHERE.
  • Stosuj obsługę transakcji aby dodatkowo mieć możliwość wyboru czy chcesz zatwierdzić czy wycofać wprowadzone zmiany. BEGIN TRANSACTION/TRAN a następnie ROLLBACK TRANSACTION/TRAN jeśli chcesz wycofać swoje zmiany lub COMMIT TRANSACTION/TRAN w przypadku gdy chcesz je zatwierdzić. W tym miejscu dodatkowa uwaga – klauzule te zadziałają w programie Microsoft SQL Server. W innych systemach zarządzania bazami danych takimi jak np. Oracle, PostgreSQL lub Snowflake mogą działać nieco inaczej.

7. Złe typy danych, np. VARCHAR dla liczb

Dopasowanie typu kolumny do danych jest kluczowe, a zastosowanie VARCHAR do wartości liczbowych może wydłużyć czas dostępu do danych i powoduje:

 

  • większe użycie pamięci i fragmentację.
  • problemy z porównywaniem (‘20’ większe od ‘4x’?).
  • utrudnioną optymalizację i brak możliwości wykorzystania indeksów.

 

Jak zapobiegać? Przede wszystkim stosuj odpowiedni typ dla danych w danej kolumnie:

 

  • Dla liczb: INT, BIGINT, NUMERIC lub DECIMAL.
  • Dla dat: DATE, DATETIME lub DATETIME2 (te typy danych również obowiązują tylko w Microsoft SQL Server, odpowiednikiem DATETIME w wielu innych jest TIMESTAMP).
  • VARCHAR dla kolumn przechowujących tekst (np. imiona i nazwiska pracowników, nazwy działów) lub wyłącznie wtedy, gdy naprawdę potrzebujesz.

8. Źle napisany GROUP BY/HAVING

Idealny przykład kolejnego i często popełnianego błędu:

 

  • HAVING stosujesz do filtrowania zagregowanych danych a nie surowych– do tego służy WHERE.
  • Brak GROUP BY na wszystkich kolumnach, które nie są w funkcjach agregujących.
  • Nieskoordynowane zapytania – SELECT Col1, SUM(Col2) bez GROUP BY Col1.

 

W efekcie otrzymujesz też błędy składni o których wspominaliśmy na początku tego artykułu. Jak zapobiegać?

 

  • W pierwszej kolejności sprawdź, które kolumny podlegają agregacji, a które nie.
  • Używaj HAVING dopiero po GROUP BY, np.:
SELECT

DepartmentID,

COUNT(*) AS EmployeesCount

FROM Employees

GROUP BY DepartmentID

HAVING COUNT(*) > 10;

9. Brak komentarzy i dokumentacji zapytań, procedur i utworzonych funkcji

SQL to kod, który bardzo często żyje latami – również w sytuacji, gdy dostaniesz lepszą ofertę pracy i odejdziesz z firmy. Już po kilku tygodniach trudno przypomnieć sobie intencje w jakich był tworzony, a co dopiero po miesiącach lub latach. Sytuacja się komplikuje, jeśli rozmawiamy o długim i skomplikowanym kodzie czy złożonych procedurach lub funkcjach. Aby zaoszczędzić sobie czasu, nerwów i bólu głowy, powinniśmy stosować komentarze. Takie rozwiązanie ułatwia też zrozumienie kodu naszym koleżankom i kolegom z zespołu, jeśli przyjdzie im z nim pracować.

 

Jak zapobiegać? Komentować to co się robi 😊

/* procedura obliczająca średnią sprzedaż na pracownika w regionie */

CREATE PROCEDURE sp_Get_Avg_Sales_Per_Employee

@RegionID INT

AS

BEGIN

SET NOCOUNT ON;

SELECT

AVG(TotalAmount)/COUNT(DISTINCT EmployeeID)

FROM Sales

WHERE RegionID = @RegionID;

SET NOCOUNT OFF;

END

 

Lub jeszcze dokładniej:

/* procedura obliczająca średnią sprzedaż na pracownika w regionie */

CREATE PROCEDURE sp_Get_Avg_Sales_Per_Employee

@RegionID INT

AS

BEGIN

SET NOCOUNT ON; -- tymczasowe wyłączenie przeliczania wierszy

/* obliczenie średniej sprzedaży */

SELECT

AVG(TotalAmount)/COUNT(DISTINCT EmployeeID)

FROM Sales

WHERE RegionID = @RegionID;

SET NOCOUNT OFF; -- włączenie przeliczania wierszy

END

 

Korzyści:

 

  • Za pół roku Ty i Twoi koledzy z zespołu szybko zrozumiecie cel prodecury.
  • Programiści i bazodanowcy szybciej identyfikują wąskie gardła i rozumieją, co ma robić kod.
  • Powyższy przykład pokazuje prostą procedurę. Korzyści opisane powyżej są jeszcze bardziej zauważalne w przypadku przypadków liczących kilkaset linijek kodu.

10. Brak indeksów lub złe indeksowanie

Dochodzimy do etapu optymalizacji. Indeks w bazach danych to rodzaj spisu, który może przyspieszyć dostęp do danych i wykonanie zapytania. Brak indeksów, zwłaszcza jeśli często przetwarzamy setki tysięcy lub miliony danych to jeden z największych wydajnościowych killerów. Gdy nasza tabelka regularnie jest zasilana coraz to nowymi danymi, zapytanie przestaje działać lub wykonuje się zdecydowanie wolniej.

 

  • Table scan zamiast index seek dla dużych tabel.
  • Za dużo indeksów – spowolnienie operacji wstawiania, aktualizowania i usuwania danych (DML).
  • Niepoprawny typ indeksu – brak użycia go w planie i dodatkowe zajmowane miejsce w bazie danych.

 

Żeby lepiej zrozumieć jak działają indeksy to wyobraź sobie dwie wersje książki telefonicznej:

 

  • Wersja A: 50-stronna książka oprawiona w okładkę zawierająca spis treści oraz nazwiska posortowane alfabetycznie
  • Wersja B: sterta 50 stron z książki telefonicznej wyrwanych i wymieszanych ze sobą i bez posortowanych alfabetycznie nazwisk – najpierw mamy stronę, 10, potem 30, następnie 8 itd.

 

Tabele bez indeksów są naszą książką telefoniczną w wersji B, a z poprawnie założonymi indeksami – w wersji A.

Jak zapobiegać?

 

  • Stosować indeksy na kolumny sortowane, filtrowane i używane w złączeniach tabel.
  • Używać i analizować plany wykonania zapytań oraz takich narzędzi jak Activity Monitor, Dynamic Management Views i Query Store (dotyczy Microsoft SQL Server).
  • Regularnie analizować użyteczność indeksów.
  • I przede wszystkim – zacząć uczyć się optymalizacji 😊

Podsumowanie

SQL to niesamowite i wbrew pozorom proste narzędzie. Możemy w nim zrobić bardzo dużo, od prostego SELECT a po skomplikowane transformacje i przekształcenia danych. Każdy z nas potrafi popełnić prosty błąd: literówkę, nie założyć filtra, dobrać zły typ danych, zapomnieć o przecinku, postawić przecinek w złym miejscu lub zapomnieć o indeksach. Dlatego tak ważne jest, aby:

 

  • zachować dokładność, dyscyplinę i formatowanie kodu,
  • sprawdzać kod 2-3 razy przed przekazaniem go dalej,
  • korzystać z aliasów, komentarzy i przyjaznej konwencji,
  • rozdzielać skomplikowane zadania na mniejsze składniki,
  • uważać przy wykonywaniu „ryzykownego” kodu (np. aktualizacje lub usunięcia danych) i stosować transakcje,
  • korzystać z możliwości indeksów.

 

Dzięki temu Twój kod stanie się bardziej czytelny, bezpieczny i wydajny — co oznacza lepsze aplikacje, zadowolonych klientów, mniej „pożarów” do gaszenia w przyszłości oraz spokojniejszą pracę i więcej satysfakcji z niej.

Podobne artykuły

Wszystkie artykuły