SQL – Przychodnia
lekarska

Baza danych przychodni jako kontekst do ćwiczeń egzaminacyjnych INF.03 — pełne spektrum zapytań SQL.

SELECT JOIN LEFT JOIN GROUP BY HAVING Podzapytania UPDATE DELETE
📘
Kluczowe polecenia — przypomnienie

SELECT … WHERE … ORDER BY

Podstawowe pobieranie danych z filtrowaniem i sortowaniem. Bez WHERE — zwraca wszystkie wiersze.

INNER JOIN

Łączy tabele — zwraca tylko wiersze z dopasowaniem w obu. Brak pary = wiersz pomijany.

LEFT JOIN

Wszystkie wiersze z tabeli lewej, nawet bez pary w prawej. Brak pary = NULL w kolumnach z prawej.

GROUP BY + HAVING

Grupuje wiersze o tych samych wartościach. HAVING filtruje grupy (WHERE nie zadziała po agregacji).

Podzapytania

Zapytanie wewnątrz zapytania — najczęściej w WHERE … IN (SELECT …). Wynik podzapytania staje się listą do filtrowania.

UPDATE / DELETE

UPDATE zmienia dane, DELETE usuwa rekordy. ZAWSZE z klauzulą WHERE — bez niej operacja dotknie całą tabelę.

INNER JOIN vs LEFT JOIN — kiedy co?
Typ Co zwraca Wiersze bez pary Kiedy użyć?
INNER JOIN Tylko wiersze z dopasowaniem w obu tabelach Pomijane Gdy chcesz tylko kompletne dane
LEFT JOIN Wszystkie z lewej + dopasowane z prawej NULL w kolumnach z prawej Gdy chcesz znaleźć „sieroty" lub zachować wszystkich
RIGHT JOIN Wszystkie z prawej + dopasowane z lewej NULL w kolumnach z lewej Rzadko — zastąp odwracając kolejność tabel i LEFT JOIN
🏥
Baza danych: Przychodnia

Cztery powiązane tabele modelujące pracę przychodni lekarskiej.

🩺 lekarze
lekarz_idINT PK
imieVARCHAR
nazwiskoVARCHAR
specjalizacja_idFK
telefonVARCHAR
📋 specjalizacje
specjalizacja_idINT PK
nazwaVARCHAR
👤 pacjenci
pacjent_idINT PK
imieVARCHAR
nazwiskoVARCHAR
data_urodzeniaDATE
miastoVARCHAR
📅 wizyty
wizyta_idINT PK
pacjent_idFK
lekarz_idFK
data_wizytyDATE
diagnozaVARCHAR
kosztDECIMAL
SQL · PRZYCHODNIA INIT
-- Stwórz bazę i wybierz ją
CREATE DATABASE przychodnia;
USE przychodnia;

-- Tabela specjalizacji
CREATE TABLE specjalizacje (
    specjalizacja_id INT PRIMARY KEY AUTO_INCREMENT,
    nazwa            VARCHAR(60) NOT NULL
);

-- Tabela lekarzy
CREATE TABLE lekarze (
    lekarz_id        INT PRIMARY KEY AUTO_INCREMENT,
    imie             VARCHAR(40) NOT NULL,
    nazwisko         VARCHAR(60) NOT NULL,
    specjalizacja_id INT,
    telefon          VARCHAR(15),
    FOREIGN KEY (specjalizacja_id) REFERENCES specjalizacje(specjalizacja_id)
);

-- Tabela pacjentów
CREATE TABLE pacjenci (
    pacjent_id    INT PRIMARY KEY AUTO_INCREMENT,
    imie          VARCHAR(40) NOT NULL,
    nazwisko      VARCHAR(60) NOT NULL,
    data_urodzenia DATE,
    miasto        VARCHAR(50)
);

