TOP 10 najważniejszych zapytań SQL dla początkujących i średniozaawansowanych – praktyczny przewodnik

SQL
Analityka danych

Spis treści:

SQL (ang. Structured Query Language) to język zapytań stworzony do przetwarzania danych w relacyjnych bazach danych. W erze cyfrowej, gdzie niemal każda firma gromadzi ogromne ilości informacji, umiejętność posługiwania się SQL-em stała się jedną z kluczowych kompetencji. To narzędzie nie tylko dla specjalistów – analityków, programistów, inżynierów czy administratorów – ale także dla menedżerów, którzy chcą podejmować decyzje oparte na danych.

SQL jest stosunkowo prosty w nauce, ale oferuje ogromne możliwości – pozwala m.in. na pobieranie, filtrowanie, sortowanie, grupowanie, łączenie, agregowanie, analizowanie, wstawianie, aktualizowanie i usuwanie danych. Jego znajomość często jest wymagana podczas rekrutacji, a praktyczne zastosowanie znacznie przyspiesza codzienną pracę.

W tym przewodniku pokazujemy 10 praktycznych zapytań SQL, które można uruchamiać lokalnie w środowisku Microsoft SQL Server – idealnym do nauki i testowania. Wszystkie zapytania są szczegółowo opisane, z wyjaśnieniem mechanizmów działania. Omawiamy zarówno podstawowe instrukcje SELECT, jak i techniki bardziej zaawansowane: funkcje agregujące, operacje na datach, funkcje tekstowe i okna.

Zachęcamy do aktywnej nauki – uruchamiania, modyfikowania zapytań i eksperymentowania. Każde z nich to cegiełka budująca solidne podstawy. SQL to język, którego nauka szybko się zwraca – pozwala automatyzować zadania i odkrywać ukryte informacje. Co ważne, jego składnia jest w dużej mierze uniwersalna, co ułatwia pracę w różnych systemach baz danych (PostgreSQL, MySQL, Oracle, Snowflake itd.).

Wierzymy, że ten przewodnik pomoże Ci nie tylko opanować techniczne aspekty SQL, ale też spojrzeć na dane z perspektywy problemów biznesowych. Zaczynamy od podstaw, stopniowo przechodząc do poziomu średniozaawansowanego. Powodzenia!

Przygotowanie danych

Poniższy kod SQL krok po kroku tworzy bazę danych, 3 tabele oraz wstawia przykładowe dane idealne do nauki i testowania zapytań omówionych w tym poradniku. Na początku, poleceniem USE master; przełączamy się na systemową bazę master. Następnie sprawdzamy, czy testowa baza danych ImperiumSzkolenioweDB już istnieje — jeśli tak, zostaje ona usunięta za pomocą DROP DATABASE, po czym tworzymy ją na nowo. W dalszej części sprawdzamy, czy istnieją tabele Transakcje, Klienci oraz Pracownicy – jeśli tak, to są usuwane i tworzone na nowo. Tabela Transakcje zawiera informacje o kwocie, opisie i dacie transakcji oraz powiązanie z klientem przez kolumnę KlientID. Tabela Klienci przechowuje dane osobowe klientów. Tabela Pracownicy jest bardziej rozbudowana – zawiera dane związane ze strukturą organizacyjną: dane pracowników, ich działów, stanowisk, daty obowiązywania każdego pojedynczego rekordu w tabeli (EFF_DATE) oraz informację, czy dany rekord jest aktualny (IsCurrent). Następnie następuje wstawienie przykładowych danych. Na końcu kodu, trzy instrukcje SELECT * pozwalają podejrzeć wszystkie dane i przy okazji sprawdzić, czy zostały utworzone poprawnie. Jest to częsta i dobra praktyka w celach weryfikacyjnych. Całość poniższego kodu stanowi solidną bazę do testowania zagadnień omówionych w niniejszym poradniku.

-- utworzenie przykładowej bazy danych:
---------------------------------------
USE master;
GO
IF EXISTS (SELECT 1 FROM sys.databases WHERE name = 'ImperiumSzkolenioweDB')
BEGIN
    DROP DATABASE ImperiumSzkolenioweDB;
