MySQL 8 · InnoDB · utf8mb4_polish_ci

Indeks Uczelni — model bazy danych

13 tabel w czterech obszarach: placówka i władze, kadra dydaktyczna, organizacja zajęć oraz studenci z dziennikiem ocen. Poniżej diagram ERD, przykładowe dane i 28 typowych zapytań z wynikami.

placówka kadra dydaktyka studenci i oceny PK/FK/UQ kursywa = NULL jeden do wielu (o = opcjonalnie)
Diagram ERD bazy danych uczelni uczelnia.adres_id → adres.id wydzial.uczelnia_id → uczelnia.id sekretariat.uczelnia_id → uczelnia.id sekretariat.wydzial_id → wydzial.id pracownik.adres_id → adres.id funkcja_kierownicza.pracownik_id → pracownik.id funkcja_kierownicza.uczelnia_id → uczelnia.id funkcja_kierownicza.wydzial_id → wydzial.id przedmiot.wydzial_id → wydzial.id grupa.wydzial_id → wydzial.id grupa.opiekun_id → pracownik.id student.grupa_id → grupa.id zajecia.przedmiot_id → przedmiot.id zajecia.pracownik_id → pracownik.id zajecia.grupa_id → grupa.id zajecia.semestr_id → semestr.id ocena_czastkowa.zajecia_id → zajecia.id ocena_czastkowa.student_id → student.id ocena_koncowa.zajecia_id → zajecia.id ocena_koncowa.student_id → student.id adres placówka PK id INT UNSIGNED ulica VARCHAR(100) nr_domu VARCHAR(10) nr_lokalu VARCHAR(10) kod_pocztowy CHAR(6) miejscowosc VARCHAR(80) kraj VARCHAR(60) uczelnia placówka PK id INT UNSIGNED nazwa VARCHAR(200) skrot VARCHAR(20) UQ nip CHAR(10) UQ regon VARCHAR(14) FK adres_id INT UNSIGNED telefon VARCHAR(20) email VARCHAR(120) www VARCHAR(200) wydzial placówka PK id INT UNSIGNED FK uczelnia_id INT UNSIGNED nazwa VARCHAR(150) skrot VARCHAR(20) UQ (uczelnia_id, nazwa) sekretariat placówka PK id INT UNSIGNED FK uczelnia_id INT UNSIGNED FK wydzial_id INT UNSIGNED nazwa VARCHAR(120) telefon VARCHAR(20) email VARCHAR(120) nr_pokoju VARCHAR(10) godziny_otwarcia VARCHAR(120) pracownik kadra PK id INT UNSIGNED imie VARCHAR(50) nazwisko VARCHAR(80) plec ENUM('K','M') UQ pesel CHAR(11) tytul_naukowy VARCHAR(40) telefon VARCHAR(20) UQ email VARCHAR(120) FK adres_id INT UNSIGNED data_zatrudnienia DATE data_zwolnienia DATE nr_konta CHAR(28) funkcja_kierownicza placówka PK id INT UNSIGNED FK pracownik_id INT UNSIGNED FK uczelnia_id INT UNSIGNED FK wydzial_id INT UNSIGNED funkcja ENUM('REKTOR','PROREKTOR','DZIEKAN','PRODZIEKAN','KANCLERZ')ENUM('REKTOR','P…) data_od DATE data_do DATE przedmiot dydaktyka PK id INT UNSIGNED FK wydzial_id INT UNSIGNED UQ kod VARCHAR(20) nazwa VARCHAR(150) punkty_ects TINYINT UNSIGNED semestr dydaktyka PK id INT UNSIGNED rok_akademicki CHAR(9) typ ENUM('ZIMOWY','LETNI')ENUM('ZIMOWY','L…) data_od DATE data_do DATE UQ (rok_akademicki, typ) grupa dydaktyka PK id INT UNSIGNED FK wydzial_id INT UNSIGNED nazwa VARCHAR(50) rocznik YEAR FK opiekun_id INT UNSIGNED UQ (nazwa, rocznik) student studenci i oceny PK id INT UNSIGNED UQ nr_albumu VARCHAR(10) imie VARCHAR(50) nazwisko VARCHAR(80) plec ENUM('K','M') UQ pesel CHAR(11) data_urodzenia DATE FK grupa_id INT UNSIGNED zajecia dydaktyka PK id INT UNSIGNED FK przedmiot_id INT UNSIGNED FK pracownik_id INT UNSIGNED FK grupa_id INT UNSIGNED FK semestr_id INT UNSIGNED UQ (przedmiot_id, grupa_id, semestr_id) ocena_czastkowa studenci i oceny PK id INT UNSIGNED FK zajecia_id INT UNSIGNED FK student_id INT UNSIGNED wartosc DECIMAL(2,1) opis VARCHAR(100) data_wystawienia DATE ocena_koncowa studenci i oceny PK id INT UNSIGNED FK zajecia_id INT UNSIGNED FK student_id INT UNSIGNED wartosc DECIMAL(2,1) data_wystawienia DATE UQ (zajecia_id, student_id)

Najedź lub dotknij tabeli, aby podświetlić jej relacje. Wersja PNG do katalogu rozwiązań: erd.png.

Decyzje projektowe

wym. 1

Placówka i władze

uczelnia ma dane teleadresowe, sekretariat kontakt (wydzial_id NULL = sekretariat główny). Rektor i dziekani to funkcja_kierownicza z datami kadencji, więc historia zmian zostaje.

wym. 2

Pracownicy

PESEL i e-mail unikalne, konto jako IBAN (PL + 26 cyfr), data ustania stosunku pracy opcjonalna i nie wcześniejsza niż zatrudnienie. Adres w osobnej tabeli adres.

wym. 3 i 6

Tabela zajecia

Łączy przedmiot, prowadzącego, grupę i semestr. Daje to „wykładowca prowadzi wiele przedmiotów”, a oceny wiążą się od razu z grupą, przedmiotem i semestrem.

wym. 4 i 5

Studenci i grupy

Student należy do jednej grupy; numer albumu i PESEL są unikalne. Grupa ma nazwę, rocznik (YEAR) i opiekuna wskazującego na pracownik.

wym. 6

Dziennik i indeks

ocena_czastkowa przyjmuje wiele wpisów, ocena_koncowa jeden na studenta i zajęcia. Skala ocen 2.0–5.0 pilnowana przez CHECK.

do konsultacji

Czego baza nie wymusza

To, że oceniany student należy do grupy z zajęć, sprawdza zapytanie 24 (lub trigger). Do omówienia: studenci bez PESEL i student w kilku grupach naraz.

Dane przykładowe

Zawartość dane.sql. Dane są fikcyjne i generowane skryptem, więc spełniają wszystkie ograniczenia schematu.

TabelaWierszy
adres11
uczelnia1
wydzial2
sekretariat3
pracownik10
funkcja_kierownicza7
przedmiot14
semestr3
grupa4
student24
zajecia29
ocena_czastkowa574
ocena_koncowa131

Uczelnia: fikcyjna Dolnośląska Akademia Nauk Stosowanych we Wrocławiu, 2 wydziały, 3 sekretariaty.

Kadra: 10 wykładowców, jedna osoba zwolniona w lutym 2026. Władze mają bieżące i zakończone kadencje.

Semestry: 2025/2026 zimowy i letni są zakończone i mają oceny końcowe. 2026/2027 zimowy trwa, więc ma tylko pierwsze oceny cząstkowe.

Identyfikatory: PESEL, NIP, REGON i IBAN mają poprawne sumy kontrolne; płeć studenta zgadza się z cyfrą w PESEL.

Celowy brak: jeden student nie ma oceny końcowej w zakończonym semestrze, żeby zapytanie 20 miało co pokazać.

Zapytania z wynikami

Zawartość zapytania.sql. Wyniki pochodzą z uruchomienia na danych przykładowych, z bieżącą datą 27.10.2026. Wartości oznaczone -- parametr można podmienić.