-- Tabela wizyt
CREATE TABLE wizyty (
    wizyta_id  INT PRIMARY KEY AUTO_INCREMENT,
    pacjent_id INT,
    lekarz_id  INT,
    data_wizyty DATE NOT NULL,
    diagnoza   VARCHAR(120),
    koszt      DECIMAL(7,2) NOT NULL,
    FOREIGN KEY (pacjent_id) REFERENCES pacjenci(pacjent_id),
    FOREIGN KEY (lekarz_id)  REFERENCES lekarze(lekarz_id)
);

-- Dane testowe
INSERT INTO specjalizacje (nazwa) VALUES
    ('Kardiolog'), ('Ortopeda'), ('Dermatolog'), ('Internista');

INSERT INTO lekarze (imie, nazwisko, specjalizacja_id, telefon) VALUES
    ('Anna',    'Kowalska',  1, '600-111-001'),
    ('Marek',   'Nowak',     1, '600-111-002'),
    ('Joanna',  'Wiśniewska',2, '600-111-003'),
    ('Piotr',   'Zając',     3, '600-111-004'),
    ('Katarzyna','Dąbrowska', 4, '600-111-005');

INSERT INTO pacjenci (imie, nazwisko, data_urodzenia, miasto) VALUES
    ('Jan',     'Kowalski',  '1985-03-12', 'Kielce'),
    ('Maria',   'Nowak',     '1990-07-25', 'Kraków'),
    ('Adam',    'Wiśniewski','1978-11-30', 'Kielce'),
    ('Ewa',     'Kaczmarek', '2000-01-15', 'Warszawa'),
    ('Tomasz',  'Zielski',   '1965-09-08', 'Pińczów'),
    ('Agata',   'Lewandowska','1995-04-20','Kraków');

-- Agata Lewandowska (id=6) nie ma żadnej wizyty — do zadania z LEFT JOIN
INSERT INTO wizyty (pacjent_id, lekarz_id, data_wizyty, diagnoza, koszt) VALUES
    (1, 1, '2025-01-10', 'Nadciśnienie tętnicze',      150.00),
    (1, 2, '2025-02-14', 'Arytmia serca',               200.00),
    (2, 3, '2025-01-22', 'Skrzywienie kręgosłupa',      180.00),
    (3, 1, '2025-03-05', 'Choroba wieńcowa',             220.00),
    (3, 4, '2025-03-18', 'Trądzik różowaty',             120.00),
    (4, 5, '2025-04-02', 'Infekcja górnych dróg oddech.',  90.00),
    (5, 2, '2025-04-10', 'Migotanie przedsionków',       250.00),
    (5, 3, '2025-04-15', 'Złamanie kości śródstopia',    300.00),
    (2, 4, '2025-05-01', 'Łuszczyca plackowata',         140.00),
    (1, 5, '2025-05-12', 'Grypa sezonowa',               80.00);
⌨️
Ćwiczenia egzaminacyjne — 12 zadań
01
Lista pacjentów z określonego miasta
SELECT WHERE ORDER BY
Polecenie: Wyświetl imię i nazwisko wszystkich pacjentów z Kielc lub Krakowa. Posortuj alfabetycznie po nazwisku.
SQL
SELECT   imie, nazwisko, miasto
FROM     pacjenci
WHERE    miasto IN ('Kielce', 'Kraków')
ORDER BY nazwisko ASC;
imienazwiskomiasto
JanKowalskiKielce
MariaNowakKraków
AdamWiśniewskiKielce
AgataLewandowskaKraków

Zapamiętaj: IN (…) to skrót dla OR — czytelniejszy i łatwiejszy do rozbudowania o kolejne wartości.