END;
GO
CREATE DATABASE ImperiumSzkolenioweDB;
GO
USE ImperiumSzkolenioweDB;
GO
-- utworzenie przykładowych tabel:
---------------------------------------
-- usunięcie tabel jeśli istnieją:
IF OBJECT_ID('ImperiumSzkolenioweDB.dbo.Transakcje') IS NOT NULL
BEGIN
	DROP TABLE ImperiumSzkolenioweDB.dbo.Transakcje;
END;
IF OBJECT_ID('ImperiumSzkolenioweDB.dbo.Klienci') IS NOT NULL
BEGIN
	DROP TABLE ImperiumSzkolenioweDB.dbo.Klienci;
END;
IF OBJECT_ID('ImperiumSzkolenioweDB.dbo.Pracownicy') IS NOT NULL
BEGIN
	DROP TABLE ImperiumSzkolenioweDB.dbo.Pracownicy;
END;
-- utworzenie tabeli Transakcje:
CREATE TABLE ImperiumSzkolenioweDB.dbo.Transakcje
(
    Id INT,
    Kwota NUMERIC(10,2),
    Opis VARCHAR(100),
    DataTransakcji DATETIME,
	KlientID INT
);
-- utworzenie tabeli Klienci:
CREATE TABLE ImperiumSzkolenioweDB.dbo.Klienci
(
    ID INT,
    Imie VARCHAR(100),
	Nazwisko VARCHAR(100)
);
-- utworzenie tabeli Pracownicy:
CREATE TABLE ImperiumSzkolenioweDB.dbo.Pracownicy
(
    PracownikID INT,
    Imie VARCHAR(50),
    Nazwisko VARCHAR(50),
    Dzial VARCHAR(50),
    Stanowisko VARCHAR(50),
    Wynagrodzenie NUMERIC(10,2),
    EFF_DATE DATE,
    IsCurrent CHAR(1)
);
-- wstawienie danych:
---------------------------------------
-- tabela Transakcje:
INSERT INTO ImperiumSzkolenioweDB.dbo.Transakcje 
(Id, Kwota, Opis, DataTransakcji, KlientID) 
VALUES
	(1, 100.00, 'Zakup A', '2025-05-01 10:00', 1),
	(2, 250.50, 'Zakup B', '2025-05-02 11:30', 1),
	(3, 75.00, 'Zakup C', '2025-05-03 09:15', 1),
	(4, 300.10, 'Zakup D', '2025-05-04 14:20', 1),
	(5, 150.75, 'Zakup E', '2025-05-05 16:00', 2),
	(6, 500.00, 'Zakup F', '2025-05-06 12:45', 2),
	(7, 60.00, 'Zakup A', '2025-05-07 08:30', 2),
	(8, 420.25, 'Zakup B', '2025-05-08 19:10', 2),
	(9, 80.80, 'Zakup C', '2025-05-09 07:50', 3),
	(10, NULL, 'Zakup D', '2025-05-10 20:00', 3),
	(11, NULL, 'Zakup E', '2025-05-11 13:05', 3),
	(12, 999.99, 'Zakup F', '2025-05-12 12:34', 3)
;
-- tabela Klienci:
INSERT INTO ImperiumSzkolenioweDB.dbo.Klienci (ID, Imie, Nazwisko)
VALUES
	(2, 'Jan', 'Brązowy'),
	(3, 'Piotr', 'Różowy'),
	(4, 'Magda', 'Zielona')