APlacówka i władze

01

Wizytówka uczelni: dane teleadresowe i wszystkie sekretariaty

Dane kontaktowe uczelni i wszystkich sekretariatów w jednym widoku. LEFT JOIN do wydziału, bo sekretariat rektora nie należy do żadnego wydziału.

JOINLEFT JOINCOALESCE
SELECT u.nazwa,
       CONCAT(a.ulica, ' ', a.nr_domu, COALESCE(CONCAT('/', a.nr_lokalu), ''), ', ',
              a.kod_pocztowy, ' ', a.miejscowosc)          AS adres,
       u.telefon, u.email, u.www,
       COALESCE(w.nazwa, 'Rektorat')                       AS jednostka,
       s.nazwa                                             AS sekretariat,
       s.telefon                                           AS tel_sekretariatu,
       s.email                                             AS email_sekretariatu,
       s.nr_pokoju, s.godziny_otwarcia
FROM uczelnia u
JOIN adres a            ON a.id = u.adres_id
JOIN sekretariat s      ON s.uczelnia_id = u.id
LEFT JOIN wydzial w     ON w.id = s.wydzial_id
ORDER BY s.wydzial_id IS NOT NULL, w.nazwa;
3 wiersze
nazwaadrestelefonemailwwwjednostkasekretariattel_sekretariatuemail_sekretariatunr_pokojugodziny_otwarcia
Dolnośląska Akademia Nauk Stosowanychplac Uniwersytecki 3, 50-137 Wrocław+48 71 375 20 00kontakt@dans.edu.plhttps://dans.edu.plRektoratSekretariat Rektora+48 71 375 20 01rektorat@dans.edu.plA-101pn-pt 8:00-15:30
Dolnośląska Akademia Nauk Stosowanychplac Uniwersytecki 3, 50-137 Wrocław+48 71 375 20 00kontakt@dans.edu.plhttps://dans.edu.plWydział Informatyki i TelekomunikacjiDziekanat WIT+48 71 375 21 10dziekanat.wit@dans.edu.plC-012pn, śr, pt 9:00-14:00
Dolnośląska Akademia Nauk Stosowanychplac Uniwersytecki 3, 50-137 Wrocław+48 71 375 20 00kontakt@dans.edu.plhttps://dans.edu.plWydział ZarządzaniaDziekanat WZ+48 71 375 22 10dziekanat.wz@dans.edu.plB-004wt, czw 10:00-15:00
02

Aktualne władze uczelni (rektor, prorektorzy, dziekani, prodziekani)

Kto dziś pełni funkcje kierownicze. Kadencja jest aktywna, gdy zaczęła się przed dziś i nie ma daty końca lub ta jest w przyszłości. FIELD() ustawia kolejność od rektora.

filtr datFIELD
SELECT f.funkcja,
       COALESCE(w.nazwa, '— cała uczelnia —')              AS jednostka,
       CONCAT(p.tytul_naukowy, ' ', p.imie, ' ', p.nazwisko) AS osoba,
       p.email, f.data_od
FROM funkcja_kierownicza f
JOIN pracownik p   ON p.id = f.pracownik_id
LEFT JOIN wydzial w ON w.id = f.wydzial_id
WHERE f.data_od <= CURDATE()
  AND (f.data_do IS NULL OR f.data_do >= CURDATE())
ORDER BY FIELD(f.funkcja, 'REKTOR', 'PROREKTOR', 'KANCLERZ', 'DZIEKAN', 'PRODZIEKAN'), w.nazwa;
5 wierszy
funkcjajednostkaosobaemaildata_od
REKTOR— cała uczelnia —prof. dr hab. inż. Andrzej Wiśniewskia.wisniewski@dans.edu.pl2024-09-01
PROREKTOR— cała uczelnia —dr hab. Małgorzata Kowalczykm.kowalczyk@dans.edu.pl2024-09-01
DZIEKANWydział Informatyki i Telekomunikacjidr hab. inż. Piotr Zielińskip.zielinski@dans.edu.pl2024-09-01
DZIEKANWydział Zarządzaniaprof. dr hab. Agnieszka Kamińskaa.kaminska@dans.edu.pl2024-09-01
PRODZIEKANWydział Informatyki i Telekomunikacjidr inż. Tomasz Wójcikt.wojcik@dans.edu.pl2024-09-01
03

Historia funkcji kierowniczych na wydziale (kadencje)

Pełna historia kadencji na wydziale, łącznie z zakończonymi. Pokazuje, po co funkcje są osobną tabelą z datami zamiast kolumny „dziekan” w tabeli wydziału.

historia
SELECT f.funkcja, CONCAT(p.imie, ' ', p.nazwisko) AS osoba, f.data_od,
       COALESCE(f.data_do, 'trwa')                AS data_do
FROM funkcja_kierownicza f
JOIN pracownik p ON p.id = f.pracownik_id
WHERE f.wydzial_id = 1                                     -- parametr: wydział
ORDER BY f.data_od DESC;
3 wiersze
funkcjaosobadata_oddata_do
DZIEKANPiotr Zieliński2024-09-01trwa
PRODZIEKANTomasz Wójcik2024-09-01trwa
DZIEKANEwa Szymańska2020-09-012024-08-31

BKadra

04

Aktywni pracownicy ze stażem pracy w latach

Aktywni pracownicy, czyli bez daty ustania stosunku pracy lub z datą w przyszłości, posortowani po stażu.

TIMESTAMPDIFF
SELECT p.id, p.tytul_naukowy, p.imie, p.nazwisko, p.email, p.telefon,
       p.data_zatrudnienia,
       TIMESTAMPDIFF(YEAR, p.data_zatrudnienia, CURDATE()) AS staz_lat
FROM pracownik p
WHERE p.data_zwolnienia IS NULL OR p.data_zwolnienia > CURDATE()
ORDER BY staz_lat DESC, p.nazwisko;
9 wierszy
idtytul_naukowyimienazwiskoemailtelefondata_zatrudnieniastaz_lat
1prof. dr hab. inż.AndrzejWiśniewskia.wisniewski@dans.edu.pl+48 663 614 6241995-10-0131
8prof. dr hab.AgnieszkaKamińskaa.kaminska@dans.edu.pl+48 779 374 7841998-10-0128
2dr hab.MałgorzataKowalczykm.kowalczyk@dans.edu.pl+48 730 111 9342001-02-1525
3dr hab. inż.PiotrZielińskip.zielinski@dans.edu.pl+48 795 416 1252003-10-0123
4dr inż.EwaSzymańskae.szymanska@dans.edu.pl+48 788 590 6252008-10-0118
9drPawełJankowskip.jankowski@dans.edu.pl+48 581 238 9022010-10-0116
5dr inż.TomaszWójcikt.wojcik@dans.edu.pl+48 628 673 5112012-10-0114
7drMarcinDąbrowskim.dabrowski@dans.edu.pl+48 636 610 4792014-03-0112
6mgr inż.KatarzynaLewandowskak.lewandowska@dans.edu.pl+48 633 809 5972016-10-0110
05

Pracownicy, z którymi rozwiązano stosunek pracy

Byli pracownicy. Dane zostają w bazie, bo są powiązane z zajęciami i ocenami z przeszłości.

filtr dat
SELECT p.imie, p.nazwisko, p.data_zatrudnienia, p.data_zwolnienia
FROM pracownik p
WHERE p.data_zwolnienia <= CURDATE()
ORDER BY p.data_zwolnienia DESC;
1 wiersz
imienazwiskodata_zatrudnieniadata_zwolnienia
JoannaMazur2021-10-012026-02-28
06

Lista do wypłat (kadry / księgowość): aktywni pracownicy z kontem i adresem

Zestawienie dla kadr i księgowości: PESEL, IBAN i adres zamieszkania aktywnych pracowników.