02
Wizyty powyżej określonego kosztu
SELECT WHERE ORDER BY
Polecenie: Wyświetl diagnozę i koszt wszystkich wizyt droższych niż 150 zł, posortowanych od najdroższej.
SQL
SELECT   diagnoza, koszt
FROM     wizyty
WHERE    koszt > 150
ORDER BY koszt DESC;
diagnozakoszt
Złamanie kości śródstopia300.00
Migotanie przedsionków250.00
Choroba wieńcowa220.00
Arytmia serca200.00
Skrzywienie kręgosłupa180.00
Łuszczyca plackowata140.00
03
INNER JOIN — pacjenci i ich lekarze
INNER JOIN 2 tabele
Polecenie: Wyświetl imię i nazwisko pacjenta, datę wizyty oraz imię i nazwisko lekarza prowadzącego. Posortuj po dacie wizyty.
SQL
SELECT   p.imie          AS pacjent_imie,
         p.nazwisko      AS pacjent_nazwisko,
         w.data_wizyty,
         l.imie          AS lekarz_imie,
         l.nazwisko      AS lekarz_nazwisko
FROM     wizyty w
INNER JOIN pacjenci p ON w.pacjent_id = p.pacjent_id
INNER JOIN lekarze  l ON w.lekarz_id  = l.lekarz_id
ORDER BY w.data_wizyty ASC;
pacjent_imiepacjent_nazwiskodata_wizytylekarz_imielekarz_nazwisko
JanKowalski2025-01-10AnnaKowalska
MariaNowak2025-01-22JoannaWiśniewska
JanKowalski2025-02-14MarekNowak
AdamWiśniewski2025-03-05AnnaKowalska
AdamWiśniewski2025-03-18PiotrZając
EwaKaczmarek2025-04-02KatarzynaDąbrowska
TomaszZielski2025-04-10MarekNowak
TomaszZielski2025-04-15JoannaWiśniewska
MariaNowak2025-05-01PiotrZając
JanKowalski2025-05-12KatarzynaDąbrowska

Zapamiętaj: Tabela centralna to wizyty — ona przechowuje FK do obu stron. Zaczynamy JOIN od niej.

04
JOIN 4 tabel — pacjent, wizyta, lekarz, specjalizacja
INNER JOIN 4 tabele
Polecenie: Wyświetl nazwisko pacjenta, diagnozę, koszt wizyty oraz nazwę specjalizacji lekarza. Posortuj po koszcie malejąco.
SQL
SELECT   p.nazwisko        AS pacjent,
         w.diagnoza,
         w.koszt,
         s.nazwa           AS specjalizacja
FROM     wizyty w
JOIN     pacjenci      p ON w.pacjent_id      = p.pacjent_id
JOIN     lekarze       l ON w.lekarz_id       = l.lekarz_id
JOIN     specjalizacje s ON l.specjalizacja_id = s.specjalizacja_id
ORDER BY w.koszt DESC;
pacjentdiagnozakosztspecjalizacja
ZielskiZłamanie kości śródstopia300.00Ortopeda
ZielskiMigotanie przedsionków250.00Kardiolog
WiśniewskiChoroba wieńcowa220.00Kardiolog
KowalskiArytmia serca200.00Kardiolog
NowakSkrzywienie kręgosłupa180.00Ortopeda
KowalskiNadciśnienie tętnicze150.00Kardiolog
NowakŁuszczyca plackowata140.00Dermatolog
WiśniewskiTrądzik różowaty120.00Dermatolog
KaczmarekInfekcja górnych dróg oddech.90.00Internista
KowalskiGrypa sezonowa80.00Internista
05
LEFT JOIN — pacjenci bez żadnej wizyty
LEFT JOIN IS NULL
Polecenie: Wyświetl wszystkich pacjentów — nawet tych bez żadnej wizyty. Wyróżnij tych, którzy jeszcze nie byli w przychodni (wizyta_id = NULL).
SQL
-- Wszyscy pacjenci z informacją czy mają wizytę
SELECT   p.imie, p.nazwisko, w.wizyta_id, w.data_wizyty
FROM     pacjenci p
LEFT JOIN wizyty w ON p.pacjent_id = w.pacjent_id
ORDER BY p.nazwisko;

-- Tylko ci bez żadnej wizyty
SELECT   p.imie, p.nazwisko, p.miasto
FROM     pacjenci p
LEFT JOIN wizyty w ON p.pacjent_id = w.pacjent_id
WHERE    w.wizyta_id IS NULL;
imienazwiskomiasto
AgataLewandowskaKraków