;
-- tabela Pracownicy:
INSERT INTO ImperiumSzkolenioweDB.dbo.Pracownicy (PracownikID, Imie, Nazwisko, Dzial, Stanowisko, Wynagrodzenie, EFF_DATE, IsCurrent)
VALUES
	(1, 'Jan', 'Kowalski', 'IT', 'Młodszy Programista', 4500.00,
 '2020-01-01', 'N'),
	(1, 'Jan', 'Kowalski', 'IT', 'Programista', 5500.00,
 '2021-07-01', 'N'),
	(1, 'Jan', 'Kowalski', 'IT', 'Starszy Programista', 6500.00,
 '2023-01-01', 'N'),
	(1, 'Jan', 'Kowalski', 'IT', 'Lider Zespołu', 8000.00,
 '2024-01-01', 'Y'),
	(2, 'Anna', 'Nowak', 'Finanse', 'Analityk', 5000.00,
 '2019-03-01', 'N'),
	(2, 'Anna', 'Nowak', 'Finanse', 'Starszy Analityk', 6000.00,
 '2021-01-01', 'N'),
	(2, 'Anna', 'Nowak', 'Finanse', 'Kierownik Finansowy', 7500.00,
 '2022-07-01', 'N'),
	(2, 'Anna', 'Nowak', 'Finanse', 'Dyrektor Finansowy', 9500.00,
 '2024-04-01', 'Y'),
	(3, 'Piotr', 'Wiśniewski', 'HR', 'Specjalista ds. HR', 5200.00,
 '2022-01-01', 'Y'),
	(4, 'Katarzyna', 'Dąbrowska', 'Marketing', 'Specjalista ds. Marketingu',
 4800.00, '2023-05-01', 'Y'),
	(5, 'Tomasz', 'Wójcik', 'Logistyka', 'Koordynator Logistyki', 4600.00,
 '2021-09-01', 'Y'),
	(6, 'Magdalena', 'Kaczmarek', 'Obsługa Klienta', 'Konsultant', 4200.00,
 '2023-11-01', 'Y')
;
-- podgląd danych:
---------------------------------------
SELECT * FROM ImperiumSzkolenioweDB.dbo.Transakcje;
SELECT * FROM ImperiumSzkolenioweDB.dbo.Klienci;
SELECT * FROM ImperiumSzkolenioweDB.dbo.Pracownicy;

Zachęcamy do zainstalowania Microsoft SQL Server i samodzielnego uruchamiania zapytań. Praktyczne testowanie kodu, jego modyfikowanie i obserwowanie wyników to najlepszy sposób na zrozumienie działania SQL i opanowanie go w praktyce.

Zapytanie 1 – wyświetlenie wybranych kolumn i nazwanie ich

SELECT 
Id AS [ID transakcji],
Kwota AS Kwota,
Opis 
FROM ImperiumSzkolenioweDB.dbo.Transakcje;

To podstawowe zapytanie SQL zwraca trzy kolumny z tabeli Transakcje: identyfikator transakcji, kwotę oraz opis. Użycie SELECT pozwala nam wskazać konkretne kolumny, które chcemy uzyskać zamiast pobierać wszystkie dane. Nie dodano klauzuli WHERE, więc zostaną zwrócone wszystkie rekordy. Brak sortowania oznacza, że kolejność zwracanych wierszy może być przypadkowa i zależna od silnika bazy danych. Jak łatwo zauważyć, skorzystaliśmy też z operatora AS aby nadać kolumnom nazwy. Można to zrobić na kilka sposobów. Pierwszy to wpisanie nowej nazwy zaraz po wspomnianym operatorze. Drugi to skorzystanie z kwadratowych nawiasów []. Trzeci sposób to skorzystanie z podwójnego cudzysłowie zamiast nawiasów, np. „ID transakcji” zamiast [ID transakcji]. W tym miejscu zachęcamy do zrobienia sobie przerwy w czytaniu i samodzielnego przetestowania 🙂

Należy pamiętać, że domyślnie kolumny są zwracane w takiej kolejności, w jakiej zostały zdefiniowane w SELECT. Dzięki temu zapytaniu użytkownik może szybko przeglądać dane znajdujące się w tabeli. Jest to najprostsza forma zapytania i doskonały punkt wyjścia dla dalszych analiz. Zastosowanie tej składni jest uniwersalne i działa w większości systemów bazodanowych.

Zapytanie 2 – ograniczenie przetwarzanych wyników

SELECT *
FROM ImperiumSzkolenioweDB.dbo.Transakcje
WHERE Kwota > 200;

