INF.03 · Moduł 19 · Klasa 4 TI

SELECT & JOIN

Wyszukiwanie i łączenie danych w SQL — na przykładzie bazy GameVault

SELECT INNER JOIN LEFT JOIN GROUP BY podzapytania HAVING
📘

Teoria — kluczowe polecenia

SELECT ... FROM ... WHERE

Podstawowe zapytanie pobierające dane. SELECT wskazuje kolumny, FROM — tabelę, a WHERE — warunek filtrowania rekordów.

INNER JOIN

Zwraca tylko te rekordy, które mają dopasowanie w obu tabelach. Jeśli rekord w jednej tabeli nie ma pary — jest pomijany.

LEFT JOIN

Zwraca wszystkie rekordy z tabeli lewej, nawet jeśli nie mają dopasowania w tabeli prawej. W takich przypadkach kolumny z prawej tabeli zawierają NULL.

GROUP BY + HAVING

GROUP BY grupuje wiersze o tych samych wartościach. HAVING filtruje grupy (jak WHERE, ale po agregacji).

Podzapytania (subquery)

Zapytanie zagnieżdżone wewnątrz innego — najczęściej w klauzuli WHERE ... IN (...). Wynik podzapytania jest używany przez zapytanie zewnętrzne.

Tabela porównania JOIN

Typ JOIN Zwraca Rekordy bez pary Kiedy używać?
INNER JOIN Tylko rekordy powiązane w obu tabelach ✗ pominięte Gdy potrzebujesz tylko kompletnych danych
LEFT JOIN Wszystkie z tabeli lewej + dopasowane z prawej ✓ NULL w prawej Gdy chcesz znaleźć "sieroty" lub zachować wszystkich
RIGHT JOIN Wszystkie z tabeli prawej + dopasowane z lewej ✓ NULL w lewej Rzadko używany — zazwyczaj zastępuje LEFT JOIN
🎮

Baza danych: GameVault

Wszystkie ćwiczenia korzystają z bazy GameVault — sklepu z grami cyfrowymi. Mamy cztery powiązane tabele:

🧑 gracze
gracz_idINT PK
nickVARCHAR
emailVARCHAR
krajVARCHAR
🎮 gry
gra_idINT PK
tytulVARCHAR
gatunekVARCHAR
cenaDECIMAL
wydawca_idFK
🛒 zakupy
zakup_idINT PK
gracz_idFK
gra_idFK
data_zakupuDATE
cena_zakupuDECIMAL
🏢 wydawcy
wydawca_idINT PK
nazwaVARCHAR
krajVARCHAR
SQL · GAMEVAULT INIT
-- Stwórz bazę i wybierz ją
CREATE DATABASE gamevault;
USE gamevault;

-- Tabela wydawców
CREATE TABLE wydawcy (
    wydawca_id INT PRIMARY KEY AUTO_INCREMENT,
    nazwa      VARCHAR(100) NOT NULL,
    kraj       VARCHAR(50)
);

-- Tabela gier
CREATE TABLE gry (
    gra_id     INT PRIMARY KEY AUTO_INCREMENT,
    tytul      VARCHAR(100) NOT NULL,
    gatunek    VARCHAR(50),
    cena       DECIMAL(7,2) NOT NULL,
    wydawca_id INT,
    FOREIGN KEY (wydawca_id) REFERENCES wydawcy(wydawca_id)
);

-- Tabela graczy
CREATE TABLE gracze (
    gracz_id INT PRIMARY KEY AUTO_INCREMENT,
    nick     VARCHAR(50) NOT NULL UNIQUE,
    email    VARCHAR(100),
    kraj     VARCHAR(50)
);

-- Tabela zakupów
CREATE TABLE zakupy (
    zakup_id   INT PRIMARY KEY AUTO_INCREMENT,
    gracz_id   INT, 
    gra_id     INT,
    data_zakupu DATE DEFAULT (CURRENT_DATE),
    cena_zakupu DECIMAL(7,2),
    FOREIGN KEY (gracz_id) REFERENCES gracze(gracz_id),
    FOREIGN KEY (gra_id)   REFERENCES gry(gra_id)
);

-- Dane testowe
INSERT INTO wydawcy (nazwa, kraj) VALUES
    ('CD Projekt', 'PL'), ('Valve', 'US'), ('IndieSoft', 'DE');

INSERT INTO gry (tytul, gatunek, cena, wydawca_id) VALUES
    ('Cyberpunk 2077', 'RPG', 199.99, 1),
    ('Half-Life: Alyx', 'FPS', 149.99, 2),
    ('Portal 2', 'Puzzle', 39.99, 2),
    ('PixelDungeon', 'RPG', 19.99, 3),
    ('SpeedRacer VR', 'Racing', 89.99, 3);