JOINdane wrażliwe
SELECT p.nazwisko, p.imie, p.pesel, p.nr_konta,
       CONCAT(a.ulica, ' ', a.nr_domu, COALESCE(CONCAT('/', a.nr_lokalu), ''), ', ',
              a.kod_pocztowy, ' ', a.miejscowosc) AS adres_zamieszkania
FROM pracownik p
JOIN adres a ON a.id = p.adres_id
WHERE p.data_zwolnienia IS NULL OR p.data_zwolnienia > CURDATE()
ORDER BY p.nazwisko, p.imie;
9 wierszy
nazwiskoimiepeselnr_kontaadres_zamieszkania
DąbrowskiMarcin83120214917PL06109010143627010798193319ul. Kościuszki 150/8, 50-439 Wrocław
JankowskiPaweł79061416374PL62105000864866283601610550ul. Borowska 203/51, 50-556 Wrocław
KamińskaAgnieszka68042115601PL91114020042059972837929841ul. Polna 14, 55-040 Kobierzyce
KowalczykMałgorzata72070411402PL22249010445364556815188447ul. Powstańców Śląskich 118/4, 53-333 Wrocław
LewandowskaKatarzyna90090814225PL64249010448768045717847267ul. Wrocławska 7, 55-095 Długołęka
SzymańskaEwa81013012866PL18105000860932081967273177ul. Legnicka 65/27, 54-206 Wrocław
WiśniewskiAndrzej64031210711PL39109010143998698797309114ul. Grunwaldzka 41/12, 50-357 Wrocław
WójcikTomasz85051713594PL28114020048876634400297875ul. Ślężna 22/3, 53-302 Wrocław
ZielińskiPiotr75112312136PL64114020045761698716897684ul. Kasprowicza 9, 51-137 Wrocław
07

Przedmioty prowadzone przez każdego wykładowcę w bieżącym semestrze

Odpowiedź na wymaganie 3: jeden wykładowca prowadzi kilka przedmiotów. Bieżący semestr wybierany jest po dacie, bez wpisywania jego id.

GROUP BYGROUP_CONCAT
SELECT CONCAT(p.imie, ' ', p.nazwisko)                         AS wykladowca,
       COUNT(*)                                                AS liczba_zajec,
       GROUP_CONCAT(CONCAT(pr.nazwa, ' (', g.nazwa, ')')
                    ORDER BY pr.nazwa SEPARATOR ', ')          AS zajecia
FROM zajecia z
JOIN pracownik p  ON p.id  = z.pracownik_id
JOIN przedmiot pr ON pr.id = z.przedmiot_id
JOIN grupa g      ON g.id  = z.grupa_id
JOIN semestr s    ON s.id  = z.semestr_id
WHERE CURDATE() BETWEEN s.data_od AND s.data_do
GROUP BY p.id, p.imie, p.nazwisko
ORDER BY liczba_zajec DESC, wykladowca;
5 wierszy
wykladowcaliczba_zajeczajecia
Andrzej Wiśniewski2Sztuczna inteligencja (INF-1A), Sztuczna inteligencja (INF-1B)
Paweł Jankowski2Rachunkowość finansowa (ZAR-1A), Zarządzanie projektami (ZAR-1A)
Ewa Szymańska1Projekt zespołowy (INF-2A)
Katarzyna Lewandowska1Inżynieria oprogramowania (INF-1B)
Tomasz Wójcik1Inżynieria oprogramowania (INF-1A)
08

Obciążenie dydaktyczne w roku akademickim: zajęcia, grupy, studenci

Obciążenie w całym roku akademickim: liczba zajęć, różnych przedmiotów, grup i studentów na zajęciach.

COUNT DISTINCT
SELECT CONCAT(p.imie, ' ', p.nazwisko) AS wykladowca,
       COUNT(DISTINCT z.id)             AS zajecia,
       COUNT(DISTINCT z.przedmiot_id)   AS rozne_przedmioty,
       COUNT(DISTINCT z.grupa_id)       AS grupy,
       COUNT(st.id)                     AS studentow_na_zajeciach
FROM pracownik p
JOIN zajecia z  ON z.pracownik_id = p.id
JOIN semestr s  ON s.id = z.semestr_id
JOIN student st ON st.grupa_id = z.grupa_id
WHERE s.rok_akademicki = '2025/2026'                       -- parametr
GROUP BY p.id, p.imie, p.nazwisko
ORDER BY zajecia DESC;
10 wierszy
wykladowcazajeciarozne_przedmiotygrupystudentow_na_zajeciach
Piotr Zieliński31318
Katarzyna Lewandowska33218
Marcin Dąbrowski31318
Małgorzata Kowalczyk21212
Ewa Szymańska21212
Tomasz Wójcik22212
Agnieszka Kamińska22112
Paweł Jankowski22112
Joanna Mazur21212
Andrzej Wiśniewski1116
09

Aktywni wykładowcy bez zajęć w bieżącym semestrze

Aktywni pracownicy bez żadnych zajęć w bieżącym semestrze, np. przy planowaniu obsady.

NOT EXISTS
SELECT p.imie, p.nazwisko, p.email
FROM pracownik p
WHERE (p.data_zwolnienia IS NULL OR p.data_zwolnienia > CURDATE())
  AND NOT EXISTS (
        SELECT 1
        FROM zajecia z
        JOIN semestr s ON s.id = z.semestr_id
        WHERE z.pracownik_id = p.id
          AND CURDATE() BETWEEN s.data_od AND s.data_do);
4 wiersze
imienazwiskoemail
MałgorzataKowalczykm.kowalczyk@dans.edu.pl
PiotrZielińskip.zielinski@dans.edu.pl
MarcinDąbrowskim.dabrowski@dans.edu.pl
AgnieszkaKamińskaa.kaminska@dans.edu.pl

CGrupy, studenci, plan zajęć

10

Grupy z opiekunem i liczbą studentów

Wymaganie 5: grupa ma nazwę, rocznik i opiekuna. LEFT JOIN do studentów, żeby pusta grupa też się pokazała.

LEFT JOINGROUP BY
SELECT g.nazwa, g.rocznik, w.skrot AS wydzial,
       CONCAT(p.tytul_naukowy, ' ', p.imie, ' ', p.nazwisko) AS opiekun,
       COUNT(st.id) AS liczba_studentow
FROM grupa g
JOIN wydzial w     ON w.id = g.wydzial_id
JOIN pracownik p   ON p.id = g.opiekun_id
LEFT JOIN student st ON st.grupa_id = g.id
GROUP BY g.id, g.nazwa, g.rocznik, w.skrot, p.tytul_naukowy, p.imie, p.nazwisko
ORDER BY g.rocznik, g.nazwa;
4 wiersze
nazwarocznikwydzialopiekunliczba_studentow
INF-2A2024WITdr Marcin Dąbrowski6
INF-1A2025WITdr inż. Ewa Szymańska6
INF-1B2025WITdr inż. Tomasz Wójcik6
ZAR-1A2025WZdr Paweł Jankowski6
11

Lista studentów grupy (np. lista obecności)

Lista studentów jednej grupy z wiekiem liczonym z daty urodzenia, np. jako lista obecności.

TIMESTAMPDIFF
SELECT st.nr_albumu, st.nazwisko, st.imie,
       TIMESTAMPDIFF(YEAR, st.data_urodzenia, CURDATE()) AS wiek
FROM student st
JOIN grupa g ON g.id = st.grupa_id
WHERE g.nazwa = 'INF-1A'                                   -- parametr
ORDER BY st.nazwisko, st.imie;
6 wierszy
nr_albumunazwiskoimiewiek
251002DudaJulia19
251005MichalskiSzymon19
251001NowickiJakub20
251006RutkowskaHanna20
251004SikoraMaja19
251003TomaszewskiAntoni20
12

Wyszukiwanie studenta po nazwisku, numerze albumu lub PESEL