Klucz: INNER JOIN by ją pominął. LEFT JOIN zachowuje wszystkich pacjentów — NULL w kolumnach wizyty = brak rekordu w tej tabeli.

06
GROUP BY — liczba wizyt i łączny przychód lekarza
GROUP BY COUNT SUM
Polecenie: Wyświetl nazwisko każdego lekarza, liczbę przyjętych pacjentów oraz łączny przychód z wizyt. Posortuj od największego przychodu.
SQL
SELECT   l.imie, l.nazwisko,
         COUNT(w.wizyta_id)  AS liczba_wizyt,
         SUM(w.koszt)        AS lacznie_zlotych
FROM     lekarze l
JOIN     wizyty  w ON l.lekarz_id = w.lekarz_id
GROUP BY l.lekarz_id, l.imie, l.nazwisko
ORDER BY lacznie_zlotych DESC;
imienazwiskoliczba_wizytlacznie_zlotych
AnnaKowalska2370.00
MarekNowak2450.00
JoannaWiśniewska2480.00
PiotrZając2260.00
KatarzynaDąbrowska2170.00

Uwaga: W GROUP BY wymieniamy wszystkie kolumny z SELECT, które nie są funkcją agregującą.

07
HAVING — lekarze z przychodem powyżej progu
GROUP BY HAVING SUM
Polecenie: Wyświetl tylko tych lekarzy, których łączny przychód z wizyt przekracza 300 zł.
SQL
SELECT   l.imie, l.nazwisko,
         SUM(w.koszt) AS przychod
FROM     lekarze l
JOIN     wizyty  w ON l.lekarz_id = w.lekarz_id
GROUP BY l.lekarz_id, l.imie, l.nazwisko
HAVING   SUM(w.koszt) > 300
ORDER BY przychod DESC;
imienazwiskoprzychod
JoannaWiśniewska480.00
MarekNowak450.00
AnnaKowalska370.00
PiotrZając260.00

HAVING vs WHERE: WHERE filtruje przed grupowaniem — nie widzi wyników SUM(). HAVING filtruje po GROUP BY — widzi agregaty.

08
Średni koszt wizyty per specjalizacja
GROUP BY AVG JOIN
Polecenie: Oblicz średni koszt wizyty dla każdej specjalizacji. Wynik zaokrąglij do 2 miejsc po przecinku i posortuj od najdroższej.
SQL
SELECT   s.nazwa          AS specjalizacja,
         ROUND(AVG(w.koszt), 2) AS sredni_koszt,
         COUNT(w.wizyta_id)   AS liczba_wizyt
FROM     wizyty        w
JOIN     lekarze       l ON w.lekarz_id       = l.lekarz_id
JOIN     specjalizacje s ON l.specjalizacja_id = s.specjalizacja_id
GROUP BY s.specjalizacja_id, s.nazwa
ORDER BY sredni_koszt DESC;
specjalizacjasredni_kosztliczba_wizyt
Ortopeda240.002
Kardiolog205.004
Dermatolog130.002
Internista85.002
09
Podzapytanie — pacjenci kardiologa
Subquery WHERE IN
Polecenie: Wyświetl imię i nazwisko pacjentów, którzy byli na wizycie u kardiologa. Użyj podzapytania.
SQL
SELECT imie, nazwisko
FROM   pacjenci
WHERE  pacjent_id IN (
    SELECT w.pacjent_id
    FROM   wizyty        w
    JOIN   lekarze       l ON w.lekarz_id       = l.lekarz_id
    JOIN   specjalizacje s ON l.specjalizacja_id = s.specjalizacja_id
    WHERE  s.nazwa = 'Kardiolog'
)
ORDER BY nazwisko;
imienazwisko
JanKowalski
TomaszZielski
AdamWiśniewski

Zapamiętaj: Podzapytanie w WHERE IN (…) jest wykonywane raz — jego wynik to lista ID, po której filtrujemy zapytanie zewnętrzne.