Zapytanie wybiera wszystkie kolumny z tabeli Transakcje, ale tylko dla tych wierszy, gdzie wartość w kolumnie Kwota przekracza 200. Użycie symbolu > pozwala na porównanie wartości liczbowych i jest jedną z najczęściej wykorzystywanych metod filtrowania danych. Dzięki temu użytkownik może szybko zawęzić zestaw wyników do tych, które są dla niego najbardziej interesujące. SELECT * oznacza, że pobierane są wszystkie kolumny, jednak w praktyce warto wybierać tylko potrzebne dane, aby poprawić wydajność – o czym już wspomnieliśmy przy okazji omawiania zapytania 1. Klauzula WHERE w tym przypadku działa jako filtr i umożliwia bardziej precyzyjne dopasowanie wyników. Zapytanie jest bardzo intuicyjne i zrozumiałe nawet dla początkujących. Dzięki niemu możemy np. analizować tylko większe transakcje lub odrzucać niewielkie kwoty. Jest to pierwszy krok w stronę analizy danych, w której interesuje nas tylko fragment całego zbioru. Wartość 200 w tym przykładzie można dowolnie zmienić, zależnie od potrzeb. Klauzula WHERE może być łączona z AND, OR lub NOT w celu stworzenia bardziej złożonych warunków logicznych. W przypadku braku spełnienia warunku zapytanie zwróci pusty zestaw danych. Filtrowanie danych to kluczowy element każdej analizy, dlatego trzeba je dobrze opanować. Dobrze jest testować zapytania z różnymi warunkami, aby zrozumieć ich wpływ na wyniki. To zapytanie daje nam pierwszy kontakt z filtrowaniem danych i uczy logicznego myślenia w kontekście języka SQL.

Zapytanie 3 – sortowanie wyników zapytania

SELECT
Id,
Kwota
FROM ImperiumSzkolenioweDB.dbo.Transakcje
ORDER BY Kwota DESC, id ASC;

Zapytanie wybiera kolumny Id i Kwota i sortuje wyniki najpierw od największej do najmniejszej względem kolumny Kwota, a później od najmniejszej do największej względem kolumny ID. Operator ORDER BY umożliwia sortowanie danych według wskazanej kolumny lub kilku kolumn. Domyślnie jest stosowana kolejność rosnąca, co oznacza że jeśli pominiemy klauzulę ASC to dane i tak zostaną posortowane rosnąco. Klauzulę tą można jawnie wskazać dla lepszej czytelności zapytania. Sortowanie ułatwia analizę danych – możemy łatwo znaleźć najmniejsze i największe wartości. Jest to bardzo pomocne przy rankingach i progach oraz różnego rodzaju analizach. Umożliwia także przygotowanie danych do dalszych operacji. ORDER BY można stosować dla jednej lub wielu kolumn. W przypadku wielu kolumn kolejność sortowania ma znaczenie. Zapytanie jest przydatne przy przeglądaniu przeróżnych wartości liczbowych. Może też pomóc w wykrywaniu anomalii, jeśli np. jakaś kwota jest niespodziewanie niska. Sortowanie nie zmienia danych w tabeli, jedynie sposób ich prezentacji. ORDER BY może być kosztowne przy dużych zbiorach danych, więc warto zainteresować się tematem indeksów i optymalizacji. Jak widać na omawianym przykładzie, klauzulę ASC można zastąpić DESC dla porządku malejącego.

Szkolenie Język SQL (MS SQL)

900  netto

Szkolenie dedykujemy wszystkim rozpoczynającym pracę z językiem SQL. Dzięki szkoleniu poznasz jego...
Zobacz szkolenie

Zapytanie 4 – kategoryzacja kwoty transakcji

SELECT 
Id,
Kwota,
CASE 
WHEN Kwota IS NULL THEN 'brak danych'
WHEN Kwota < 100 THEN 'niska'
WHEN Kwota BETWEEN 100 AND 500 THEN 'średnia'
ELSE 'wysoka'
END AS [Kategoria Kwoty]
FROM ImperiumSzkolenioweDB.dbo.Transakcje;

Zapytanie zawiera identyfikator transakcji, kwotę oraz – przede wszystkim – dodatkowy opis, jaki poziom ma ta kwota – niski, średni, czy wysoki. Użyta została tutaj bardzo przydatna i popularna konstrukcja CASE, która działa podobnie jak „jeśli… to… a w przeciwnym wypadku…” w innych językach programowania. To oznacza, że możemy przypisać etykiety tekstowe na podstawie tego, czy określone warunki są spełnione.

