Jesse Flantua - 101729
Een sportschool wil een systeem waarin leden, lessen, instructeurs, apparaten en persoonlijke trainingsschema’s worden bijgehouden. Van elk lid worden de naam, het lidnummer, het abonnementstype en de vervaldatum van het lidmaatschap geregistreerd. De sportschool biedt groepslessen aan, zoals spinning of yoga, met een vaste instructeur, dag en tijd. Leden kunnen zich inschrijven voor lessen en er wordt geregistreerd of ze aanwezig waren. Daarnaast kunnen leden een persoonlijk trainingsschema krijgen, waarin per oefening wordt vastgelegd op welk apparaat getraind wordt, hoeveel sets en herhalingen nodig zijn, en wat het trainingsdoel is. De sportschool wil ook de onderhoudsdata van de apparaten kunnen bijhouden. Het systeem moet overzichten kunnen genereren van actieve schema’s, bezetting van apparaten en deelname aan groepslessen.
In deze stap selecteren we alle zelfstandig naamwoorden, acties en tijd/plaats/hoeveelheid aanduidingen uit de casus tekst.
Zelfstandig naamwoorden:
Acties:
We groeperen nu wat bij elkaar hoort:
Op basis van stap 3 kunnen we de volgende entiteiten identificeren met hun eigenschappen:
Gemaakte keuzes:
Gemaakte keuzes:
Gemaakte keuzes:
Gemaakte keuzes:
Gemaakte keuzes:
Gemaakte keuzes:
Gemaakte keuzes:
We bepalen nu voor elke entiteit wat de primary-key wordt:
Keuze: Lidnummer is uniek per lid en staat al in de casus beschreven, dus is een logische primary key.
Keuze: Een code is geschikt als primary key omdat het kort en uniek is.
Keuze: We voegen een kunstmatige ID toe omdat de combinatie van eigenschappen niet uniek genoeg is (er kunnen meerdere lessen met dezelfde naam op verschillende momenten zijn).
Keuze: Een kunstmatige ID is het meest praktisch omdat apparaatnamen kunnen veranderen.
Keuze: Een kunstmatige ID omdat meerdere schema’s dezelfde naam kunnen hebben.
Keuze: Een kunstmatige ID omdat oefeningnamen kunnen veranderen of dubbelzinnig zijn.
Keuze: Een kunstmatige ID omdat meerdere onderhoudsacties op dezelfde datum kunnen plaatsvinden.
Nu verwerken we de acties die we in stap 3 hebben geïdentificeerd:
Dit is een veel-op-veel relatie:
Dit levert een nieuwe entiteit op: Inschrijving
Inschrijving:
Keuze:
Deze relatie is al verwerkt in stap 5: Trainingsschema heeft een foreign key naar Lid (lidnummer).
Dit levert een nieuwe entiteit op die de koppeling maakt tussen Trainingsschema en Oefening: Schema_oefening
Schema_oefening:
Keuze:
Opmerking: apparaatID zou NULL kunnen zijn voor oefeningen die geen apparaat nodig hebben (bijv. push-ups).
Deze is al verwerkt in stap 5 met de Onderhoud entiteit.
We controleren of alle entiteiten logische relaties hebben:
✓ Lid - gekoppeld aan Inschrijving en Trainingsschema ✓ Instructeur - gekoppeld aan Les ✓ Les - gekoppeld aan Instructeur en Inschrijving ✓ Apparaat - gekoppeld aan Schema_oefening en Onderhoud ✓ Trainingsschema - gekoppeld aan Lid en Schema_oefening ✓ Oefening - gekoppeld aan Schema_oefening ✓ Onderhoud - gekoppeld aan Apparaat ✓ Inschrijving - koppeltabel tussen Lid en Les ✓ Schema_oefening - koppeltabel tussen Trainingsschema en Oefening
Alle entiteiten hebben logische relaties. Er zijn geen zwevende entiteiten die nergens aan gekoppeld zijn.
Controle 1: Instructeurnaam bij Les?
Controle 2: Lidnaam bij Inschrijving?
Controle 3: Apparaatnaam bij Schema_oefening?
Controle 4: Oefeningnaam bij Schema_oefening?
Conclusie: Geen fouten geconstateerd. Het ontwerp is vrij van redundantie en alle eigenschappen zijn correct afhankelijk van hun primary keys.
Nu bepalen we voor elke eigenschap het juiste datatype:
INT (primary key, auto-increment mogelijk)VARCHAR(100) (ruim genoeg voor voor- en achternaam samen)VARCHAR(50) (bijv. “Basis”, “Premium”, “Student”)DATEVARCHAR(10) (bijv. “INST001”, “JD”)VARCHAR(100)INT (primary key, auto-increment)VARCHAR(50) (bijv. “Spinning”, “Yoga”, “CrossFit”)VARCHAR(10) (bijv. “Maandag”, “Dinsdag”) of ENUM('Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag', 'Zondag')TIME (bijv. “18:00:00”)VARCHAR(10) (foreign key)Keuze voor dag: ENUM is efficiënter maar minder flexibel. VARCHAR geeft meer vrijheid voor verschillende schrijfwijzen.
INT (primary key, auto-increment)VARCHAR(100) (bijv. “Loopband 1”, “Roeitrainer Links”)VARCHAR(50) (bijv. “Cardio”, “Kracht”, “Vrije gewichten”)DATEINT (primary key, auto-increment)VARCHAR(100) (bijv. “Beginners schema”, “Spiermassa opbouw”)DATETEXT (kan een langere beschrijving zijn)INT (foreign key)INT (primary key, auto-increment)VARCHAR(100) (bijv. “Squats”, “Bankdrukken”, “Bicep curls”)TEXT (uitleg hoe de oefening uitgevoerd moet worden)INT (primary key, auto-increment)DATETEXT (wat er onderhouden is)INT (foreign key)INT (primary key deel 1, foreign key)INT (primary key deel 2, foreign key)BOOLEAN of TINYINT(1) (0 = niet aanwezig, 1 = aanwezig)DATEINT (primary key deel 1, foreign key)INT (primary key deel 2, foreign key)INT (primary key deel 3, volgorde in schema)INT (foreign key, kan NULL zijn)INT (aantal sets, bijv. 3)INT (aantal herhalingen per set, bijv. 12)VARCHAR(255) (specifiek doel voor deze oefening)| Kolom | Datatype | Key | Beschrijving |
|---|---|---|---|
| lidnummer | INT | PK | Uniek identificatienummer van het lid |
| naam | VARCHAR(100) | Volledige naam van het lid | |
| abonnementstype | VARCHAR(50) | Type abonnement (Basis, Premium, etc.) | |
| vervaldatum | DATE | Datum waarop het abonnement verloopt |
| Kolom | Datatype | Key | Beschrijving |
|---|---|---|---|
| instructeurcode | VARCHAR(10) | PK | Unieke code van de instructeur |
| instructeurnaam | VARCHAR(100) | Naam van de instructeur |
| Kolom | Datatype | Key | Beschrijving |
|---|---|---|---|
| lesID | INT | PK | Uniek identificatienummer van de les |
| lesnaam | VARCHAR(50) | Naam van de les (Spinning, Yoga, etc.) | |
| dag | VARCHAR(10) | Dag van de week waarop de les plaatsvindt | |
| tijd | TIME | Tijdstip waarop de les begint | |
| instructeurcode | VARCHAR(10) | FK | Code van de instructeur die de les geeft |
| Kolom | Datatype | Key | Beschrijving |
|---|---|---|---|
| apparaatID | INT | PK | Uniek identificatienummer van het apparaat |
| apparaatnaam | VARCHAR(100) | Naam van het apparaat | |
| apparaattype | VARCHAR(50) | Type apparaat (Cardio, Kracht, etc.) | |
| aanschafdatum | DATE | Datum waarop het apparaat is aangeschaft |
| Kolom | Datatype | Key | Beschrijving |
|---|---|---|---|
| schemaID | INT | PK | Uniek identificatienummer van het schema |
| schemanaam | VARCHAR(100) | Naam van het trainingsschema | |
| startdatum | DATE | Datum waarop het schema start | |
| trainingsdoel | TEXT | Algemeen doel van het trainingsschema | |
| lidnummer | INT | FK | Lidnummer van het lid dat dit schema heeft |
| Kolom | Datatype | Key | Beschrijving |
|---|---|---|---|
| oefeningID | INT | PK | Uniek identificatienummer van de oefening |
| oefeningnaam | VARCHAR(100) | Naam van de oefening | |
| beschrijving | TEXT | Uitleg hoe de oefening wordt uitgevoerd |
| Kolom | Datatype | Key | Beschrijving |
|---|---|---|---|
| onderhoudID | INT | PK | Uniek identificatienummer van de onderhoudsactie |
| onderhoudsdatum | DATE | Datum waarop het onderhoud plaatsvond | |
| beschrijving | TEXT | Beschrijving van wat er onderhouden is | |
| apparaatID | INT | FK | ID van het apparaat dat onderhouden is |
| Kolom | Datatype | Key | Beschrijving |
|---|---|---|---|
| lesID | INT | PK, FK | ID van de les waarvoor ingeschreven is |
| lidnummer | INT | PK, FK | Lidnummer van het ingeschreven lid |
| aanwezig | BOOLEAN | Of het lid aanwezig was (1) of niet (0) | |
| inschrijfdatum | DATE | Datum waarop de inschrijving plaatsvond |
| Kolom | Datatype | Key | Beschrijving |
|---|---|---|---|
| schemaID | INT | PK, FK | ID van het trainingsschema |
| oefeningID | INT | PK, FK | ID van de oefening |
| volgnummer | INT | PK | Volgorde van de oefening in het schema |
| apparaatID | INT | FK | ID van het te gebruiken apparaat (kan NULL) |
| sets | INT | Aantal sets van deze oefening | |
| herhalingen | INT | Aantal herhalingen per set | |
| oefening_doel | VARCHAR(255) | Specifiek doel voor deze oefening |
Het database ontwerp voor de sportschool is nu compleet. We hebben 9 tabellen geïdentificeerd:
Alle stappen van het stappenplan zijn doorlopen en er zijn bewuste keuzes gemaakt voor datatypes en relaties. Het ontwerp is gecontroleerd op redundantie en afhankelijkheid van primary keys.
In de volgende stap (Onderdeel B) kan dit ontwerp geïmplementeerd worden in een daadwerkelijke database met alle tabellen, kolommen, datatypes en foreign key constraints.
Naam: sportschool_systeem
De database bestaat uit 9 tabellen zoals ontworpen in Onderdeel A:
Het volledige SQL script voor het aanmaken van de database en alle tabellen is hieronder weergegeven, je kunt dit script ook vinden in het bestand sportschool_create_database.sql.
-- ===================================
-- CASUS 9: SPORTSCHOOL DATABASE SETUP
-- ===================================
-- Dit SQL script maakt de volledige database aan voor het sportschool systeem
-- met alle tabellen, kolommen, datatypes en foreign key constraints.
--
-- Database naam: sportschool_systeem
-- ===================================
-- Database aanmaken (indien nodig, verwijder als deze al bestaat)
-- DROP DATABASE IF EXISTS sportschool_systeem;
-- CREATE DATABASE sportschool_systeem CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- USE sportschool_systeem;
-- ===================================
-- TABEL 1: Lid
-- ===================================
-- Bevat alle leden van de sportschool
CREATE TABLE Lid (
lidnummer INT AUTO_INCREMENT PRIMARY KEY,
naam VARCHAR(100) NOT NULL,
abonnementstype VARCHAR(50) NOT NULL,
vervaldatum DATE NOT NULL,
deleted_at DATETIME DEFAULT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ===================================
-- TABEL 2: Instructeur
-- ===================================
-- Bevat alle instructeurs die lessen geven
CREATE TABLE Instructeur (
instructeurcode VARCHAR(10) PRIMARY KEY,
instructeurnaam VARCHAR(100) NOT NULL,
deleted_at DATETIME DEFAULT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ===================================
-- TABEL 3: Les
-- ===================================
-- Bevat alle groepslessen die aangeboden worden
CREATE TABLE Les (
lesID INT AUTO_INCREMENT PRIMARY KEY,
lesnaam VARCHAR(50) NOT NULL,
dag VARCHAR(10) NOT NULL,
tijd TIME NOT NULL,
instructeurcode VARCHAR(10) NOT NULL,
deleted_at DATETIME DEFAULT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (instructeurcode) REFERENCES Instructeur(instructeurcode) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ===================================
-- TABEL 4: Apparaat
-- ===================================
-- Bevat alle fitnessapparaten in de sportschool
CREATE TABLE Apparaat (
apparaatID INT AUTO_INCREMENT PRIMARY KEY,
apparaatnaam VARCHAR(100) NOT NULL,
apparaattype VARCHAR(50) NOT NULL,
aanschafdatum DATE NOT NULL,
deleted_at DATETIME DEFAULT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ===================================
-- TABEL 5: Trainingsschema
-- ===================================
-- Bevat persoonlijke trainingsschema's voor leden
CREATE TABLE Trainingsschema (
schemaID INT AUTO_INCREMENT PRIMARY KEY,
schemanaam VARCHAR(100) NOT NULL,
startdatum DATE NOT NULL,
trainingsdoel TEXT,
lidnummer INT NOT NULL,
deleted_at DATETIME DEFAULT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (lidnummer) REFERENCES Lid(lidnummer) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ===================================
-- TABEL 6: Oefening
-- ===================================
-- Bevat alle beschikbare oefeningen (herbruikbaar in meerdere schema's)
CREATE TABLE Oefening (
oefeningID INT AUTO_INCREMENT PRIMARY KEY,
oefeningnaam VARCHAR(100) NOT NULL,
beschrijving TEXT,
deleted_at DATETIME DEFAULT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ===================================
-- TABEL 7: Onderhoud
-- ===================================
-- Registreert onderhoudsmomenten van apparaten
CREATE TABLE Onderhoud (
onderhoudID INT AUTO_INCREMENT PRIMARY KEY,
onderhoudsdatum DATE NOT NULL,
beschrijving TEXT,
apparaatID INT NOT NULL,
deleted_at DATETIME DEFAULT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (apparaatID) REFERENCES Apparaat(apparaatID) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ===================================
-- TABEL 8: Inschrijving (Koppeltabel)
-- ===================================
-- Koppelt leden aan lessen (veel-op-veel relatie)
-- Registreert ook aanwezigheid
CREATE TABLE Inschrijving (
lesID INT NOT NULL,
lidnummer INT NOT NULL,
aanwezig TINYINT(1) DEFAULT 0,
inschrijfdatum DATE NOT NULL,
deleted_at DATETIME DEFAULT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (lesID, lidnummer),
FOREIGN KEY (lesID) REFERENCES Les(lesID) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (lidnummer) REFERENCES Lid(lidnummer) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ===================================
-- TABEL 9: Schema_oefening (Koppeltabel)
-- ===================================
-- Koppelt oefeningen aan trainingsschema's (veel-op-veel relatie)
-- Bevat specifieke informatie per oefening (sets, herhalingen, apparaat)
CREATE TABLE Schema_oefening (
schemaID INT NOT NULL,
oefeningID INT NOT NULL,
volgnummer INT NOT NULL,
apparaatID INT DEFAULT NULL,
sets INT NOT NULL,
herhalingen INT NOT NULL,
oefening_doel VARCHAR(255),
deleted_at DATETIME DEFAULT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (schemaID, oefeningID, volgnummer),
FOREIGN KEY (schemaID) REFERENCES Trainingsschema(schemaID) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (oefeningID) REFERENCES Oefening(oefeningID) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (apparaatID) REFERENCES Apparaat(apparaatID) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ===================================
-- INDICES VOOR BETERE PERFORMANCE
-- ===================================
-- Index op foreign keys voor snellere joins
CREATE INDEX idx_les_instructeur ON Les(instructeurcode);
CREATE INDEX idx_trainingsschema_lid ON Trainingsschema(lidnummer);
CREATE INDEX idx_onderhoud_apparaat ON Onderhoud(apparaatID);
CREATE INDEX idx_schema_oefening_apparaat ON Schema_oefening(apparaatID);
-- Index op datum velden voor snellere datum queries
CREATE INDEX idx_lid_vervaldatum ON Lid(vervaldatum);
CREATE INDEX idx_trainingsschema_startdatum ON Trainingsschema(startdatum);
CREATE INDEX idx_onderhoud_datum ON Onderhoud(onderhoudsdatum);
CREATE INDEX idx_inschrijving_datum ON Inschrijving(inschrijfdatum);
-- Index op deleted_at voor soft delete queries
CREATE INDEX idx_lid_deleted ON Lid(deleted_at);
CREATE INDEX idx_instructeur_deleted ON Instructeur(deleted_at);
CREATE INDEX idx_les_deleted ON Les(deleted_at);
CREATE INDEX idx_apparaat_deleted ON Apparaat(deleted_at);
CREATE INDEX idx_trainingsschema_deleted ON Trainingsschema(deleted_at);
CREATE INDEX idx_oefening_deleted ON Oefening(deleted_at);
CREATE INDEX idx_onderhoud_deleted ON Onderhoud(deleted_at);
CREATE INDEX idx_inschrijving_deleted ON Inschrijving(deleted_at);
CREATE INDEX idx_schema_oefening_deleted ON Schema_oefening(deleted_at);
-- ===================================
-- EINDE VAN DATABASE SETUP
-- ===================================
-- Overzicht van alle tabellen tonen
SHOW TABLES;
Alle tabellen bevatten zoals vereist:
Om het resultaat to bekijken kan je hier de data-disctionary inzien: sportschool.pdf

De database is gevuld met realistische testdata die representatief is voor een echte sportschool:
| Tabel | Aantal Records | Type Data |
|---|---|---|
| Lid | 30 | Echte Nederlandse namen, verschillende abonnementstypen |
| Instructeur | 22 | Professionele instructeursnamen met unieke codes |
| Les | 25 | Diverse lestypen verspreid over de hele week |
| Apparaat | 25 | Realistische apparatuur (cardio, kracht, functioneel) |
| Oefening | 30 | Complete bibliotheek van standaard oefeningen |
| Trainingsschema | 25 | Gevarieerde trainingsschema’s voor verschillende doelen |
| Schema_oefening | 60+ | Koppeling tussen schema’s en oefeningen met details |
| Inschrijving | 60+ | Les-inschrijvingen met aanwezigheidsregistratie |
| Onderhoud | 27 | Onderhoudshistorie van apparaten |
Totaal: 300+ records verspreid over alle tabellen.
Het volledige script vind je in het bestand sportschool_fill_data.sql. Hieronder een uitleg per tabel:
INSERT INTO Instructeur (instructeurcode, instructeurnaam) VALUES
('INST001', 'Mike van der Berg'),
('INST002', 'Lisa Janssen'),
('INST003', 'Thomas de Vries'),
-- ... meer instructeurs
('INST022', 'Melissa Koning');
Keuzes:
INSERT INTO Les (lesnaam, dag, tijd, instructeurcode) VALUES
('Spinning Advanced', 'Maandag', '18:00:00', 'INST001'),
('Yoga Beginners', 'Maandag', '19:00:00', 'INST002'),
('CrossFit', 'Maandag', '20:00:00', 'INST003'),
-- ... meer lessen
('Circuit Training', 'Woensdag', '20:00:00', 'INST019');
Keuzes:
INSERT INTO Lid (naam, abonnementstype, vervaldatum) VALUES
('Jan Petersen', 'Premium', '2026-12-31'),
('Maria van Damme', 'Basis', '2026-06-30'),
('Peter de Koning', 'Premium', '2026-08-15'),
-- ... meer leden
('Tessa Lindeman', 'Premium', '2027-03-15');
(geen “klant1, klant2” maar echte namen):
| Tabel | Aantal Records | Voorbeeld Data |
|---|---|---|
| Lid | 30 | Jan Petersen, Maria van Damme, Peter de Koning… |
| Instructeur | 22 | Mike van der Berg, Lisa Janssen, Thomas de Vries… |
| Les | 25 | Spinning Advanced, Yoga Beginners, CrossFit… |
| Apparaat | 25 | Loopband 1, Crosstrainer 1, Squat Rack 1… |
| Oefening | 30 | Bankdrukken, Squats, Deadlift, Pull-ups… |
| Trainingsschema | 25 | Kracht Opbouw Basis, Cardio Intensief… |
| Schema_oefening | 60+ | Koppelingen met sets, herhalingen, doelen |
| Inschrijving | 60+ | Les-inschrijvingen met aanwezigheid (1/0) |
| Onderhoud | 27 | Onderhoudsacties met beschrijvingen |
Totaal: 300+ records
✓ Realistische Nederlandse namen - Geen generieke namen
✓ Variatie in data - Verschillende abonnementstypen, lestijden, etc.
✓ Logische koppelingen - Leden ingeschreven voor passende lessen
✓ Complete relaties - Alle foreign keys correct ingevuld
✓ Historische data - Onderhoud en inschrijvingen met verschillende datums
✓ Mix van situaties - Aanwezige en afwezige leden, verschillende schema’s
JOIN Instructeur i ON l.instructeurcode = i.instructeurcode;
– Toon aantal inschrijvingen per les
SELECT l.lesnaam, COUNT(\*) as aantal_inschrijvingen
FROM Les l
LEFT JOIN Inschrijving ins ON l.lesID = ins.lesID
GROUP BY l.lesID, l.lesnaam
ORDER BY aantal_inschrijvingen DESC;
Volgorde van Uitvoeren: Voer eerst sportschool_database.sql uit en daarna pas sportschool_fill_data.sql. De data kan niet ingevoerd worden als de tabellen nog niet bestaan.
Timestamps: De created_at en updated_at velden worden automatisch ingevuld door MySQL, deze hoef je niet handmatig in te vullen.
deleted_at velden zijn standaard NULL, wat betekent dat alle records actief zijn.Het fill script bevat aan het einde een verificatie query die controleert of alle tabellen ≥20 records bevatten:
SELECT 'Lid' as Tabel, COUNT(*) as Aantal FROM Lid
UNION ALL SELECT 'Instructeur', COUNT(*) FROM Instructeur
-- ... (zie sportschool_fill_data.sql voor volledige query)
In dit onderdeel worden verschillende SQL queries uitgevoerd op de sportschool database. Voor elke query wordt een beschrijving gegeven, het SQL-commando en een verwijzing naar het resultaat screenshot.
Opdracht: Geef van twee tabellen het SQL-commando waarmee een volledig extra record in de tabel ingevoerd wordt. Laat in het resultaat zien dat de kolom created_at de juiste waarde heeft.
De vraag/opdracht aan de database:
Voeg een nieuw lid toe aan de database en toon dat created_at automatisch is ingevuld.
SQL-commando:
-- Record invoegen
INSERT INTO Lid (naam, abonnementstype, vervaldatum)
VALUES ('Sophie Verstappen', 'Premium', '2027-06-30');
-- Resultaat tonen met created_at
SELECT lidnummer, naam, abonnementstype, vervaldatum, created_at, updated_at
FROM Lid
WHERE naam = 'Sophie Verstappen';
Resultaat: 
De vraag/opdracht aan de database:
Voeg een nieuwe oefening toe aan de database en toon dat created_at automatisch is ingevuld.
SQL-commando:
-- Record invoegen
INSERT INTO Oefening (oefeningnaam, beschrijving)
VALUES ('Plank to Push-up', 'Begin in plank positie en wissel af tussen plank en push-up positie');
-- Resultaat tonen met created_at
SELECT oefeningID, oefeningnaam, beschrijving, created_at, updated_at
FROM Oefening
WHERE oefeningnaam = 'Plank to Push-up';
Resultaat: 
Opdracht: Geef van twee tabellen het SQL-commando waarmee minimaal twee velden (per commando) in de database aangepast worden. Laat in het resultaat zien dat de kolom created_at en de kolom updated_at de juiste waarde heeft.
De vraag/opdracht aan de database:
Pas het abonnementstype en de vervaldatum van een lid aan en toon dat updated_at automatisch is bijgewerkt.
SQL-commando:
-- Eerst oude waarden tonen
SELECT lidnummer, naam, abonnementstype, vervaldatum, created_at, updated_at
FROM Lid
WHERE lidnummer = 1;
-- Update uitvoeren
UPDATE Lid
SET abonnementstype = 'Student',
vervaldatum = '2026-08-31'
WHERE lidnummer = 1;
-- Nieuwe waarden tonen (created_at ongewijzigd, updated_at bijgewerkt)
SELECT lidnummer, naam, abonnementstype, vervaldatum, created_at, updated_at
FROM Lid
WHERE lidnummer = 1;
Resultaat: 
De vraag/opdracht aan de database:
Pas de naam en het type van een apparaat aan en toon dat updated_at automatisch is bijgewerkt.
SQL-commando:
-- Eerst oude waarden tonen
SELECT apparaatID, apparaatnaam, apparaattype, aanschafdatum, created_at, updated_at
FROM Apparaat
WHERE apparaatID = 1;
-- Update uitvoeren
UPDATE Apparaat
SET apparaatnaam = 'Loopband Premium 1',
apparaattype = 'Cardio Premium'
WHERE apparaatID = 1;
-- Nieuwe waarden tonen (created_at ongewijzigd, updated_at bijgewerkt)
SELECT apparaatID, apparaatnaam, apparaattype, aanschafdatum, created_at, updated_at
FROM Apparaat
WHERE apparaatID = 1;
Resultaat: 
Opdracht: Geef van een tabel het SQL-commando waarmee een bepaald record gemarkeerd wordt als deleted (maar niet definitief verwijderd is). Laat zien dat de kolom deleted_at de juiste waarde heeft.
De vraag/opdracht aan de database:
Markeer een les als verwijderd (soft delete) en toon dat deleted_at is ingevuld met de huidige datum/tijd.
SQL-commando:
-- Eerst oude waarden tonen (deleted_at is NULL)
SELECT lesID, lesnaam, dag, tijd, deleted_at
FROM Les
WHERE lesID = 5;
-- Soft delete uitvoeren
UPDATE Les
SET deleted_at = NOW()
WHERE lesID = 5;
-- Nieuwe waarden tonen (deleted_at is nu ingevuld)
SELECT lesID, lesnaam, dag, tijd, deleted_at
FROM Les
WHERE lesID = 5;
Resultaat: 
Opdracht: Verzin een query die data uit dezelfde tabel met elkaar vergelijkt.
De vraag/opdracht aan de database:
Toon alle leden waarvan het abonnement eerder verloopt dan het abonnement van ‘Jan Petersen’.
SQL-commando:
SELECT l1.lidnummer, l1.naam, l1.abonnementstype, l1.vervaldatum
FROM Lid l1
WHERE l1.vervaldatum < (
SELECT l2.vervaldatum
FROM Lid l2
WHERE l2.naam = 'Jan Petersen'
)
AND l1.deleted_at IS NULL
ORDER BY l1.vervaldatum;
Resultaat: 
Opdracht: Verzin twee query’s die data uit meerdere tabellen haalt en geen subquery gebruikt wordt.
De vraag/opdracht aan de database:
Toon alle lessen met de naam van de instructeur die de les geeft, gesorteerd op dag en tijd.
SQL-commando:
SELECT
l.lesnaam,
l.dag,
l.tijd,
i.instructeurnaam
FROM Les l
INNER JOIN Instructeur i ON l.instructeurcode = i.instructeurcode
WHERE l.deleted_at IS NULL
ORDER BY
FIELD(l.dag, 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag', 'Zondag'),
l.tijd;
Resultaat: 
De vraag/opdracht aan de database:
Toon alle leden met hun trainingsschema’s, inclusief schemanaam en trainingsdoel.
SQL-commando:
SELECT
l.lidnummer,
l.naam AS lidnaam,
l.abonnementstype,
t.schemanaam,
t.startdatum,
t.trainingsdoel
FROM Lid l
INNER JOIN Trainingsschema t ON l.lidnummer = t.lidnummer
WHERE l.deleted_at IS NULL AND t.deleted_at IS NULL
ORDER BY l.naam, t.startdatum DESC;
Resultaat: 
Opdracht: Verzin twee query’s die data uit meerdere tabellen haalt, waarbij een subquery gebruikt wordt.
De vraag/opdracht aan de database:
Toon alle lessen die meer inschrijvingen hebben dan het gemiddelde aantal inschrijvingen per les.
SQL-commando:
SELECT
l.lesnaam,
l.dag,
l.tijd,
i.instructeurnaam,
COUNT(ins.lidnummer) AS aantal_inschrijvingen
FROM Les l
INNER JOIN Instructeur i ON l.instructeurcode = i.instructeurcode
LEFT JOIN Inschrijving ins ON l.lesID = ins.lesID AND ins.deleted_at IS NULL
WHERE l.deleted_at IS NULL
GROUP BY l.lesID, l.lesnaam, l.dag, l.tijd, i.instructeurnaam
HAVING COUNT(ins.lidnummer) > (
SELECT AVG(inschrijving_count)
FROM (
SELECT COUNT(*) AS inschrijving_count
FROM Inschrijving
WHERE deleted_at IS NULL
GROUP BY lesID
) AS gemiddelde
)
ORDER BY aantal_inschrijvingen DESC;
Resultaat: 
De vraag/opdracht aan de database:
Toon leden waarvan het abonnement is verlopen en die trainingsschema’s hebben die gestart zijn na de startdatum van het oudste actieve schema.
SQL-commando:
SELECT
l.lidnummer,
l.naam,
l.abonnementstype,
l.vervaldatum,
t.schemanaam,
t.startdatum
FROM Lid l
INNER JOIN Trainingsschema t ON l.lidnummer = t.lidnummer
WHERE l.vervaldatum < CURDATE()
AND t.startdatum > (
SELECT MIN(startdatum)
FROM Trainingsschema
WHERE deleted_at IS NULL
)
AND l.deleted_at IS NULL AND t.deleted_at IS NULL
ORDER BY l.vervaldatum DESC;
Resultaat: 
Opdracht: Verzin twee query’s die data uit meerdere tabellen haalt, waarbij meer dan één subquery gebruikt wordt.
De vraag/opdracht aan de database:
Toon apparaten die in meer schema-oefeningen gebruikt worden dan gemiddeld EN waarvan het laatste onderhoud recenter is dan de gemiddelde onderhoudsdatum.
SQL-commando:
SELECT
a.apparaatnaam,
a.apparaattype,
COUNT(DISTINCT so.schemaID) AS aantal_schema_gebruikt,
MAX(o.onderhoudsdatum) AS laatste_onderhoud
FROM Apparaat a
LEFT JOIN Schema_oefening so ON a.apparaatID = so.apparaatID AND so.deleted_at IS NULL
LEFT JOIN Onderhoud o ON a.apparaatID = o.apparaatID AND o.deleted_at IS NULL
WHERE a.deleted_at IS NULL
GROUP BY a.apparaatID, a.apparaatnaam, a.apparaattype
HAVING
COUNT(DISTINCT so.schemaID) > (
SELECT AVG(schema_count)
FROM (
SELECT COUNT(DISTINCT schemaID) AS schema_count
FROM Schema_oefening
WHERE apparaatID IS NOT NULL AND deleted_at IS NULL
GROUP BY apparaatID
) AS gemiddeld_gebruik
)
AND MAX(o.onderhoudsdatum) > (
SELECT AVG(onderhoudsdatum)
FROM Onderhoud
WHERE deleted_at IS NULL
)
ORDER BY aantal_schema_gebruikt DESC;
Resultaat: 
De vraag/opdracht aan de database:
Toon leden die vaker aanwezig zijn bij lessen dan gemiddeld EN waarvan het abonnement later verloopt dan de gemiddelde vervaldatum.
SQL-commando:
SELECT
l.lidnummer,
l.naam,
l.abonnementstype,
l.vervaldatum,
COUNT(ins.lesID) AS totaal_inschrijvingen,
SUM(CASE WHEN ins.aanwezig = 1 THEN 1 ELSE 0 END) AS keer_aanwezig,
ROUND(SUM(CASE WHEN ins.aanwezig = 1 THEN 1 ELSE 0 END) * 100.0 / COUNT(ins.lesID), 2) AS aanwezigheidspercentage
FROM Lid l
INNER JOIN Inschrijving ins ON l.lidnummer = ins.lidnummer AND ins.deleted_at IS NULL
WHERE l.deleted_at IS NULL
GROUP BY l.lidnummer, l.naam, l.abonnementstype, l.vervaldatum
HAVING
SUM(CASE WHEN ins.aanwezig = 1 THEN 1 ELSE 0 END) > (
SELECT AVG(aanwezig_count)
FROM (
SELECT SUM(CASE WHEN aanwezig = 1 THEN 1 ELSE 0 END) AS aanwezig_count
FROM Inschrijving
WHERE deleted_at IS NULL
GROUP BY lidnummer
) AS gemiddelde_aanwezigheid
)
AND l.vervaldatum > (
SELECT AVG(vervaldatum)
FROM Lid
WHERE deleted_at IS NULL
)
ORDER BY aanwezigheidspercentage DESC;
Resultaat: 
Opdracht: Verzin twee query’s die data uit minimaal drie tabellen gebruikt.
De vraag/opdracht aan de database:
Toon een overzicht van lessen met instructeur, ingeschreven leden en hun aanwezigheid.
SQL-commando:
SELECT
l.lesnaam,
l.dag,
l.tijd,
i.instructeurnaam,
lid.naam AS lidnaam,
lid.abonnementstype,
CASE
WHEN ins.aanwezig = 1 THEN 'Aanwezig'
ELSE 'Afwezig'
END AS status
FROM Les l
INNER JOIN Instructeur i ON l.instructeurcode = i.instructeurcode
INNER JOIN Inschrijving ins ON l.lesID = ins.lesID
INNER JOIN Lid lid ON ins.lidnummer = lid.lidnummer
WHERE l.deleted_at IS NULL
AND ins.deleted_at IS NULL
AND lid.deleted_at IS NULL
ORDER BY l.dag, l.tijd, lid.naam;
Resultaat: 
De vraag/opdracht aan de database:
Toon een overzicht van trainingsschema’s met de bijbehorende oefeningen en benodigde apparaten.
SQL-commando:
SELECT
l.naam AS lidnaam,
t.schemanaam,
t.trainingsdoel,
o.oefeningnaam,
so.sets,
so.herhalingen,
COALESCE(a.apparaatnaam, 'Geen apparaat') AS apparaatnaam,
so.oefening_doel
FROM Lid l
INNER JOIN Trainingsschema t ON l.lidnummer = t.lidnummer
INNER JOIN Schema_oefening so ON t.schemaID = so.schemaID
INNER JOIN Oefening o ON so.oefeningID = o.oefeningID
LEFT JOIN Apparaat a ON so.apparaatID = a.apparaatID
WHERE l.deleted_at IS NULL
AND t.deleted_at IS NULL
AND so.deleted_at IS NULL
AND o.deleted_at IS NULL
ORDER BY l.naam, t.schemanaam, so.volgnummer;
Resultaat: 
Opdracht: Verzin twee query’s die een GROUP BY gebruiken (zonder having component).
De vraag/opdracht aan de database:
Toon per instructeur hoeveel lessen hij/zij geeft, gesorteerd op aantal lessen.
SQL-commando:
SELECT
i.instructeurnaam,
COUNT(l.lesID) AS aantal_lessen,
GROUP_CONCAT(DISTINCT l.lesnaam ORDER BY l.lesnaam SEPARATOR ', ') AS gegeven_lessen
FROM Instructeur i
LEFT JOIN Les l ON i.instructeurcode = l.instructeurcode AND l.deleted_at IS NULL
WHERE i.deleted_at IS NULL
GROUP BY i.instructeurcode, i.instructeurnaam
ORDER BY aantal_lessen DESC, i.instructeurnaam;
Resultaat: 
De vraag/opdracht aan de database:
Toon per abonnementstype hoeveel trainingsschema’s er actief zijn.
SQL-commando:
SELECT
l.abonnementstype,
COUNT(t.schemaID) AS aantal_schemas,
COUNT(DISTINCT l.lidnummer) AS aantal_leden
FROM Lid l
LEFT JOIN Trainingsschema t ON l.lidnummer = t.lidnummer AND t.deleted_at IS NULL
WHERE l.deleted_at IS NULL
GROUP BY l.abonnementstype
ORDER BY aantal_schemas DESC;
Resultaat: 
Opdracht: Verzin twee query’s die een GROUP BY gebruiken met een HAVING component.
De vraag/opdracht aan de database:
Toon alleen lessen die minimaal 3 inschrijvingen hebben, met het totaal aantal inschrijvingen en aanwezigen.
SQL-commando:
SELECT
l.lesnaam,
l.dag,
l.tijd,
i.instructeurnaam,
COUNT(ins.lidnummer) AS totaal_inschrijvingen,
SUM(CASE WHEN ins.aanwezig = 1 THEN 1 ELSE 0 END) AS aantal_aanwezig,
ROUND(SUM(CASE WHEN ins.aanwezig = 1 THEN 1 ELSE 0 END) * 100.0 / COUNT(ins.lidnummer), 2) AS aanwezigheidspercentage
FROM Les l
INNER JOIN Instructeur i ON l.instructeurcode = i.instructeurcode
LEFT JOIN Inschrijving ins ON l.lesID = ins.lesID AND ins.deleted_at IS NULL
WHERE l.deleted_at IS NULL
GROUP BY l.lesID, l.lesnaam, l.dag, l.tijd, i.instructeurnaam
HAVING COUNT(ins.lidnummer) >= 3
ORDER BY totaal_inschrijvingen DESC;
Resultaat: 
De vraag/opdracht aan de database:
Toon alleen apparaten die meer dan 2 keer onderhoud hebben gehad, met details over laatste onderhoud.
SQL-commando:
SELECT
a.apparaatnaam,
a.apparaattype,
COUNT(o.onderhoudID) AS aantal_onderhoudsbeurten,
MIN(o.onderhoudsdatum) AS eerste_onderhoud,
MAX(o.onderhoudsdatum) AS laatste_onderhoud,
DATEDIFF(MAX(o.onderhoudsdatum), MIN(o.onderhoudsdatum)) AS dagen_tussen_eerste_laatste
FROM Apparaat a
INNER JOIN Onderhoud o ON a.apparaatID = o.apparaatID
WHERE a.deleted_at IS NULL AND o.deleted_at IS NULL
GROUP BY a.apparaatID, a.apparaatnaam, a.apparaattype
HAVING COUNT(o.onderhoudID) > 2
ORDER BY aantal_onderhoudsbeurten DESC;
Resultaat: 
Opdracht: Verzin een query die uit meer dan drie tabellen informatie ophaalt.
De vraag/opdracht aan de database:
Toon een compleet overzicht per lid met hun trainingsschema’s, oefeningen, apparaten, EN hun les-inschrijvingen met instructeurs. Dit geeft een volledig beeld van alle trainingsactiviteiten.
SQL-commando:
SELECT
l.lidnummer,
l.naam AS lidnaam,
l.abonnementstype,
l.vervaldatum,
'Trainingsschema' AS activiteit_type,
t.schemanaam AS activiteit_naam,
o.oefeningnaam AS detail,
CONCAT(so.sets, ' sets x ', so.herhalingen, ' herhalingen') AS specificatie,
COALESCE(a.apparaatnaam, 'Geen apparaat') AS hulpmiddel,
NULL AS dag_tijd
FROM Lid l
INNER JOIN Trainingsschema t ON l.lidnummer = t.lidnummer
INNER JOIN Schema_oefening so ON t.schemaID = so.schemaID
INNER JOIN Oefening o ON so.oefeningID = o.oefeningID
LEFT JOIN Apparaat a ON so.apparaatID = a.apparaatID
WHERE l.deleted_at IS NULL
AND t.deleted_at IS NULL
AND so.deleted_at IS NULL
UNION ALL
SELECT
l.lidnummer,
l.naam AS lidnaam,
l.abonnementstype,
l.vervaldatum,
'Groepsles' AS activiteit_type,
les.lesnaam AS activiteit_naam,
i.instructeurnaam AS detail,
CASE WHEN ins.aanwezig = 1 THEN 'Aanwezig' ELSE 'Afwezig' END AS specificatie,
CONCAT(les.dag, ' ', les.tijd) AS hulpmiddel,
CONCAT(les.dag, ' om ', TIME_FORMAT(les.tijd, '%H:%i')) AS dag_tijd
FROM Lid l
INNER JOIN Inschrijving ins ON l.lidnummer = ins.lidnummer
INNER JOIN Les les ON ins.lesID = les.lesID
INNER JOIN Instructeur i ON les.instructeurcode = i.instructeurcode
WHERE l.deleted_at IS NULL
AND ins.deleted_at IS NULL
AND les.deleted_at IS NULL
ORDER BY lidnummer, activiteit_type, activiteit_naam;
Resultaat: 
Toelichting: Deze query combineert data uit 5-6 tabellen in één overzicht:
Dit geeft een volledig beeld van alle trainingsactiviteiten van leden, zowel persoonlijke schema’s als groepslessen.
✓ D1: 2 INSERT queries met created_at validatie
✓ D2: 2 UPDATE queries met updated_at validatie
✓ D3: 1 Soft DELETE query met deleted_at validatie
✓ D4: 1 Self-join vergelijkingsquery
✓ D5: 2 JOIN queries zonder subquery
✓ D6: 2 JOIN queries met één subquery
✓ D7: 2 JOIN queries met meerdere subqueries
✓ D8: 2 JOIN queries met minimaal 3 tabellen
✓ D9: 2 GROUP BY queries zonder HAVING
✓ D10: 2 GROUP BY queries met HAVING
✓ D11: 1 Complexe query met 5+ tabellen