10
UPDATE — korekta kosztów wizyt
UPDATE WHERE
Polecenie: Podnieś koszt wszystkich wizyt do lekarzy internistów o 15%. Przed zmianą sprawdź SELECT-em które rekordy zostaną zmodyfikowane.
⚠️ Zawsze najpierw wykonaj SELECT z tym samym WHERE — upewnij się, że trafisz dokładnie w te rekordy, które chcesz zmienić.
SQL
-- Krok 1: sprawdź co zmienisz
SELECT   w.wizyta_id, w.diagnoza, w.koszt
FROM     wizyty        w
JOIN     lekarze       l ON w.lekarz_id       = l.lekarz_id
JOIN     specjalizacje s ON l.specjalizacja_id = s.specjalizacja_id
WHERE    s.nazwa = 'Internista';

-- Krok 2: dopiero teraz aktualizuj
UPDATE   wizyty
SET      koszt = koszt * 1.15
WHERE    lekarz_id IN (
    SELECT l.lekarz_id
    FROM   lekarze       l
    JOIN   specjalizacje s ON l.specjalizacja_id = s.specjalizacja_id
    WHERE  s.nazwa = 'Internista'
);

-- Krok 3: weryfikacja
SELECT wizyta_id, diagnoza, koszt FROM wizyty
WHERE  lekarz_id IN (SELECT lekarz_id FROM lekarze WHERE specjalizacja_id = 4);
wizyta_iddiagnozakoszt przedkoszt po
6Infekcja górnych dróg oddech.90.00103.50
10Grypa sezonowa80.0092.00
11
DELETE — usuwanie nieaktywnych pacjentów
DELETE Subquery
Polecenie: Usuń pacjentów z Krakowa, którzy nie mają żadnej wizyty w bazie.
⚠️ DELETE jest nieodwracalny bez transakcji. Zawsze sprawdź SELECT-em co usuniesz, zanim wykonasz operację.
SQL
-- Krok 1: sprawdź kogo usuniesz
SELECT   p.pacjent_id, p.imie, p.nazwisko, p.miasto
FROM     pacjenci p
LEFT JOIN wizyty w ON p.pacjent_id = w.pacjent_id
WHERE    p.miasto = 'Kraków'
  AND    w.wizyta_id IS NULL;

-- Krok 2: dopiero teraz usuń
DELETE FROM pacjenci
WHERE  miasto = 'Kraków'
  AND  pacjent_id NOT IN (
      SELECT pacjent_id FROM wizyty
  );
Usunięty rekordPowód
Agata Lewandowska (Kraków)Brak jakiejkolwiek wizyty w tabeli wizyty

Maria Nowak (Kraków) zostaje — ma wizyty. Klucz obcy i tak by zablokował usunięcie rekordu z powiązanymi wierszami.

12
Zadanie złożone — typ egzaminacyjny
JOIN GROUP BY HAVING ORDER BY
Polecenie: Wyświetl imię i nazwisko pacjentów, którzy odwiedzili więcej niż jednego lekarza, wraz z łączną kwotą wydaną na wizyty. Posortuj od największych wydatków.
SQL
SELECT   p.imie,
         p.nazwisko,
         COUNT(DISTINCT w.lekarz_id) AS rozni_lekarze,
         SUM(w.koszt)                 AS lacznie_wydane
FROM     pacjenci p
JOIN     wizyty   w ON p.pacjent_id = w.pacjent_id
GROUP BY p.pacjent_id, p.imie, p.nazwisko
HAVING   COUNT(DISTINCT w.lekarz_id) > 1
ORDER BY lacznie_wydane DESC;
imienazwiskorozni_lekarzelacznie_wydane
TomaszZielski2550.00
JanKowalski3430.00
AdamWiśniewski2340.00
MariaNowak2320.00

COUNT(DISTINCT …) liczy unikalne wartości — bez DISTINCT policzyłby każdą wizytę osobno, nawet jeśli do tego samego lekarza.