Dla każdej transakcji sprawdzamy wartość pola Kwota. Jeśli jest pusta (czyli NULL), to zamiast zostanie wyświetlony tekst „brak danych”. Jeśli kwota jest mniejsza niż 100, będzie to tekst „niska”. Jeśli kwota jest między 100 a 500 – tekst „średnia”. Jeśli natomiast przekracza 500, zostanie oznaczona jako „wysoka”. Wynik działania tego zapytania to tabela, która zawiera identyfikator transakcji, jej kwotę oraz tekstową kategorię. Taki sposób kategoryzacji pozwala na lepsze zrozumienie danych – zamiast patrzeć tylko na liczby, od razu widzimy, do jakiej grupy należy każda transakcja. Może to być przydatne np. w raportach, analizach finansowych lub przy prezentacjach danych osobom nietechnicznym. CASE to bardzo elastyczne narzędzie – można z jego pomocą robić nie tylko etykiety tekstowe, ale też bardziej zaawansowane przekształcenia danych.

Zapytanie 5 - statystyki grupowe z funkcjami agregującymi

SELECT 
Opis,
SUM(Kwota) AS [Suma Kwot],
AVG(Kwota) AS [Średnia Kwota],
MIN(Kwota) AS [Najmniejsza Kwota],
MAX(Kwota) AS [Najwieksza Kwota],
COUNT(*) AS [Liczba Transakcji]
FROM ImperiumSzkolenioweDB.dbo.Transakcje
GROUP BY Opis
HAVING SUM(Kwota) > 300;

Zapytanie analizuje dane w tabeli Transakcje z wykorzystaniem funkcji agregujących. Funkcje te pozwalają tworzyć sumy, średnie, obliczać minima, maksima oraz łączną liczbę wierszy w podziale na określone kryterium. GROUP BY Opis oznacza, że będziemy liczyć to wszystko osobno dla każdego typu (opisu) transakcji, np. dla „Zakup A”, „Zakup B” itd.

W każdej z tych grup liczymy 5 wielkości: SUM(Kwota) – suma wszystkich kwot, AVG(Kwota) – średnia arytmetyczna, MIN(Kwota) – najmniejsza wartość, MAX(Kwota) – największa wartość oraz COUNT(*) – ile transakcji było w danym rodzaju zakupu. Dzięki temu możemy szybko zobaczyć, jak wyglądały różne rodzaje transakcji pod względem wartości.

Na końcu używamy HAVING, które działa podobnie jak WHERE, ale odnosi się do danych po agregacji. W tym przypadku filtrujemy tylko te grupy (opisy), których łączna kwota przekroczyła 300. Oznacza to, że jeśli np. wszystkie transakcje oznaczone jako „Zakup A”, „Zakup B” itd. miały razem równe 300 zł lub mniej, to taka grupa w ogóle nie zostanie wyświetlona.

To bardzo typowy przykład, jak analizować dane grupami – np. po produktach, miesiącach, pracownikach, kategoriach itp. Takie zapytania są podstawą wielu raportów biznesowych. HAVING często bywa mylone z WHERE, ale warto pamiętać, że dotyczy danych już po grupowaniu.

Zapytanie 6 – operacje na tekście

SELECT 
Id,
Opis,
UPPER(Opis) AS [Opis Wielkie Litery],
LOWER(Opis) AS [Opis Male Litery],
LEFT(Opis, 5) AS [Pierwsze 5 Znakow],
SUBSTRING(Opis, 3, 6) AS [Znaki od 3 do 6],
RIGHT(Opis, 1) AS [Ostatni 1 Znak]
FROM ImperiumSzkolenioweDB.dbo.Transakcje;