Wyszukiwarka dziekanatu: po fragmencie nazwiska, numerze albumu lub PESEL. Wszystkie trzy kolumny mają indeks.

LIKEOR
SELECT st.id, st.nr_albumu, st.imie, st.nazwisko, st.pesel, g.nazwa AS grupa
FROM student st
JOIN grupa g ON g.id = st.grupa_id
WHERE st.nazwisko LIKE 'Now%'                              -- parametr
   OR st.nr_albumu = '251001'                              -- parametr
   OR st.pesel     = '00000000000';                        -- parametr
2 wiersze
idnr_albumuimienazwiskopeselgrupa
1251001JakubNowicki06261441134INF-1A
15243015WojciechNowak05301556597INF-2A
13

Plan zajęć grupy w bieżącym semestrze (przedmiot, ECTS, prowadzący)

Plan zajęć grupy w bieżącym semestrze z punktami ECTS i prowadzącym.

JOIN ×4
SELECT pr.kod, pr.nazwa AS przedmiot, pr.punkty_ects,
       CONCAT(p.tytul_naukowy, ' ', p.imie, ' ', p.nazwisko) AS prowadzacy
FROM zajecia z
JOIN przedmiot pr ON pr.id = z.przedmiot_id
JOIN pracownik p  ON p.id  = z.pracownik_id
JOIN grupa g      ON g.id  = z.grupa_id
JOIN semestr s    ON s.id  = z.semestr_id
WHERE g.nazwa = 'INF-1A'                                   -- parametr
  AND CURDATE() BETWEEN s.data_od AND s.data_do
ORDER BY pr.nazwa;
2 wiersze
kodprzedmiotpunkty_ectsprowadzacy
INF-IOInżynieria oprogramowania4dr inż. Tomasz Wójcik
INF-SISztuczna inteligencja5prof. dr hab. inż. Andrzej Wiśniewski

DDziennik i indeks

14

Dziennik: oceny cząstkowe studenta z jednego przedmiotu w semestrze

Wymaganie 6, widok studenta: jego oceny cząstkowe z jednego przedmiotu w jednym semestrze.

dziennik
SELECT oc.data_wystawienia, oc.opis, oc.wartosc
FROM ocena_czastkowa oc
JOIN zajecia z    ON z.id  = oc.zajecia_id
JOIN przedmiot pr ON pr.id = z.przedmiot_id
WHERE oc.student_id = 1                                    -- parametr
  AND pr.kod = 'INF-BD'                                    -- parametr
  AND z.semestr_id = 2                                     -- parametr
ORDER BY oc.data_wystawienia;
4 wiersze
data_wystawieniaopiswartosc
2026-03-14kolokwium 13.5
2026-04-07kolokwium 23.0
2026-04-29laboratorium3.5
2026-05-19projekt3.5
15

Dziennik grupy: średnia cząstkowych i liczba ocen każdego studenta

Widok prowadzącego: dla każdego studenta grupy liczba ocen, średnia i ciąg ocen w kolejności wystawienia.

LEFT JOINAVGGROUP_CONCAT
--     z każdego przedmiotu (widok prowadzącego)
SELECT pr.nazwa AS przedmiot, st.nazwisko, st.imie,
       COUNT(oc.id)               AS liczba_ocen,
       ROUND(AVG(oc.wartosc), 2)  AS srednia_czastkowych,
       GROUP_CONCAT(oc.wartosc ORDER BY oc.data_wystawienia SEPARATOR ' | ') AS oceny
FROM zajecia z
JOIN przedmiot pr ON pr.id = z.przedmiot_id
JOIN student st   ON st.grupa_id = z.grupa_id
LEFT JOIN ocena_czastkowa oc ON oc.zajecia_id = z.id AND oc.student_id = st.id
WHERE z.grupa_id = 1                                       -- parametr
  AND z.semestr_id = 2                                     -- parametr
GROUP BY pr.nazwa, st.id, st.nazwisko, st.imie
ORDER BY pr.nazwa, st.nazwisko;
18 wierszy
przedmiotnazwiskoimieliczba_ocensrednia_czastkowychoceny
Algorytmy i struktury danychDudaJulia43.634.0 | 3.5 | 3.5 | 3.5
Algorytmy i struktury danychMichalskiSzymon43.634.0 | 3.0 | 4.5 | 3.0
Algorytmy i struktury danychNowickiJakub43.383.5 | 4.0 | 2.0 | 4.0
Algorytmy i struktury danychRutkowskaHanna43.755.0 | 3.5 | 3.5 | 3.0
Algorytmy i struktury danychSikoraMaja43.253.0 | 3.0 | 3.5 | 3.5
Algorytmy i struktury danychTomaszewskiAntoni43.03.5 | 2.0 | 3.0 | 3.5
Bazy danychDudaJulia43.383.0 | 3.5 | 3.5 | 3.5
Bazy danychMichalskiSzymon43.03.0 | 3.5 | 3.5 | 2.0
Bazy danychNowickiJakub43.383.5 | 3.0 | 3.5 | 3.5
Bazy danychRutkowskaHanna43.634.0 | 3.5 | 3.5 | 3.5
Bazy danychSikoraMaja43.133.5 | 3.5 | 3.5 | 2.0
Bazy danychTomaszewskiAntoni43.253.0 | 3.5 | 3.0 | 3.5
Systemy operacyjneDudaJulia44.04.5 | 4.0 | 3.5 | 4.0
Systemy operacyjneMichalskiSzymon43.634.0 | 4.0 | 3.0 | 3.5
Systemy operacyjneNowickiJakub43.884.5 | 4.5 | 3.0 | 3.5
Systemy operacyjneRutkowskaHanna44.04.5 | 4.0 | 3.5 | 4.0
Systemy operacyjneSikoraMaja43.753.5 | 4.0 | 4.5 | 3.0
Systemy operacyjneTomaszewskiAntoni43.53.0 | 4.0 | 3.5 | 3.5
16

Indeks studenta: oceny końcowe ze wszystkich semestrów

Indeks studenta: oceny końcowe ze wszystkich semestrów, z punktami ECTS i prowadzącym.

indeks
SELECT s.rok_akademicki, s.typ AS semestr, pr.nazwa AS przedmiot, pr.punkty_ects,
       ok.wartosc AS ocena_koncowa, ok.data_wystawienia,
       CONCAT(p.imie, ' ', p.nazwisko) AS wystawil_prowadzacy
FROM ocena_koncowa ok
JOIN zajecia z    ON z.id  = ok.zajecia_id
JOIN semestr s    ON s.id  = z.semestr_id
JOIN przedmiot pr ON pr.id = z.przedmiot_id
JOIN pracownik p  ON p.id  = z.pracownik_id
WHERE ok.student_id = 1                                    -- parametr
ORDER BY s.data_od, pr.nazwa;
6 wierszy
rok_akademickisemestrprzedmiotpunkty_ectsocena_koncowadata_wystawieniawystawil_prowadzacy
2025/2026ZIMOWYMatematyka dyskretna64.02026-02-08Małgorzata Kowalczyk
2025/2026ZIMOWYProgramowanie obiektowe63.02026-02-08Tomasz Wójcik
2025/2026ZIMOWYSieci komputerowe54.52026-02-08Joanna Mazur
2025/2026LETNIAlgorytmy i struktury danych63.52026-07-03Piotr Zieliński
2025/2026LETNIBazy danych53.52026-07-03Ewa Szymańska
2025/2026LETNISystemy operacyjne54.02026-07-03Marcin Dąbrowski
17

Średnia semestralna studentów ważona punktami ECTS

Średnia semestralna ważona punktami ECTS oraz suma zaliczonych ECTS, liczona tylko z ocen co najmniej 3.0.