INSERT INTO gracze (nick, email, kraj) VALUES
    ('ShadowWolf', 'wolf@gm.pl', 'PL'),
    ('NeonByte', 'neon@gm.us', 'US'),
    ('PixelQueen', 'queen@gm.de', 'DE'),
    ('NoobMaster', 'noob@gm.pl', 'PL');

INSERT INTO zakupy (gracz_id, gra_id, data_zakupu, cena_zakupu) VALUES
    (1, 1, '2024-11-01', 199.99),
    (1, 3, '2024-11-15', 39.99),
    (2, 2, '2024-12-01', 149.99),
    (3, 4, '2025-01-10', 19.99),
    (3, 1, '2025-01-20', 199.99);
-- NoobMaster (gracz_id=4) nie ma żadnych zakupów
⌨️

Ćwiczenia krok po kroku

01
Podstawowy SELECT z filtrem i sortowaniem
WHERE · ORDER BY
ZADANIE
Wyświetl tytuł i cenę wszystkich gier z gatunku RPG, posortowanych od najtańszej do najdroższej. Pokaż tylko gry tańsze niż 150 zł.
SQL
SELECT tytul, cena
FROM   gry
WHERE  gatunek = 'RPG'
  AND  cena < 150
ORDER BY cena ASC;
▸ WYNIK ZAPYTANIA
tytulcena
PixelDungeon19.99
💡 Cyberpunk 2077 kosztuje 199.99 zł — przekracza limit 150 zł, więc nie pojawia się w wyniku.
02
INNER JOIN — gracze i ich zakupy
INNER JOIN
ZADANIE
Wyświetl nick gracza i tytuł każdej kupionej przez niego gry. Posortuj według nicka.
SQL
SELECT   g.nick,
         gr.tytul
FROM     gracze g
INNER JOIN zakupy z  ON g.gracz_id = z.gracz_id
INNER JOIN gry    gr ON z.gra_id   = gr.gra_id
ORDER BY g.nick ASC;
▸ WYNIK ZAPYTANIA
nicktytul
NeonByteHalf-Life: Alyx
PixelQueenPixelDungeon
PixelQueenCyberpunk 2077
ShadowWolfCyberpunk 2077
ShadowWolfPortal 2
💡 NoobMaster nie pojawia się — INNER JOIN pomija graczy bez zakupów!
03
LEFT JOIN — gracze bez żadnego zakupu
LEFT JOIN · IS NULL
ZADANIE
Wyświetl wszystkich graczy — nawet tych, którzy nic nie kupili. Znajdź "nieaktywnych" graczy (zakup_id = NULL).
SQL
-- Wszyscy gracze z ich zakupami (lub NULL)
SELECT   g.nick, z.zakup_id, z.data_zakupu
FROM     gracze g
LEFT JOIN zakupy z ON g.gracz_id = z.gracz_id
ORDER BY g.nick;

-- Tylko nieaktywni gracze
SELECT   g.nick, g.kraj
FROM     gracze g
LEFT JOIN zakupy z ON g.gracz_id = z.gracz_id
WHERE    z.zakup_id IS NULL;
▸ WYNIK — tylko nieaktywni gracze
nickkraj
NoobMasterPL
04
GROUP BY + agregacja — wydatki graczy
GROUP BY · SUM · COUNT
ZADANIE
Oblicz łączne wydatki i liczbę zakupów każdego gracza. Wyświetl tylko tych, którzy wydali ponad 100 zł. Posortuj od najwyższych wydatków.
SQL
SELECT   g.nick,
         COUNT(z.zakup_id)           AS liczba_zakupow,
         SUM(z.cena_zakupu)          AS lacznie_wydane
FROM     gracze g
JOIN     zakupy z ON g.gracz_id = z.gracz_id
GROUP BY g.gracz_id, g.nick
HAVING   SUM(z.cena_zakupu) > 100
ORDER BY lacznie_wydane DESC;
▸ WYNIK ZAPYTANIA
nickliczba_zakupowlacznie_wydane
ShadowWolf2239.98
PixelQueen2219.98
NeonByte1149.99
💡 HAVING to warunek na grupy — działa jak WHERE, ale po GROUP BY. Nie możesz użyć WHERE do filtrowania wyniku SUM()!
05
JOIN trzech tabel — gry i ich wydawcy
3x JOIN
ZADANIE
Wyświetl nick gracza, tytuł gry i nazwę wydawcy dla wszystkich zakupów. Posortuj po nazwie wydawcy.
SQL
SELECT   g.nick          AS gracz,
         gr.tytul         AS gra,
         w.nazwa          AS wydawca