Zapytanie służy do manipulowania tekstem w kolumnie Opis, a dokładniej – pobieramy z niej różne wersje tego samego napisu lub jego fragment a następnie wyświetlamy na różne sposoby. Funkcja UPPER(Opis) zamienia cały tekst na wielkie litery, np. „zakup A” zmieni się na „ZAKUP A”. LOWER(Opis) robi odwrotnie – wszystko zostaje przekonwertowane na małe litery. LEFT(Opis, 5) pobiera pierwsze 5 znaków z tekstu, czyli tzw. początek napisu. Jak łatwo zauważyć, funkcja ta składa się z 2 elementów. Pierwszy to kolumna, z której będziemy chcieli wydzielić określoną liczbę znaków. Drugi to informacja, ile znaków ma zostać wydzielonych.  Podobnie działa RIGHT(Opis, 1) która w naszym przypadku pobiera i wyświetla ostatni znak. SUBSTRING(Opis, 3, 6) to trochę bardziej zaawansowana funkcja – wydzielanie zaczynamy od 3. znaku i pobieramy 6 kolejnych. Czyli jeśli napis to „Zakup A”, to wyświetlanym wynikiem będzie „kup A”.

Takie funkcje są bardzo przydatne, gdy chcemy pracować z tekstem – np. wyciągać prefiksy, inicjały, skróty, lub sprawdzać poprawność danych. To również przydaje się w czyszczeniu danych, np. usuwaniu zbędnych końcówek lub standaryzacji.

Zapytanie 7 – różne formaty dat

SELECT 
Id
,DataTransakcji
,CONVERT(VARCHAR(MAX), DataTransakcji, 101) AS [Data Tran. Format DD/MM/RRRR]
,CONVERT(VARCHAR(MAX), DataTransakcji, 104) AS [Data Tran. Format DD.MM.RRRR]
,CONVERT(VARCHAR(MAX), DataTransakcji, 106) AS [Data Tran. Format DD MMMM RRRR]
,CONVERT(VARCHAR(MAX), DataTransakcji, 112) AS [Data Tran. Format RRRRMMDD]
FROM ImperiumSzkolenioweDB.dbo.Transakcje;

W tym zapytaniu konwertujemy kolumnę z datą (DataTransakcji) – korzystamy z funkcji CONVERT, która pozwala zmienić sposób jej wyświetlania. W zależności od drugiego argumentu (np. 101, 104, itd.), data zostanie wyświetlona inaczej. Format 101 oznacza zapis DD/MM/YYYY, czyli dzień, miesiąc i rok oddzielone tzw. slashem. 104 to zapis DD.MM.YYYY, czyli z kropkami. 106 pokazuje miesiąc słownie, np. „01 May 2025”. 112 to tzw. format techniczny: YYYYMMDD, bez żadnych separatorów – bardzo przydatny przy sortowaniu lub nazwach plików. Dzięki temu można dostosować wyświetlanie daty do potrzeb użytkownika końcowego, eksportu danych czy systemów raportujących. W praktyce często musimy wyeksportować dane do Excela, gdzie oczekiwany jest konkretny format daty. Funkcja CONVERT pozwala to uporządkować.

Zapytanie 8 – obsługa wartości nieokreślonych NULL

SELECT 
Id,
Kwota,
CASE
	WHEN Kwota IS NOT NULL
	THEN Kwota
	ELSE 0
END AS KwotaBezNulla_1,
COALESCE(Kwota, 0) AS KwotaBezNulla_2,
ISNULL(Kwota, 0) AS KwotaBezNulla_3
FROM ImperiumSzkolenioweDB.dbo.Transakcje;

Czasem w bazach danych niektóre wartości są puste, nieokreślone – czyli mają wartość NULL. Dla liczb, np. kwot, może to powodować błędy przy liczeniu. To zapytanie pokazuje 3 sposoby, jak zamienić NULL na 0.

Pierwszy to klauzula CASE: jeśli kolumna Kwota nie jest pusta (IS NOT NULL), to zostawiamy jej wartość, w przeciwnym razie wyświetlamy 0. Drugi to COALESCE, który działa tak, że wybiera pierwszy niepusty argument – czyli jeśli Kwota jest pusta, to zwraca 0. Trzeci sposób to ISNULL, który działa bardzo podobnie do COALESCE. Wszystkie te sposoby dają ten sam efekt – w miejscach, gdzie była pusta kwota, pojawi się 0.

Zapytanie 9 – złączenia tabel JOIN