średnia ważonaCASE
--     (ECTS zaliczone liczone tylko z ocen pozytywnych)
SELECT s.rok_akademicki, s.typ, g.nazwa AS grupa, st.nr_albumu, st.nazwisko, st.imie,
       ROUND(SUM(ok.wartosc * pr.punkty_ects) / SUM(pr.punkty_ects), 2) AS srednia_wazona,
       SUM(CASE WHEN ok.wartosc >= 3.0 THEN pr.punkty_ects ELSE 0 END)  AS ects_zaliczone
FROM ocena_koncowa ok
JOIN zajecia z    ON z.id  = ok.zajecia_id
JOIN semestr s    ON s.id  = z.semestr_id
JOIN przedmiot pr ON pr.id = z.przedmiot_id
JOIN student st   ON st.id = ok.student_id
JOIN grupa g      ON g.id  = st.grupa_id
WHERE s.id = 2                                             -- parametr: semestr
GROUP BY s.rok_akademicki, s.typ, g.nazwa, st.id, st.nr_albumu, st.nazwisko, st.imie
ORDER BY g.nazwa, srednia_wazona DESC;
24 wiersze
rok_akademickitypgrupanr_albumunazwiskoimiesrednia_wazonaects_zaliczone
2025/2026LETNIINF-1A251002DudaJulia3.8416
2025/2026LETNIINF-1A251001NowickiJakub3.6616
2025/2026LETNIINF-1A251006RutkowskaHanna3.6616
2025/2026LETNIINF-1A251005MichalskiSzymon3.3416
2025/2026LETNIINF-1A251004SikoraMaja3.1616
2025/2026LETNIINF-1A251003TomaszewskiAntoni3.016
2025/2026LETNIINF-1B252011ZającKacper4.6616
2025/2026LETNIINF-1B252008OlszewskaLena4.3116
2025/2026LETNIINF-1B252010PawlakAlicja4.3116
2025/2026LETNIINF-1B252007CzarneckiFilip3.6616
2025/2026LETNIINF-1B252012GórskaOliwia3.3416
2025/2026LETNIINF-1B252009MalinowskiJan2.8811
2025/2026LETNIINF-2A243018KowalskaAmelia5.014
2025/2026LETNIINF-2A243013GrabowskiMikołaj4.6814
2025/2026LETNIINF-2A243015NowakWojciech4.5414
2025/2026LETNIINF-2A243016KrólWiktoria3.514
2025/2026LETNIINF-2A243014MajewskaNatalia3.0710
2025/2026LETNIINF-2A243017WieczorekIgor3.09
2025/2026LETNIZAR-1A254024SzewczykZofia5.010
2025/2026LETNIZAR-1A254022PietrzakEmilia4.710
2025/2026LETNIZAR-1A254020BaranLaura4.510
2025/2026LETNIZAR-1A254019StępieńAdam4.210
2025/2026LETNIZAR-1A254021WróbelBartosz3.510
2025/2026LETNIZAR-1A254023JabłońskiOskar3.310
18

Ranking studentów w grupach (np. do stypendium rektora) — funkcje okna

Ranking studentów w każdej grupie za cały rok, np. do stypendium rektora. RANK() daje to samo miejsce przy równych średnich.

RANK() OVERpodzapytanie
SELECT grupa, nr_albumu, nazwisko, imie, srednia,
       RANK() OVER (PARTITION BY grupa ORDER BY srednia DESC) AS miejsce_w_grupie
FROM (
    SELECT g.nazwa AS grupa, st.nr_albumu, st.nazwisko, st.imie,
           ROUND(SUM(ok.wartosc * pr.punkty_ects) / SUM(pr.punkty_ects), 2) AS srednia
    FROM ocena_koncowa ok
    JOIN zajecia z    ON z.id  = ok.zajecia_id
    JOIN przedmiot pr ON pr.id = z.przedmiot_id
    JOIN student st   ON st.id = ok.student_id
    JOIN grupa g      ON g.id  = st.grupa_id
    JOIN semestr s    ON s.id  = z.semestr_id
    WHERE s.rok_akademicki = '2025/2026'                   -- parametr
    GROUP BY g.nazwa, st.id, st.nr_albumu, st.nazwisko, st.imie
) AS srednie
ORDER BY grupa, miejsce_w_grupie;
24 wiersze
grupanr_albumunazwiskoimiesredniamiejsce_w_grupie
INF-1A251002DudaJulia3.921
INF-1A251006RutkowskaHanna3.742
INF-1A251001NowickiJakub3.733
INF-1A251005MichalskiSzymon3.584
INF-1A251003TomaszewskiAntoni3.425
INF-1A251004SikoraMaja3.425
INF-1B252011ZającKacper4.761
INF-1B252008OlszewskaLena4.482
INF-1B252010PawlakAlicja4.233
INF-1B252007CzarneckiFilip3.914
INF-1B252012GórskaOliwia3.615
INF-1B252009MalinowskiJan2.766
INF-2A243018KowalskaAmelia5.01
INF-2A243015NowakWojciech4.522
INF-2A243013GrabowskiMikołaj4.233
INF-2A243016KrólWiktoria3.124
INF-2A243017WieczorekIgor3.085
INF-2A243014MajewskaNatalia3.056
ZAR-1A254022PietrzakEmilia4.861
ZAR-1A254024SzewczykZofia4.742
ZAR-1A254020BaranLaura4.363
ZAR-1A254019StępieńAdam4.214
ZAR-1A254021WróbelBartosz3.55
ZAR-1A254023JabłońskiOskar3.296
19

Studenci z oceną niedostateczną (2.0) — lista do poprawek / warunku

Oceny niedostateczne z ocen końcowych: lista do poprawek albo wpisu warunkowego.

filtr
SELECT s.rok_akademicki, s.typ, g.nazwa AS grupa, st.nr_albumu, st.nazwisko, st.imie,
       pr.nazwa AS przedmiot, CONCAT(p.imie, ' ', p.nazwisko) AS prowadzacy
FROM ocena_koncowa ok
JOIN zajecia z    ON z.id  = ok.zajecia_id
JOIN semestr s    ON s.id  = z.semestr_id
JOIN przedmiot pr ON pr.id = z.przedmiot_id
JOIN pracownik p  ON p.id  = z.pracownik_id
JOIN student st   ON st.id = ok.student_id
JOIN grupa g      ON g.id  = st.grupa_id
WHERE ok.wartosc = 2.0
ORDER BY s.data_od, g.nazwa, st.nazwisko;
6 wierszy
rok_akademickitypgrupanr_albumunazwiskoimieprzedmiotprowadzacy
2025/2026ZIMOWYINF-1B252009MalinowskiJanMatematyka dyskretnaMałgorzata Kowalczyk
2025/2026ZIMOWYINF-2A243016KrólWiktoriaAlgorytmy i struktury danychPiotr Zieliński
2025/2026ZIMOWYINF-2A243014MajewskaNataliaBazy danychEwa Szymańska
2025/2026LETNIINF-1B252009MalinowskiJanSystemy operacyjneMarcin Dąbrowski
2025/2026LETNIINF-2A243014MajewskaNataliaInżynieria oprogramowaniaTomasz Wójcik
2025/2026LETNIINF-2A243017WieczorekIgorSztuczna inteligencjaAndrzej Wiśniewski
20

Brakujące wpisy: zakończone zajęcia, z których student nie ma oceny końcowej

Zakończone zajęcia, z których student nie ma oceny końcowej. W danych przykładowych jest jeden celowy brak.

LEFT JOIN … IS NULL
SELECT s.rok_akademicki, s.typ, g.nazwa AS grupa, pr.nazwa AS przedmiot,
       st.nr_albumu, st.nazwisko, st.imie, CONCAT(p.imie, ' ', p.nazwisko) AS prowadzacy
FROM zajecia z
JOIN semestr s    ON s.id  = z.semestr_id
JOIN przedmiot pr ON pr.id = z.przedmiot_id
JOIN pracownik p  ON p.id  = z.pracownik_id
JOIN grupa g      ON g.id  = z.grupa_id
JOIN student st   ON st.grupa_id = z.grupa_id
LEFT JOIN ocena_koncowa ok ON ok.zajecia_id = z.id AND ok.student_id = st.id
WHERE s.data_do < CURDATE()
  AND ok.id IS NULL;
