Introduce a simpler training-based workflow for the OnlineAcademy Playwright runner. Changes included: - support --training-dir and automatic loading of content.md and training.json - support --env-file so OA credentials no longer need manual shell sourcing - resolve asset paths relative to the training directory - improve human-readable review and execute output with step-by-step progress - keep the browser open after execute when no save flag is used - add optional --save flow that clicks 'Opslaan als' and then chooses 'Concept' - add a concrete user guide for preparing and running training imports - update handover documentation to reflect the current repo structure and workflow - align the repo around trainings/<name>/content.md, training.json and assets/ - remove reliance on older pilot/test material in the documented main flow
16 KiB
training
| training |
|---|
| SQL met FitWorks |
Module: Hoofdstuk 4 - Basisqueries
Pagina: Terugblik en opening
Tekst
Titel: Terugblik
In hoofdstuk 3 heb je kennisgemaakt met SQL als declaratieve taal: je schrijft wat je wilt, niet hoe de database dat uitrekent. Je hebt de drie groepen van SQL leren kennen - DML, DDL en DCL - en de fitworks-database geinstalleerd. Die database staat nu klaar.
Neem even 30 seconden. In hoofdstuk 3 stond al een korte SELECT-query als voorbeeld. Kon je op dat moment al raden wat hij deed? En nu, na het lezen van de beschrijving van DDL en DML, zou je dan anders antwoorden? Bewaar dat gevoel; aan het einde van dit hoofdstuk kijk je terug op je eigen redenering.
Tekst
Titel: Opening
Je bent systeembeheerder bij FitWorks. De vestigingsmanager loopt op je af: "Kun jij me snel een lijst geven van alle leden die geen telefoonnummer hebben ingevuld? We willen ze via e-mail aanschrijven."
Een simpele vraag. Maar hoe stel je die aan een database?
Je kunt niet op een knop klikken. Er bestaat geen menu-optie "geef me leden zonder telefoon". Je hebt een instructie nodig die de database begrijpt: precies, ondubbelzinnig en in de juiste volgorde. Dat is een query. En er zit een addertje onder het gras in die vraag over telefoonnummers. Dat addertje heet NULL. Je ontdekt het in sectie 4.3.
Pagina: Leerdoelen
Tekst
Titel: Leerdoelen
Na dit hoofdstuk kun je:
- een query schrijven met
SELECT,FROMenWHEREen uitleggen wat elk onderdeel doet; - resultaten filteren met vergelijkingsoperatoren,
BETWEEN,INen logische operatorsAND,ORenNOT; - uitleggen wat
NULLis, waarom je er niet op kunt filteren met=, en hoe je dat correct doet metIS NULL; - resultaten sorteren met
ORDER BYen dubbele waarden verwijderen metDISTINCT; - kolomaliassen toewijzen met
ASen vaste tekst, literals, in een query opnemen; - voor- en achternamen samenvoegen met
CONCATen datatypes omzetten metCAST.
Pagina: 4.1 SELECT en FROM
Tekst
Titel: De balie vraagt, de database antwoordt
Het is dinsdagochtend bij FitWorks en een trainer vraagt aan de receptie: "Wie staat er allemaal ingeschreven?" De receptioniste opent phpMyAdmin, typt een instructie en krijgt binnen een seconde een lijst op het scherm. Die instructie is klein maar compleet:
SELECT voornaam, achternaam FROM leden;
Dit is een volledige SQL-query. Ze bestaat uit twee onderdelen. SELECT zegt welke kolommen je wilt zien, gescheiden door komma's. FROM zegt uit welke tabel die kolommen komen. Zonder FROM weet de database niet waar hij moet zoeken. Zonder SELECT weet hij niet wat hij moet tonen. Ze zijn onlosmakelijk verbonden.
De volgorde is vastgelegd: SELECT altijd eerst, dan FROM, dan pas eventuele aanvullingen zoals WHERE. SQL klaagt direct als je die volgorde omdraait. Het is geen aanbeveling, maar een syntaxregel.
Afbeelding
Bestand: assets/hoofdstuk_04_scherm_01_select_from_anatomie.png
Titel: Anatomie van een SELECT-query
Onderschrift: Overzicht van de onderdelen SELECT, FROM en later WHERE.
Alt: Diagram van een SELECT-query met de onderdelen SELECT, FROM en WHERE.
Tekst
Titel: Alle kolommen tegelijk
Soms wil je snel alle kolommen zien die een tabel bevat, zonder ze een voor een op te noemen. Dat doe je met de asterisk *:
SELECT * FROM leden;
De * is een verkorting voor "geef me alles". Handig bij het verkennen van een tabel. In de praktijk gebruik je * spaarzaam: het haalt onnodig veel data op, maakt je query minder leesbaar en kan vertragen als een tabel tientallen kolommen heeft. Maar als leergereedschap bij het doorzoeken van fitworks is het prima.
Kolomnamen in fitworks zijn altijd lowercase. Schrijf dus voornaam, niet Voornaam of VOORNAAM. MySQL maakt op de meeste systemen geen onderscheid tussen hoofd- en kleine letters in kolomnamen, maar consequent lowercase schrijven voorkomt verwarring.
Open vraag
Vraag: Je wilt de e-mailadressen en geboortedatums van alle leden zien. Welke query schrijf je?
Toelichting: Modelantwoord: SELECT email, geboortedatum FROM leden;
Pagina: 4.2 WHERE
Tekst
Titel: Filteren op voorwaarden
De tabel leden bevat zo'n 400 rijen. Als je de manager een overzicht stuurt van actieve leden, heb je die 400 rijen niet nodig. Je wilt alleen de rijen waarbij actief gelijk is aan 1. Daarvoor gebruik je WHERE.
WHERE is een filter. De database loopt alle rijen van de opgegeven tabel langs en geeft alleen de rijen terug die voldoen aan de voorwaarde die jij opgeeft. Rijen die er niet aan voldoen, worden stilletjes weggelaten.
SELECT voornaam, achternaam, email FROM leden WHERE actief = 1;
WHERE staat altijd na FROM. De volgorde SELECT, FROM, WHERE is onwrikbaar. Typ je WHERE voor FROM, dan krijg je een foutmelding.
Tekst
Titel: Vergelijkingsoperatoren
In een WHERE-clausule vergelijk je een kolom met een waarde. Voor die vergelijking gebruik je een operator.
BETWEEN filtert op een bereik, inclusief de grenswaarden zelf:
SELECT naam, prijs_per_maand FROM abonnementen WHERE prijs_per_maand BETWEEN 20 AND 40;
IN laat je meerdere toegestane waarden opgeven als een lijst:
SELECT betaling_id, bedrag, status FROM betalingen WHERE status IN ('betaald', 'openstaand');
Dit is identiek aan WHERE status = 'betaald' OR status = 'openstaand', maar compacter en beter leesbaar.
Afbeelding
Bestand: assets/hoofdstuk_04_scherm_02_where_operatoren.png Titel: WHERE-operatoren Onderschrift: Overzicht van veelgebruikte vergelijkingsoperatoren in fitworks. Alt: Overzicht van WHERE-operatoren met voorbeelden uit de fitworks-database.
Tekst
Titel: Logische operators
Voorwaarden kun je combineren.
Met AND moeten beide voorwaarden gelden:
SELECT voornaam, achternaam FROM leden WHERE actief = 1 AND locatie_id = 2;
Met OR hoeft minimaal een voorwaarde te kloppen:
SELECT betaling_id, bedrag, status FROM betalingen WHERE status = 'mislukt' OR status = 'openstaand';
NOT keert een voorwaarde om:
SELECT voornaam, achternaam FROM leden WHERE NOT actief = 0;
Tekst
Titel: Tekst en getallen
Getallen schrijf je zonder aanhalingstekens: actief = 1.
Tekst schrijf je tussen enkele aanhalingstekens: status = 'betaald'.
Gebruik in MySQL geen dubbele aanhalingstekens voor tekstwaarden. In de fitworks-database zijn kolomnamen nooit geciteerd; ze staan altijd zonder aanhalingstekens in de query.
Open vraag
Vraag: Welke query schrijf je om trainers op locatie 1 te tonen die ook een specialisatie hebben opgegeven?
Toelichting: Mogelijk modelantwoord: SELECT voornaam, achternaam, specialisatie FROM trainers WHERE locatie_id = 1 AND specialisatie IS NOT NULL; Met AND moeten beide voorwaarden tegelijk waar zijn. Een trainer zonder specialisatie of van een andere locatie valt dan af.
Pagina: 4.3 NULL
Tekst
Titel: Een waarde die geen waarde is
Terug naar de vraag van de vestigingsmanager: leden zonder telefoonnummer. Wat staat er in de database als iemand geen nummer heeft opgegeven? Geen nul. Geen lege string. Er staat iets anders: NULL.
NULL is de afwezigheid van een waarde. Het is geen getal, geen tekst en geen spatie. Het betekent: er is geen informatie. In fitworks kom je NULL op meerdere plekken tegen, bijvoorbeeld bij tussenvoegsel, telefoon en specialisatie.
Tekst
Titel: Waarom werkt = NULL niet
Dit is een veelgemaakte fout:
SELECT voornaam, achternaam FROM leden WHERE telefoon = NULL;
De query wordt uitgevoerd zonder foutmelding, maar geeft nul resultaten terug. In SQL is NULL geen gewone waarde die je kunt vergelijken. De uitdrukking NULL = NULL is niet waar, maar onbekend. En een onbekende vergelijking telt niet als waar.
Tekst
Titel: De juiste aanpak
Gebruik speciale NULL-controles:
SELECT voornaam, achternaam FROM leden WHERE telefoon IS NULL;
SELECT voornaam, achternaam FROM leden WHERE telefoon IS NOT NULL;
IS NULL is de enige correcte manier om te controleren of een kolom geen waarde bevat. IS NOT NULL doet het omgekeerde.
Afbeelding
Bestand: assets/hoofdstuk_04_scherm_03_null_afhandeling.png
Titel: NULL-afhandeling
Onderschrift: Vergelijking tussen = NULL en IS NULL.
Alt: Diagram dat het verschil laat zien tussen = NULL en IS NULL.
Tekst
Titel: Wat gaat er mis als je dit negeert
Het probleem met = NULL is verraderlijk: de query geeft geen foutmelding en lijkt normaal te werken, maar geeft stilletjes een leeg resultaat. Daardoor kan een rapport foutieve conclusies oproepen, bijvoorbeeld dat alle leden een telefoonnummer hebben ingevuld.
Onthoud: NULL vergelijk je nooit met =. Altijd met IS NULL of IS NOT NULL.
Open vraag
Vraag: Schrijf de query die alle lestypes toont waarvan de beschrijving niet is ingevuld.
Toelichting: Modelantwoord: SELECT naam, duur_minuten, niveau FROM lestypes WHERE beschrijving IS NULL;
Pagina: 4.4 ORDER BY en DISTINCT
Tekst
Titel: ORDER BY
De database geeft rijen terug in de volgorde die hem uitkomt. Als je een ledenlijst wilt sorteren op inschrijfdatum, heb je ORDER BY nodig.
SELECT voornaam, achternaam, lid_sinds FROM leden ORDER BY lid_sinds;
Standaard sorteert ORDER BY oplopend. Dat heet ASC. Wil je de nieuwste inschrijvingen bovenaan, gebruik dan DESC:
SELECT voornaam, achternaam, lid_sinds FROM leden ORDER BY lid_sinds DESC;
Je kunt ook op meerdere kolommen sorteren:
SELECT achternaam, voornaam, lid_sinds FROM leden ORDER BY achternaam, voornaam;
ORDER BY staat altijd als laatste in de query, dus na WHERE als die ook aanwezig is.
Afbeelding
Bestand: assets/hoofdstuk_04_scherm_04_order_by_distinct.png Titel: ORDER BY en DISTINCT Onderschrift: Sorteren van resultaten en verwijderen van duplicaten. Alt: Diagram dat laat zien hoe ORDER BY sorteert en DISTINCT duplicaten verwijdert.
Tekst
Titel: DISTINCT
Wil je alleen unieke specialisaties zien, dan gebruik je DISTINCT:
SELECT DISTINCT specialisatie FROM trainers;
DISTINCT zet je direct na SELECT. Het verwijdert rijen die in alle geselecteerde kolommen identiek zijn. SELECT DISTINCT specialisatie, voornaam geeft dus unieke combinaties van specialisatie en voornaam, niet per se unieke specialisaties.
Open vraag
Vraag: Welke query schrijf je om alle abonnementsnamen met maandprijs te tonen, gesorteerd van duurste naar goedkoopste? Wat verandert er als je DISTINCT toevoegt?
Toelichting: Modelantwoord: SELECT naam, prijs_per_maand FROM abonnementen ORDER BY prijs_per_maand DESC; DISTINCT verandert hier niets zolang namen uniek zijn. Het effect hangt af van de geselecteerde kolommen; DISTINCT werkt op de combinatie van alle geselecteerde velden.
Pagina: 4.5 Kolomaliassen en literals
Tekst
Titel: Kolomaliassen
Met een alias geef je een kolom in het resultaat een andere naam, zonder de tabel te veranderen.
SELECT voornaam AS Voornaam, achternaam AS Achternaam, lid_sinds AS 'Lid sinds' FROM leden;
Het resultaat toont leesbaardere kolomkoppen. De alias bestaat alleen in dit queryresultaat. Als een alias een spatie bevat, zet je hem tussen enkele aanhalingstekens.
Afbeelding
Bestand: assets/hoofdstuk_04_scherm_05_aliassen_literals.png Titel: Aliassen en literals Onderschrift: Kolomaliassen maken queryresultaten leesbaarder. Alt: Voorbeeld van aliassen en literals in een SQL-resultaat.
Tekst
Titel: Literals
Een literal is een vaste waarde die je direct in een query schrijft. Dat kan tekst of een getal zijn dat voor elke rij hetzelfde blijft.
SELECT voornaam, achternaam, 'FitWorks lid' AS type FROM leden WHERE actief = 1;
Zo krijgt elke rij een extra kolom type met dezelfde vaste waarde.
Open vraag
Vraag: Schrijf een query die de naam en maandprijs van alle abonnementen toont, met als kolomkoppen Abonnementsnaam en Maandprijs. Sorteer op prijs, laagste eerst.
Toelichting: Modelantwoord: SELECT naam AS Abonnementsnaam, prijs_per_maand AS Maandprijs FROM abonnementen ORDER BY prijs_per_maand;
Pagina: 4.6 CONCAT en CAST
Tekst
Titel: CONCAT
Bij FitWorks zijn voornaam en achternaam in aparte kolommen opgeslagen. Wil je een volledige naam als een kolom tonen, dan gebruik je CONCAT:
SELECT CONCAT(voornaam, ' ', achternaam) AS volledige_naam FROM leden;
Sommige leden hebben ook een tussenvoegsel. Dan helpt COALESCE om NULL op te vangen:
SELECT CONCAT( voornaam, ' ', COALESCE(CONCAT(tussenvoegsel, ' '), ''), achternaam ) AS volledige_naam FROM leden;
COALESCE vervangt NULL door een standaardwaarde, hier een lege string.
Tekst
Titel: CAST
Elke waarde in een database heeft een datatype. Soms wil je een waarde als een ander type gebruiken. Daarvoor is CAST.
SELECT naam, CONCAT('Prijs: ', CAST(prijs_per_maand AS CHAR)) AS prijsweergave FROM abonnementen;
CAST(prijs_per_maand AS CHAR) zet een getal om naar tekst, zodat je het veilig kunt combineren met andere tekst.
Afbeelding
Bestand: assets/hoofdstuk_04_scherm_06_concatenatie_cast.png Titel: CONCAT en CAST Onderschrift: Kolommen samenvoegen en datatypes omzetten. Alt: Diagram dat laat zien hoe CONCAT waarden samenvoegt en CAST datatypes omzet.
Tabel
Titel: Veelgebruikte CAST-conversies Top header: Aan Left header: Uit
| Van | Naar | Gebruik |
|---|---|---|
| Getal | Tekst | CAST(prijs_per_maand AS CHAR) |
| Tekst | Geheel getal | CAST('42' AS UNSIGNED) |
| Datum | Tekst | CAST(geboortedatum AS CHAR) |
Open vraag
Vraag: Schrijf een query die de volledige naam van elke trainer toont in een kolom genaamd Trainer.
Toelichting: Modelantwoord: SELECT CONCAT(voornaam, ' ', achternaam) AS Trainer FROM trainers;
Pagina: Quiz
Meerkeuze
Vraag: Je wilt alle betalingen zien met status mislukt of openstaand, gesorteerd van hoog naar laag bedrag. Welke query is correct?
SELECT betaling_id, bedrag, status FROM betalingen WHERE status = 'mislukt' OR 'openstaand' ORDER BY bedrag DESC;SELECT betaling_id, bedrag, status FROM betalingen WHERE status IN ('mislukt', 'openstaand') ORDER BY bedrag DESC;SELECT betaling_id, bedrag, status FROM betalingen WHERE status IN ('mislukt', 'openstaand') ORDER BY bedrag ASC;SELECT betaling_id, bedrag, status FROM betalingen WHERE status = 'mislukt' OR status = 'openstaand' ORDER BY bedrag ASC;Toelichting: Optie B is correct.INmaakt de statusfilter compact enORDER BY bedrag DESCsorteert van hoog naar laag. Optie A is syntactisch fout, en opties C en D sorteren in de verkeerde richting.
Meerkeuze
Vraag: Welke query geeft correct alle leden terug die geen tussenvoegsel hebben?
SELECT voornaam, achternaam FROM leden WHERE tussenvoegsel = NULL;SELECT voornaam, achternaam FROM leden WHERE tussenvoegsel = '';SELECT voornaam, achternaam FROM leden WHERE tussenvoegsel IS NULL;SELECT voornaam, achternaam FROM leden WHERE tussenvoegsel IS NOT NULL;Toelichting: Optie C is correct.NULLvergelijk je niet met=, maar metIS NULL. Een lege string is bovendien iets anders danNULL.
Meerkeuze
Vraag: Een collega ziet met SELECT DISTINCT specialisatie, voornaam FROM trainers; nog steeds herhaalde waarden in specialisatie. Wat is de oorzaak?
DISTINCTwerkt alleen op de eerste geselecteerde kolom.DISTINCTverwijdert dubbele rijen op basis van alle geselecteerde kolommen tegelijk.DISTINCTmoet altijd gecombineerd worden metORDER BY.DISTINCTwerkt niet op tekstkolommen zoalsspecialisatie. Toelichting:DISTINCTkijkt naar de combinatie van alle geselecteerde kolommen. Twee trainers met dezelfde specialisatie maar een andere voornaam zijn dus nog steeds twee unieke rijen. Voor alleen unieke specialisaties moet je alleen die kolom selecteren.
Pagina: Samenvatting en vooruitblik
Tekst
Titel: Samenvatting
SELECT en FROM vormen het hart van elke query. Met WHERE filter je rijen, met ORDER BY sorteer je resultaten en met DISTINCT verwijder je duplicaten.
NULL is de afwezigheid van een waarde. Je filtert erop met IS NULL en IS NOT NULL, nooit met =.
Kolomaliassen met AS maken resultaten leesbaarder. Literals voegen vaste waarden toe. CONCAT plakt waarden aan elkaar en CAST zet waarden om naar een ander datatype.
Tekst
Titel: Vooruitblik
Je hebt nu de basis in handen om de fitworks-database te bevragen. In hoofdstuk 5 ga je verder met tekstbewerkingen, zoals LIKE voor zoeken op patronen, en met datumfuncties. Je gaat de data dan niet alleen ophalen, maar er echt mee werken.