SELECT
t.DataTransakcji
,t.Kwota
,t.KlientID
,k.Imie AS [Imię Klienta]
,k.Nazwisko AS [Nazwisko Klienta]
FROM ImperiumSzkolenioweDB.dbo.Transakcje as t
LEFT JOIN ImperiumSzkolenioweDB.dbo.Klienci as k
ON k.ID = t.KlientID;

Zapytanie łączy dwie tabele: Transakcje i Klienci – po to, aby wyświetlić dane o transakcjach razem z informacjami o osobach, które je wykonały. Łączymy je razem za pomocą klauzuli LEFT JOIN, czyli „złączenia zewnętrznego po lewej stronie”. To oznacza, że chcemy zobaczyć wszystkie wiersze z tabeli Transakcje (tabela główna) oraz tylko te z tabeli Klienci, dla których zostanie znalezione dopasowanie po kolumnach wykorzystanych w klauzuli ON. Tymi kolumnami jest KlientID w tabeli Transakcje oraz ID w tabeli Klienci. Jeśli dla danego KlientID znajdzie się klient, to zapytanie pokaże jego imię i nazwisko. Jeśli nie – pola Imię i Nazwisko będą puste (NULL). Dzięki temu widzimy wszystkie transakcje, niezależnie od tego, czy klient został zidentyfikowany w drugiej tabeli. Alias t oznacza tabelę Transakcje, a k to alias dla Klienci. Dzięki temu kod jest krótszy i czytelniejszy – np. zamiast pisać Transakcje.DataTransakcji, wystarczy t.DataTransakcji.

To zapytanie daje bardzo praktyczny wynik: lista transakcji wraz z imieniem i nazwiskiem klienta. Pozwala to lepiej zrozumieć, kto co kupował i kiedy. Gdybyśmy nie użyli JOIN, mielibyśmy tylko numery ID – bez danych osobowych. JOINy to w zapytaniach SQL absolutna podstawa. Bez nich nie da się analizować danych z różnych tabel. W tym miejscu zachęcamy do krótkiej przerwy w czytaniu artykułu i sprawdzeniu jak zmienią się wyświetlane wyniki jeśli zamiast LEFT JOIN zastosujemy:

1) RIGHT JOIN

2) FULL JOIN

3) JOIN, tzn. zwykłe złączenie bez klauzul LEFT, RIGHT ani FULL

Zapytanie 10 – najnowszy wpis dla każdego pracownika

WITH _cte_Pracownicy AS
(
	SELECT 
		*,
		ROW_NUMBER() OVER (PARTITION BY PracownikID 
ORDER BY EFF_DATE DESC) AS RN 
FROM ImperiumSzkolenioweDB.dbo.Pracownicy
)
SELECT *
FROM _cte_Pracownicy
WHERE RN = 1;

To zapytanie wykorzystuje technikę CTE (ang. Common Table Expression) i klauzulę WITH oraz popularną funkcję okna ROW_NUMBER, żeby znaleźć najnowszą wersję danych historycznych dla każdego pracownika. Tabela Pracownicy przechowuje dane w stylu SCD2 (ang. Slowly Changing Dimensions) – czyli każda zmiana danych (np. awans lub nowa pensja) trafia do niej jako nowy wiersz.

Pierwszy krok to zdefiniowanie CTE – czyli tymczasowej tabeli nazwanej w naszym przypadku jako _cte_Pracownicy. W jej wnętrzu używamy funkcji ROW_NUMBER() z klauzulą OVER (…), żeby ponumerować wiersze dla każdego pracownika osobno (PARTITION BY PracownikID), w kolejności od najnowszej daty (EFF_DATE DESC). Wynik: dla każdego pracownika wiersze zostają posortowane według daty, a do najnowszego przypisany zostaje numer 1 (RN = 1). Następnie, już poza CTE, pobieramy z niej tylko te rekordy, które mają RN = 1, czyli właśnie najnowsze dane dla każdego pracownika. Dzięki temu nie musimy ręcznie szukać ostatnich wersji wpisów – wszystko liczy się automatycznie. Jest to bardzo wygodna metoda w analizie danych typu „historyczne zmiany”, czyli takich, gdzie np. pensja lub stanowisko były aktualizowana kilka razy.