1 wiersz
rok_akademickitypgrupaprzedmiotnr_albumunazwiskoimieprowadzacy
2025/2026ZIMOWYZAR-1APodstawy zarządzania254021WróbelBartoszAgnieszka Kamińska
21

Statystyki przedmiotów: średnia, rozkład ocen końcowych i zdawalność

Statystyki przedmiotu w semestrze: średnia, rozkład ocen i odsetek zdających. W MySQL wyrażenie logiczne to 0/1, więc SUM liczy wystąpienia.

SUM(warunek)AVG
SELECT pr.nazwa AS przedmiot, s.rok_akademicki, s.typ,
       COUNT(*)                                          AS ocen,
       ROUND(AVG(ok.wartosc), 2)                         AS srednia,
       SUM(ok.wartosc = 5.0)                             AS bdb,
       SUM(ok.wartosc IN (4.0, 4.5))                     AS db,
       SUM(ok.wartosc IN (3.0, 3.5))                     AS dst,
       SUM(ok.wartosc = 2.0)                             AS ndst,
       ROUND(100 * AVG(ok.wartosc >= 3.0), 1)            AS zdawalnosc_proc
FROM ocena_koncowa ok
JOIN zajecia z    ON z.id  = ok.zajecia_id
JOIN przedmiot pr ON pr.id = z.przedmiot_id
JOIN semestr s    ON s.id  = z.semestr_id
GROUP BY pr.id, pr.nazwa, s.id, s.rok_akademicki, s.typ
ORDER BY s.id, zdawalnosc_proc, pr.nazwa;
16 wierszy
przedmiotrok_akademickitypocensredniabdbdbdstndstzdawalnosc_proc
Algorytmy i struktury danych2025/2026ZIMOWY63.67122183.3
Bazy danych2025/2026ZIMOWY63.58113183.3
Matematyka dyskretna2025/2026ZIMOWY124.0191191.7
Mikroekonomia2025/2026ZIMOWY64.081320100.0
Podstawy zarządzania2025/2026ZIMOWY54.31310100.0
Programowanie obiektowe2025/2026ZIMOWY123.711380100.0
Sieci komputerowe2025/2026ZIMOWY124.131830100.0
Systemy operacyjne2025/2026ZIMOWY63.921230100.0
Inżynieria oprogramowania2025/2026LETNI63.67122183.3
Sztuczna inteligencja2025/2026LETNI63.67122183.3
Systemy operacyjne2025/2026LETNI123.75155191.7
Algorytmy i struktury danych2025/2026LETNI123.630480100.0
Bazy danych2025/2026LETNI123.581380100.0
Marketing2025/2026LETNI64.252220100.0
Rachunkowość finansowa2025/2026LETNI64.171320100.0
Sieci komputerowe2025/2026LETNI64.53300100.0
22

Porównanie: średnia cząstkowych a ocena końcowa (kontrola rozbieżności >= 0.5)

Kontrola jakości: ocena końcowa różni się od średniej cząstkowych o co najmniej 0.5.

HAVING
SELECT st.nazwisko, st.imie, pr.nazwa AS przedmiot, s.rok_akademicki, s.typ,
       ROUND(AVG(oc.wartosc), 2) AS srednia_czastkowych,
       ok.wartosc                AS ocena_koncowa
FROM ocena_koncowa ok
JOIN zajecia z           ON z.id  = ok.zajecia_id
JOIN semestr s           ON s.id  = z.semestr_id
JOIN przedmiot pr        ON pr.id = z.przedmiot_id
JOIN student st          ON st.id = ok.student_id
JOIN ocena_czastkowa oc  ON oc.zajecia_id = ok.zajecia_id AND oc.student_id = ok.student_id
GROUP BY ok.id, st.nazwisko, st.imie, pr.nazwa, s.rok_akademicki, s.typ, ok.wartosc
HAVING ABS(AVG(oc.wartosc) - ok.wartosc) >= 0.5
ORDER BY ABS(AVG(oc.wartosc) - ok.wartosc) DESC;
11 wierszy
nazwiskoimieprzedmiotrok_akademickitypsrednia_czastkowychocena_koncowa
MalinowskiJanMatematyka dyskretna2025/2026ZIMOWY2.882.0
MajewskaNataliaBazy danych2025/2026ZIMOWY2.632.0
WieczorekIgorSztuczna inteligencja2025/2026LETNI2.632.0
RutkowskaHannaSieci komputerowe2025/2026ZIMOWY4.54.0
PawlakAlicjaMatematyka dyskretna2025/2026ZIMOWY4.54.0
ZającKacperSieci komputerowe2025/2026ZIMOWY5.04.5
JabłońskiOskarPodstawy zarządzania2025/2026ZIMOWY3.53.0
SzewczykZofiaPodstawy zarządzania2025/2026ZIMOWY5.04.5
BaranLauraMikroekonomia2025/2026ZIMOWY4.54.0
TomaszewskiAntoniSystemy operacyjne2025/2026LETNI3.53.0
WieczorekIgorInżynieria oprogramowania2025/2026LETNI2.53.0
23

Bieżący semestr: kto nie ma jeszcze żadnej oceny cząstkowej z danych zajęć

Bieżący semestr: studenci, którzy z danych zajęć nie mają jeszcze żadnej oceny cząstkowej.

NOT EXISTS
SELECT g.nazwa AS grupa, pr.nazwa AS przedmiot, st.nr_albumu, st.nazwisko, st.imie
FROM zajecia z
JOIN semestr s    ON s.id  = z.semestr_id
JOIN przedmiot pr ON pr.id = z.przedmiot_id
JOIN grupa g      ON g.id  = z.grupa_id
JOIN student st   ON st.grupa_id = z.grupa_id
WHERE CURDATE() BETWEEN s.data_od AND s.data_do
  AND NOT EXISTS (SELECT 1 FROM ocena_czastkowa oc
                  WHERE oc.zajecia_id = z.id AND oc.student_id = st.id)
ORDER BY g.nazwa, pr.nazwa, st.nazwisko;
8 wierszy
grupaprzedmiotnr_albumunazwiskoimie
INF-1AInżynieria oprogramowania251001NowickiJakub
INF-1ASztuczna inteligencja251001NowickiJakub
INF-1ASztuczna inteligencja251003TomaszewskiAntoni
INF-1BInżynieria oprogramowania252009MalinowskiJan
INF-2AProjekt zespołowy243013GrabowskiMikołaj
ZAR-1ARachunkowość finansowa254020BaranLaura
ZAR-1ARachunkowość finansowa254024SzewczykZofia
ZAR-1AZarządzanie projektami254020BaranLaura

EKontrola spójności

24

Oceny wystawione studentom spoza grupy, dla której prowadzone są zajęcia

Reguła, której schemat nie wymusza kluczem obcym: oceniany student musi należeć do grupy z zajęć. Poprawne dane dają 0 wierszy.

UNION ALLspójność
SELECT 'cząstkowa' AS rodzaj, oc.id, oc.student_id, oc.zajecia_id
FROM ocena_czastkowa oc
JOIN zajecia z  ON z.id = oc.zajecia_id
JOIN student st ON st.id = oc.student_id
WHERE st.grupa_id <> z.grupa_id
UNION ALL
SELECT 'końcowa', ok.id, ok.student_id, ok.zajecia_id
FROM ocena_koncowa ok
JOIN zajecia z  ON z.id = ok.zajecia_id
JOIN student st ON st.id = ok.student_id
WHERE st.grupa_id <> z.grupa_id;
0 wierszy Dane są spójne.
25

Zajęcia przypisane wykładowcy, który w trakcie semestru już nie pracował

Zajęcia w semestrze, który zaczął się po zwolnieniu prowadzącego. Poprawne dane dają 0 wierszy.