FROM     gracze   g
JOIN     zakupy   z  ON g.gracz_id   = z.gracz_id
JOIN     gry      gr ON z.gra_id     = gr.gra_id
JOIN     wydawcy  w  ON gr.wydawca_id = w.wydawca_id
ORDER BY w.nazwa, g.nick;
▸ WYNIK ZAPYTANIA
graczgrawydawca
PixelQueenCyberpunk 2077CD Projekt
ShadowWolfCyberpunk 2077CD Projekt
PixelQueenPixelDungeonIndieSoft
NeonByteHalf-Life: AlyxValve
ShadowWolfPortal 2Valve
06
Podzapytanie — najdroższa kupiona gra
Subquery · WHERE IN
ZADANIE
Używając podzapytania, wyświetl nick i kraj graczy, którzy kupili grę z gatunku RPG.
SQL
SELECT  nick, kraj
FROM    gracze
WHERE   gracz_id IN (
    -- wewnętrzne zapytanie zwraca listę gracz_id
    SELECT z.gracz_id
    FROM   zakupy z
    JOIN   gry gr ON z.gra_id = gr.gra_id
    WHERE  gr.gatunek = 'RPG'
)
ORDER BY nick;
▸ WYNIK ZAPYTANIA
nickkraj
PixelQueenDE
ShadowWolfPL
💡 Podzapytanie w WHERE zwraca listę ID — zapytanie zewnętrzne filtruje graczy, których ID jest na tej liście (IN).
🚀

Zadanie projektowe

● INF.03 · PROJEKT KOŃCOWY

Analiza danych GameVault

Korzystając z bazy GameVault, napisz zapytania SQL realizujące poniższe wymagania. Każde zapytanie zapisz w phpMyAdmin i sprawdź wynik.

Każde zapytanie powinno być opisane komentarzem SQL (--). Zapisz kod w pliku .sql.

🧠

Quiz — sprawdź wiedzę

Q1Jaka jest różnica między INNER JOIN a LEFT JOIN?
A INNER JOIN zwraca wszystkie rekordy z lewej tabeli, LEFT JOIN tylko wspólne
B LEFT JOIN zwraca wszystkie rekordy z lewej tabeli, INNER JOIN tylko te powiązane w obu tabelach
C Są identyczne — tylko inaczej się piszą
D INNER JOIN działa tylko z kluczem głównym
Q2Do czego służy klauzula HAVING?
A Filtruje rekordy przed grupowaniem — jak WHERE
B Filtruje grupy POWSTAŁE po GROUP BY — np. wynik SUM() lub COUNT()
C Sortuje wyniki grupowania
D Łączy dwie tabele po kluczu
Q3Które zapytanie wyświetli graczy, którzy NIE mają żadnego zakupu?
A SELECT * FROM gracze INNER JOIN zakupy ON gracze.gracz_id = zakupy.gracz_id
B SELECT * FROM gracze WHERE gracz_id NOT IN (SELECT gracz_id FROM zakupy)
C SELECT * FROM gracze WHERE zakup_id IS NULL
D SELECT * FROM gracze GROUP BY gracz_id HAVING COUNT(*) = 0
Q4Co zwróci zapytanie z GROUP BY bez funkcji agregującej (np. COUNT, SUM)?
A Błąd składniowy — GROUP BY wymaga funkcji agregującej
B Unikalne wartości kolumny, po której grupujemy (podobnie jak DISTINCT)
C Wszystkie rekordy bez grupowania
D Automatycznie liczy COUNT(*) dla każdej grupy
Q5Gdzie w zapytaniu pojawia się warunek łączenia tabel w JOIN?
A W klauzuli WHERE
B W klauzuli ON po nazwie tabeli w JOIN
C W klauzuli HAVING
D W klauzuli FROM
0/5

Checklista umiejętności

Postęp0 / 8
Piszę zapytania SELECT z warunkami WHERE
Stosuję INNER JOIN do łączenia tabel
Stosuję LEFT JOIN i interpretuję NULL
Łączę trzy i więcej tabel w jednym zapytaniu
Używam GROUP BY z COUNT(), SUM(), AVG()
Filtruję grupy klauzulą HAVING
Piszę i interpretuję podzapytania (subquery)
Sortuję i filtruję wyniki ORDER BY / WHERE