To zapytanie nie tylko wyciąga najnowsze dane, ale też działa dynamicznie – jeśli jutro dodamy nowy wiersz z aktualizacją, wynik zmieni się sam. To bardzo dobre podejście, gdy dane są dynamiczne i często aktualizowane. CTE w połączeniu z klauzulą ROW_NUMBER() to jedna z podstawowych technik w języku SQL do wyciągania najlepszych, ostatnich lub pierwszych wyników z większych grup.

Dobre praktyki pisania zapytań SQL

Pisząc zapytania SQL, warto kierować się kilkoma dobrymi praktykami, które sprawiają, że kod jest bardziej czytelny, wydajny i łatwiejszy w utrzymaniu. Przede wszystkim należy je pisać zrozumiale, stosując odpowiednie wcięcia i formatowanie – to nie tylko estetyka, ale duże ułatwienie przy analizie. Bez wcięć kod będzie dużo mniej czytelny, zwłaszcza jeśli liczy setki lub tysiące linijek. Nazwy tabel i kolumn powinny być czytelne i jednoznaczne. Warto stosować aliasy tabel i kolumn, ale tylko wtedy, gdy to rzeczywiście ułatwia zrozumienie. W bardziej złożonych zapytaniach dobrze jest używać CTE (WITH) zamiast zagnieżdżonych podzapytań – kod staje się modularny i przejrzysty. Zamiast SELECT *, lepiej wskazywać konkretne kolumny – zwiększa to wydajność i czytelność. W zapytaniach z JOIN należy jawnie wskazywać warunki połączenia. Każde WHERE, GROUP BY, HAVING czy ORDER BY powinno być logicznie uporządkowane i spójne z tym, co chcemy osiągnąć. Filtrowanie danych warto wykonywać jak najwcześniej, aby ograniczyć ilość przetwarzanych rekordów. Przy pracy z NULL-ami dobrze jest korzystać z COALESCE, ISNULL lub CASE, aby uniknąć błędów logicznych. Warto pamiętać, że NULL nie oznacza zera ani pustego tekstu – tylko brak danych. Przy agregacjach trzeba pamiętać, że wartości NULL są ignorowane. Funkcje tekstowe (LEFT, RIGHT, UPPER, LOWER) warto używać z rozwagą, zwłaszcza jeśli chodzi o indeksowane kolumny. Dobrą praktyką jest też ciągłe testowanie wyników – najlepiej na małych zbiorach danych, zanim wrzucimy zapytanie na duży system. Na koniec, zawsze warto przetestować zapytanie z różnymi danymi wejściowymi, żeby mieć pewność, że działa poprawnie w każdych warunkach.

Podsumowanie

W omówionych wcześniej zapytaniach poruszyliśmy wiele kluczowych aspektów pracy z SQL – od klasyfikacji danych, przez agregacje, funkcje tekstowe, konwersję dat, obsługę NULL-i, aż po łączenie tabel i analizę danych historycznych. Każde z nich pokazuje, że SQL to nie tylko język zapytań do baz danych, ale też potężne narzędzie analityczne. Zrozumienie przedstawionych przykładów to nie tylko nauka składni, ale przede wszystkim sposobu myślenia. SQL to język logiczny – pomaga zadawać pytania do danych i otrzymywać sensowne odpowiedzi. Dzięki niemu potrafimy wyciągnąć najnowszy rekord, wyczyścić dane, zagregować je czy przekształcić – a to wszystko w jednym zapytaniu. Znajomość SQL to jedna z najważniejszych kompetencji analitycznych, niezależnie od branży. Umiejętność pisania zapytań sprawia, że stajesz się niezależny od zespołu IT – samodzielnie analizujesz dane i budujesz raporty. Co ważne – raz nauczone zapytania można stosować w takich programach jak: MySQL, PostgreSQL, Oracle, Snowflake czy dowolnym innym do pracy z bazami danych. Najwięcej drogi czytelniku nauczysz się nie czytając, ale samodzielnie testując i eksperymentując z zapytaniami – więc uruchamiaj kod i sprawdzaj, co robi 🙂

Podobne artykuły

Wszystkie artykuły