spójność
SELECT z.id, CONCAT(p.imie, ' ', p.nazwisko) AS wykladowca, p.data_zwolnienia,
       s.rok_akademicki, s.typ
FROM zajecia z
JOIN pracownik p ON p.id = z.pracownik_id
JOIN semestr s   ON s.id = z.semestr_id
WHERE p.data_zwolnienia < s.data_od;
0 wierszy Dane są spójne.

FOperacje zapisu

26

Wpis oceny cząstkowej do dziennika

Wpis oceny do dziennika. CHECK odrzuci wartość spoza skali 2.0–5.0.

INSERT
INSERT INTO ocena_czastkowa (zajecia_id, student_id, wartosc, opis, data_wystawienia)
VALUES (24, 1, 4.5, 'kolokwium 1', CURDATE());              -- parametr
1 wiersz zmieniony W pliku zapytanie jest w transakcji zakończonej ROLLBACK, więc dane się nie zmieniają.
27

Przeniesienie studenta do innej grupy

Przeniesienie studenta do innej grupy. Jego historia ocen zostaje przy zajęciach starej grupy.

UPDATE
UPDATE student SET grupa_id = 2 WHERE nr_albumu = '251001'; -- parametr
1 wiersz zmieniony W pliku zapytanie jest w transakcji zakończonej ROLLBACK, więc dane się nie zmieniają.
28

Rozwiązanie stosunku pracy

Rozwiązanie stosunku pracy. CHECK pilnuje, żeby data nie była wcześniejsza niż zatrudnienie.

UPDATE
UPDATE pracownik SET data_zwolnienia = '2027-01-31' WHERE id = 6;  -- parametr
1 wiersz zmieniony W pliku zapytanie jest w transakcji zakończonej ROLLBACK, więc dane się nie zmieniają.

DDL — schema.sql

PokażUkryj
-- System uczelni - schemat bazy danych (MySQL 8.0.16+)
-- Wygenerowano z modelu w build.py; bez danych testowych.

DROP DATABASE IF EXISTS uczelnia;
CREATE DATABASE uczelnia CHARACTER SET utf8mb4 COLLATE utf8mb4_polish_ci;
USE uczelnia;

CREATE TABLE adres (
    id                   INT UNSIGNED NOT NULL AUTO_INCREMENT,
    ulica                VARCHAR(100) NOT NULL,
    nr_domu              VARCHAR(10) NOT NULL,
    nr_lokalu            VARCHAR(10) NULL,
    kod_pocztowy         CHAR(6) NOT NULL,
    miejscowosc          VARCHAR(80) NOT NULL,
    kraj                 VARCHAR(60) NOT NULL DEFAULT 'Polska',
    CONSTRAINT pk_adres PRIMARY KEY (id),
    CONSTRAINT chk_adres_kod CHECK (kod_pocztowy REGEXP '^[0-9]{2}-[0-9]{3}$')
) ENGINE=InnoDB COMMENT='Adresy placówek i miejsca zamieszkania pracowników';

CREATE TABLE uczelnia (
    id                   INT UNSIGNED NOT NULL AUTO_INCREMENT,
    nazwa                VARCHAR(200) NOT NULL,
    skrot                VARCHAR(20) NULL,
    nip                  CHAR(10) NOT NULL,
    regon                VARCHAR(14) NOT NULL,
    adres_id             INT UNSIGNED NOT NULL,
    telefon              VARCHAR(20) NOT NULL,
    email                VARCHAR(120) NOT NULL,
    www                  VARCHAR(200) NULL,
    CONSTRAINT pk_uczelnia PRIMARY KEY (id),
    CONSTRAINT uq_uczelnia_nip UNIQUE (nip),
    CONSTRAINT uq_uczelnia_regon UNIQUE (regon),
    CONSTRAINT fk_uczelnia_adres FOREIGN KEY (adres_id) REFERENCES adres (id)
) ENGINE=InnoDB COMMENT='Dane placówki';

CREATE TABLE wydzial (
    id                   INT UNSIGNED NOT NULL AUTO_INCREMENT,
    uczelnia_id          INT UNSIGNED NOT NULL,
    nazwa                VARCHAR(150) NOT NULL,
    skrot                VARCHAR(20) NULL,
    CONSTRAINT pk_wydzial PRIMARY KEY (id),
    CONSTRAINT uq_wydzial_nazwa UNIQUE (uczelnia_id, nazwa),
    CONSTRAINT fk_wydzial_uczelnia FOREIGN KEY (uczelnia_id) REFERENCES uczelnia (id)
) ENGINE=InnoDB COMMENT='Wydziały uczelni';

CREATE TABLE sekretariat (
    id                   INT UNSIGNED NOT NULL AUTO_INCREMENT,
    uczelnia_id          INT UNSIGNED NOT NULL,
    wydzial_id           INT UNSIGNED NULL COMMENT 'NULL = sekretariat główny / rektorat',
    nazwa                VARCHAR(120) NOT NULL,
    telefon              VARCHAR(20) NOT NULL,
    email                VARCHAR(120) NOT NULL,
    nr_pokoju            VARCHAR(10) NULL,
    godziny_otwarcia     VARCHAR(120) NULL,
    CONSTRAINT pk_sekretariat PRIMARY KEY (id),
    CONSTRAINT fk_sekretariat_uczelnia FOREIGN KEY (uczelnia_id) REFERENCES uczelnia (id),
    CONSTRAINT fk_sekretariat_wydzial FOREIGN KEY (wydzial_id) REFERENCES wydzial (id)
) ENGINE=InnoDB COMMENT='Sekretariaty: wydzial_id NULL = sekretariat główny (rektorat)';

CREATE TABLE pracownik (
    id                   INT UNSIGNED NOT NULL AUTO_INCREMENT,
    imie                 VARCHAR(50) NOT NULL,
    nazwisko             VARCHAR(80) NOT NULL,
    plec                 ENUM('K','M') NOT NULL,
    pesel                CHAR(11) NOT NULL,
    tytul_naukowy        VARCHAR(40) NULL,
    telefon              VARCHAR(20) NOT NULL,
    email                VARCHAR(120) NOT NULL,
    adres_id             INT UNSIGNED NOT NULL,
    data_zatrudnienia    DATE NOT NULL,
    data_zwolnienia      DATE NULL COMMENT 'Data ustania stosunku pracy',
    nr_konta             CHAR(28) NOT NULL COMMENT 'IBAN: PL + 26 cyfr',
    CONSTRAINT pk_pracownik PRIMARY KEY (id),
    CONSTRAINT uq_pracownik_pesel UNIQUE (pesel),
    CONSTRAINT uq_pracownik_email UNIQUE (email),
    INDEX idx_pracownik_nazwisko (nazwisko, imie),
    CONSTRAINT fk_pracownik_adres FOREIGN KEY (adres_id) REFERENCES adres (id),
    CONSTRAINT chk_pracownik_pesel CHECK (pesel REGEXP '^[0-9]{11}$'),
    CONSTRAINT chk_pracownik_konto CHECK (nr_konta REGEXP '^PL[0-9]{26}$'),
    CONSTRAINT chk_pracownik_daty CHECK (data_zwolnienia IS NULL OR data_zwolnienia >= data_zatrudnienia)
) ENGINE=InnoDB COMMENT='Pracownicy dydaktyczni';

CREATE TABLE funkcja_kierownicza (
    id                   INT UNSIGNED NOT NULL AUTO_INCREMENT,
    pracownik_id         INT UNSIGNED NOT NULL,
    uczelnia_id          INT UNSIGNED NOT NULL,
    wydzial_id           INT UNSIGNED NULL COMMENT 'Wymagane dla dziekana/prodziekana',
    funkcja              ENUM('REKTOR','PROREKTOR','DZIEKAN','PRODZIEKAN','KANCLERZ') NOT NULL,
    data_od              DATE NOT NULL,
    data_do              DATE NULL,
    CONSTRAINT pk_funkcja_kierownicza PRIMARY KEY (id),
    CONSTRAINT fk_funkcja_kierownicza_pracownik FOREIGN KEY (pracownik_id) REFERENCES pracownik (id),
    CONSTRAINT fk_funkcja_kierownicza_uczelnia FOREIGN KEY (uczelnia_id) REFERENCES uczelnia (id),
    CONSTRAINT fk_funkcja_kierownicza_wydzial FOREIGN KEY (wydzial_id) REFERENCES wydzial (id),
    CONSTRAINT chk_funkcja_daty CHECK (data_do IS NULL OR data_do >= data_od),
    CONSTRAINT chk_funkcja_wydzial CHECK ((funkcja IN ('DZIEKAN','PRODZIEKAN')) = (wydzial_id IS NOT NULL))
) ENGINE=InnoDB COMMENT='Rektor, prorektorzy, dziekani - z historią kadencji';

CREATE TABLE przedmiot (
    id                   INT UNSIGNED NOT NULL AUTO_INCREMENT,
    wydzial_id           INT UNSIGNED NOT NULL,
    kod                  VARCHAR(20) NOT NULL,
    nazwa                VARCHAR(150) NOT NULL,
    punkty_ects          TINYINT UNSIGNED NOT NULL,
    CONSTRAINT pk_przedmiot PRIMARY KEY (id),
    CONSTRAINT uq_przedmiot_kod UNIQUE (kod),
    CONSTRAINT fk_przedmiot_wydzial FOREIGN KEY (wydzial_id) REFERENCES wydzial (id),
    CONSTRAINT chk_przedmiot_ects CHECK (punkty_ects BETWEEN 1 AND 30)
) ENGINE=InnoDB COMMENT='Katalog przedmiotów';

CREATE TABLE semestr (
    id                   INT UNSIGNED NOT NULL AUTO_INCREMENT,
    rok_akademicki       CHAR(9) NOT NULL COMMENT 'np. 2026/2027',
    typ                  ENUM('ZIMOWY','LETNI') NOT NULL,
    data_od              DATE NOT NULL,
    data_do              DATE NOT NULL,
    CONSTRAINT pk_semestr PRIMARY KEY (id),
    CONSTRAINT uq_semestr UNIQUE (rok_akademicki, typ),
    CONSTRAINT chk_semestr_rok CHECK (rok_akademicki REGEXP '^[0-9]{4}/[0-9]{4}$'),
    CONSTRAINT chk_semestr_daty CHECK (data_do > data_od)
) ENGINE=InnoDB COMMENT='Semestry akademickie';

CREATE TABLE grupa (
    id                   INT UNSIGNED NOT NULL AUTO_INCREMENT,
    wydzial_id           INT UNSIGNED NOT NULL,
    nazwa                VARCHAR(50) NOT NULL,
    rocznik              YEAR NOT NULL,
    opiekun_id           INT UNSIGNED NOT NULL COMMENT 'Opiekun grupy - jeden z wykładowców',
    CONSTRAINT pk_grupa PRIMARY KEY (id),
    CONSTRAINT uq_grupa UNIQUE (nazwa, rocznik),
    CONSTRAINT fk_grupa_wydzial FOREIGN KEY (wydzial_id) REFERENCES wydzial (id),
    CONSTRAINT fk_grupa_opiekun FOREIGN KEY (opiekun_id) REFERENCES pracownik (id)
) ENGINE=InnoDB COMMENT='Grupy studenckie z opiekunem';

CREATE TABLE student (
    id                   INT UNSIGNED NOT NULL AUTO_INCREMENT,
    nr_albumu            VARCHAR(10) NOT NULL,
    imie                 VARCHAR(50) NOT NULL,
    nazwisko             VARCHAR(80) NOT NULL,
    plec                 ENUM('K','M') NOT NULL,
    pesel                CHAR(11) NOT NULL,
    data_urodzenia       DATE NOT NULL,
    grupa_id             INT UNSIGNED NOT NULL,
    CONSTRAINT pk_student PRIMARY KEY (id),
    CONSTRAINT uq_student_nr_albumu UNIQUE (nr_albumu),
    CONSTRAINT uq_student_pesel UNIQUE (pesel),
    INDEX idx_student_nazwisko (nazwisko, imie),
    CONSTRAINT fk_student_grupa FOREIGN KEY (grupa_id) REFERENCES grupa (id),
    CONSTRAINT chk_student_pesel CHECK (pesel REGEXP '^[0-9]{11}$')
) ENGINE=InnoDB COMMENT='Studenci';

CREATE TABLE zajecia (
    id                   INT UNSIGNED NOT NULL AUTO_INCREMENT,
    przedmiot_id         INT UNSIGNED NOT NULL,
    pracownik_id         INT UNSIGNED NOT NULL,
    grupa_id             INT UNSIGNED NOT NULL,
    semestr_id           INT UNSIGNED NOT NULL,
    CONSTRAINT pk_zajecia PRIMARY KEY (id),
    CONSTRAINT uq_zajecia UNIQUE (przedmiot_id, grupa_id, semestr_id),
    CONSTRAINT fk_zajecia_przedmiot FOREIGN KEY (przedmiot_id) REFERENCES przedmiot (id),
    CONSTRAINT fk_zajecia_pracownik FOREIGN KEY (pracownik_id) REFERENCES pracownik (id),
    CONSTRAINT fk_zajecia_grupa FOREIGN KEY (grupa_id) REFERENCES grupa (id),
    CONSTRAINT fk_zajecia_semestr FOREIGN KEY (semestr_id) REFERENCES semestr (id)
) ENGINE=InnoDB COMMENT='Kto prowadzi dany przedmiot w danej grupie i semestrze';

CREATE TABLE ocena_czastkowa (
    id                   INT UNSIGNED NOT NULL AUTO_INCREMENT,
    zajecia_id           INT UNSIGNED NOT NULL,
    student_id           INT UNSIGNED NOT NULL,
    wartosc              DECIMAL(2,1) NOT NULL,
    opis                 VARCHAR(100) NULL COMMENT 'np. kolokwium 1',
    data_wystawienia     DATE NOT NULL,
    CONSTRAINT pk_ocena_czastkowa PRIMARY KEY (id),
    INDEX idx_ocena_cz_student (student_id, zajecia_id),
    CONSTRAINT fk_ocena_czastkowa_zajecia FOREIGN KEY (zajecia_id) REFERENCES zajecia (id),
    CONSTRAINT fk_ocena_czastkowa_student FOREIGN KEY (student_id) REFERENCES student (id),
    CONSTRAINT chk_ocena_cz_wartosc CHECK (wartosc IN (2.0, 3.0, 3.5, 4.0, 4.5, 5.0))
) ENGINE=InnoDB COMMENT='Dziennik: oceny cząstkowe';

CREATE TABLE ocena_koncowa (
    id                   INT UNSIGNED NOT NULL AUTO_INCREMENT,
    zajecia_id           INT UNSIGNED NOT NULL,
    student_id           INT UNSIGNED NOT NULL,
    wartosc              DECIMAL(2,1) NOT NULL,
    data_wystawienia     DATE NOT NULL,
    CONSTRAINT pk_ocena_koncowa PRIMARY KEY (id),
    CONSTRAINT uq_ocena_koncowa UNIQUE (zajecia_id, student_id),
    CONSTRAINT fk_ocena_koncowa_zajecia FOREIGN KEY (zajecia_id) REFERENCES zajecia (id),
    CONSTRAINT fk_ocena_koncowa_student FOREIGN KEY (student_id) REFERENCES student (id),
    CONSTRAINT chk_ocena_k_wartosc CHECK (wartosc IN (2.0, 3.0, 3.5, 4.0, 4.5, 5.0))
) ENGINE=InnoDB COMMENT='Indeks: ocena końcowa z zajęć w semestrze';