Only this pageAll pages
Powered by GitBook
Couldn't generate the PDF for 128 pages, generation stopped at 100.
Extend with 50 more pages.
1 of 100

[G_PRO] Databanken

Loading...

Inleiding

Loading...

Loading...

Loading...

Semester 1: databanken intro

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Groeperen en samenvatten

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

PRO (GEEN LEERSTOF EN/OF IN OPBOUW)

PRO

Geen leerstof en/of in opbouw

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

SEMESTER 2: DATABANKEN

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Databanken

Informatica is het verwerken van informatie. Het beheer van gegevens is dus vaak het eigenlijke doel van een informaticasysteem en is de bestandsorganisatie minstens zo belangrijk dan de eigenlijke programmatuur. Met gegevens bedoelen we hier informatie die voor kortere of langere tijd wordt opgeslagen en die onontbeerlijk is voor de werking van de organisatie/onderneming/…

Mee helpen?

Helpen?

Fijn dat je interesse toont om aan deze cursus te helpen. Dit kan op meerdere manieren:

  • Je mag ons altijd gewoon mailen met tips, opmerkingen of verbeteringen.

  • Je kan rechtstreeks aanpassingen doen via een fork in github en deze via een merge requests in de cursus integreren.

Welkom

Indien bepaalde hoofdstukken of onderdelen niet duidelijk zijn of je hebt suggesties ter verbetering/aanvulling, aarzel dan niet om ons te contacteren.

Veel leer- en leesplezier.

Peter Spaas, Vincent Van Camp en Vincent Nys

Installatie van alle software

Installatiehandleiding

Login

Basisscherm MySQL Workbench:

Stap 1: Klik op het plusje.

Stap 2: Vul de juiste gegevens in.

Voor jouw gebruikersnaam en paswoord verwijzen we naar de e-mail die je hebt ontvangen van lector V. Nys.

Je klikt na het invullen van de juiste gegevens op de knop "Store in Vault" en vult het paswoord dat je via vermelde mail hebt ontvangen in.

Stap 3: Connectie testen.

Je moet volgende boodschap krijgen.

Benodigdheden

In alle lessen (hoorcolleges en practica) hebben we volgende zaken nodig:

  1. Deze cursus

Nuttige extra's

Boeken

Er zijn oneindig veel boeken over SQL, maar een goed boek om een overzicht te krijgen van de basisinstructies is:

Deze cursus wordt gebruikt als handboek binnen de opleiding .

Download:

Een laptop met daarop Windows 10 (Pro/Education/Enterprise) en MySQL Workbench. Je vindt gedetailleerde filmpjes en schriftelijke instructies op .

, Peter Spaas, LannooCampus

graduaat programmeren van de AP Hogeschool
https://dev.mysql.com/downloads/workbench/
de pagina "Installatie van alle software"
Standard SQL - Basisbook

Voorbeeld

Voorbeeld 1

Een klassiek voorbeeld daarvan is een lijst met de namen en adressen. Die lijst kan er als volgt uitzien:

Naam

Voornaam

Adres

Postcode

Gemeente

Telefoon

Verdieping

PIETERS

PIeter

Tralalastraat 25

2660

Hoboken

03 333 33 33

1

JANSSENS

Jan

Jasstraat 2

2000

Antwerpen

03 222 22 22

1

DEBONDT

Ron

Jopstraat 5

2100

Deurne

03 111 11 11

3

JORIS

Joost

Boedreef 25

2600

Berchem

03 444 44 44

2

VOET

Bart

Plopstraat 9

2630

Aartselaar

03 888 88 88

3

In bovenstaand voorbeeld is de eerste rij de aanduiding van de kolommen (veldnamen).

Bovenstaand voorbeeld bevat dus vijf rijen (records) die telkens bepaalde waarden bevatten, hier gaat het over de "Naam", "Voornaam", "Adres", "Postcode", "Gemeente", "Telefoon" en "Verdieping".

Voorbeeld 2

Een bedrijf in de productiesector zal onder andere volgende gegevens willen bijhouden, nl.:

  • Personeelsgegevens

  • De zgn. productiegegevens. Hierbij kunnen we denken aan bv. de productsamenstelling

  • De gegevens betreffende de noodzakelijke bestellingen

  • De gegevens van de bestaande klanten

  • De gegevens van de leveranciers

  • De gegevens m.b.t. de goederen die nog in stock zijn en/of de goederen die besteld moeten worden

In dit geval is het best om voor elk bullet point een aparte tabel te voorzien. Groepjes gegevens die sterk aan elkaar gelinkt zijn (bijvoorbeeld "voornaam werknemer" en "familienaam werknemer" komen in dezelfde tabel). Groepjes gegevens die losser gelinkt zijn komen in verschillende tabellen. Er zijn nog andere redenen om verschillende tabellen te gebruiken. Die komen later.

Voorbeeld 3

Een bank houdt per rekening van een bepaalde klant geen box bij met daarin het geld van een klant. Het bedrag op de rekening van die klant is een cijfer dat wordt bijgehouden in een database. Er zou bijvoorbeeld een tabel Rekeningen kunnen zijn, die je je zo kan voorstellen:

Klantnummer
Bedrag
Type

123456789

15.000

spaarrekening

456789123

2.124.000

belegging

789123456

11.000

spaarrekening

Daarnaast heeft de bank nog allerlei informatie, bijvoorbeeld over hypotheken,... Die zullen vermoedelijk in andere tabellen staan. De volledige verzameling tabellen vormt de database van de bank.

installatie MySQL Workbench

Wat is een relationele databank

De "relationele" database is waarschijnlijk de meestgebruikte soort database. Dit type database is uitgewerkt in de jaren 1970 en steunt op de wiskundige verzamelingenleer.

Zonder deze wiskundige fundering in veel detail te behandelen: het basisidee is dat "interessante gehelen" van informatie in verzamelingen van één soort data worden opgedeeld. Tussen deze verzamelingen bestaan verbanden.

Onderstaande figuur illustreert dit. Op deze figuur zijn de interessante gehelen personen en boeken. Deze twee verzamelingen bestaan los van elkaar, maar er is een verband: sommige personen hebben sommige boeken gelezen.

Er kunnen veel verbanden zijn. We kunnen bijvoorbeeld een tweede verband hebben dat uitdrukt dat een bepaalde persoon een bepaald boek heeft geschreven:

Om dit in een elektronisch systeem voor te stellen, maken we geen tekeningen, maar gebruiken we tabellen. We doen dit zowel voor de "interessante gehelen" (dus de elementen van de verzamelingen) als voor de verbanden tussen elementen van deze verzamelingen.

Voor de eerste tekening zou een tabelvoorstelling van de verzamelingen er zo kunnen uitzien:

Voornaam

Familienaam

Geboortejaar

Vincent

Nys

1987

Esther

Nys

1990

Michiel

Nys

1984

William

Shakespeare

1564

Ted

Chiang

1967

Kazuo

Ishiguro

1954

Titel

Jaar van uitgave

The Tempest

1623

Exhalation: Stories

2019

Never Let Me Go

2005

Ook het verband zou kunnen worden uitgedrukt met een tabel. Hoe dat precies werkt is voor iets verder, maar een vereenvoudiging die een goed beeld geeft is als volgt:

Lezer

Titel

Michiel

The Tempest

Vincent

Exhalation: Stories

Esther

Never Let Me Go

Zowat alle DBMS'en voor relationele databases spreken (ongeveer) dezelfde taal: de "Structured Query Language" of SQL. Deze taal is ook gebaseerd op de relationele algebra's en ze maakt dat je vrij snel kan leren werken met één relationele database als je al met een andere relationele database overweg kan. Elk DBMS spreekt wel een eigen "dialect" van SQL, dus je kan niet verwachten dat code voor bijvoorbeeld PostgreSQL (een type relationele database) letterlijk kan worden uitgevoerd in SQLite (een ander type relationele database). Maar normaal moet je er niet veel aan aanpassen.

Typisch voor relationele databases is dat je op voorhand moet vastleggen wat de structuur van je database is: welke tabellen er zijn en wat voor gegevens hier in mogen worden geplaatst. Deze structuur noemen we het "schema". Als je nieuwe gegevens wil bijhouden die niet passen binnen de structuur, moet je eerst de structuur aanpassen. Dit is niet bij alle soorten databanken zo. Andere types databanken hebben soms een heel losse structuur. Dit geeft hen soms meer flexibiliteit, maar zorgt er soms ook voor dat ze minder kunnen waken over de integriteit van hun gegevens.

Basisstructuren van een relationele databank

De basiswoordenschat van relationele databanken is als volgt:

  • entiteittypes: de verzamelingen van interessante gehelen waarover we informatie bijhouden. Op de figuur onderaan is dit bijvoorbeeld de hele verzameling met personen of de hele verzameling met boeken.

  • entiteiten: de elementen van de verzameling. Elke persoon in de linkse verzameling, bijvoorbeeld William Shakespeare, is een entiteit. Elke persoon en elk boek is een entiteit.

  • relatietypes: een soort verband tussen verzamelingen. Op de figuur is "heeft gelezen" een relatietype.

  • relaties: een concreet verband tussen entiteiten. Op de figuur is bv. "Michiel heeft The Tempest gelezen" een relatie.

  • tabellen: de structuur waarin een volledig entiteittype of een relatietype wordt opgeslagen. Deze tabel heeft steeds een naam, of tabelnaam.

  • records: een tabel bestaat uit rijen of records. Eén record bevat alle gegevens van de rij en stelt dus één entiteit voor.

  • kolommen (of "velden"): iedere kolom omvat één aanduiding van een gegeven binnen een tabel. Iedere kolom heeft een unieke naam.

Ter herinnering, de verzameling met personen stelden we zo voor:

Voornaam

Familienaam

Geboortejaar

Vincent

Nys

1987

Esther

Nys

1990

Michiel

Nys

1984

William

Shakespeare

1564

Ted

Chiang

1967

Kazuo

Ishiguro

1954

Diagramnotatie

Om een beter overzicht van het databaseschema te krijgen, maken we vaak gebruik van een diagram. Dit wordt een entity-relationship diagram (of "ERD") genoemd. Dit lijkt wat op de verzamelingennotatie die we eerder zagen, maar werkt op een ander niveau.

Neem volgend voorbeeld:

Dit zegt volgende zaken:

Exhalation is het lievelingsboek van Vincent
The Tempest is het lievelingsboek van Michiel
Never Let Me Go is het lievelingsboek van Esther

In het algemeen kunnen we dus stellen: personen hebben een lievelingsboek. Als we verder vastleggen dat personen altijd exact één lievelingsboek hebben, kunnen we dit grafisch uitdrukken als volgt:

Hier staat niets over Vincent, Esther of Michiel. Maar er staat dat er een relatietype is dat personen en boeken verbindt. De exacte betekenis van de getallen, de icoontjes en de stippellijn laten we voor iets later, maar onthoud alvast dat de rechthoeken de entiteiten voorstellen (met hun kolommen) en de ruiten relaties.

Let op: er zijn veel stijlen van notatie voor het entity-relationship diagram. Wij hebben dit met MySQL Workbench getekend door eerst volgende knop te gebruiken:

Daarna hebben we de plusknop gebruikt en op "Add diagram" geklikt.

Ten slotte hebben we via de menuknop "Model" gekozen voor "Object Notation" → "Workbench (Simplified)" en voor "Relationship Notation" → "Classic". Dit is ook de afspraak die we zullen hanteren voor de rest van de cursus.

Als je een ERD tegenkomt op het internet, ga dan eerst na welke notatie gebruikt is. Anders kan je niet weten wat het ERD precies voorstelt.

(My)SQL

Structuur van MySQL

MySQL is opgedeeld in een aantal deeltalen. Elke deeltaal dient voor een bepaald soort gebruik. Het is nuttig (sommige van) deze deeltalen meteen te kunnen herkennen, want ze maken het makkelijker de syntax te onthouden.

De volledige lijst deeltalen vind je terug in de (uitstekende) MySQL documentatie. Hij ziet er zo uit:

Je hoeft deze niet uit het hoofd te leren, maar het is wel nuttig om vanaf het begin een onderscheid te maken tussen data definition statements en data manipulation statements.

Data definition statements (ook de "Data Definition Language" of "DDL") dienen om vast te leggen hoe je gegevens er zullen uitzien. Met andere woorden, de structuur van de data. Ze bieden een antwoord op vragen zoals:

  • Welke tabellen zijn er?

  • Welk type gegevens mag ik in een bepaalde kolom bijhouden?

  • Is het toegestaan bepaalde cellen leeg te laten?

Data manipulation statements (of de "Data Manipulation Language" of "DML") dient om specifieke data te beheren en niet de structuur van de data. Voorbeelden hiervan zijn:

  • Maak een nieuwe rij aan in de tabel Personen.

  • Pas Said aan naar Saïd.

  • Verwijder Sanae uit de tabel Personen.

Als je jezelf steeds de vraag stelt of iets thuishoort in de DML of in de DDL, zal je veel makkelijker de juiste syntax terugvinden.

Voorbeeldinstructies MySQL

MySQL-instructies zijn vrij leesbaar in vergelijking met veel programmeertalen. Hieronder volgen enkele voorbeelden. Je hoeft deze nog niet zelf te kunnen gebruiken. Ze staan er alleen om je een idee te geven van hoe MySQL syntax er uitziet.

Volgend stukje code voegt een nieuwe persoon, Zadie Smith, toe aan de tabel met personen: INSERT INTO Personen(Voornaam,Familienaam,Geboortejaar) VALUES ('Zadie','Smith',1975); Hier betekent INSERT dat er iets nieuws wordt toegevoegd.

Volgend stukje code voegt een nieuwe tabel, Liedjes, toe aan de database. Elk liedje heeft een titel en een duurtijd. De titel is tekst met maximum 100 symbolen en de duurtijd is een geheel getal: CREATE TABLE Liedjes(Titel VARCHAR(100), Duurtijd INT);

Instructies zoals deze zal je intypen in je MySQL client (typisch MySQL Workbench).

MySQL-instructies zijn voor een deel hoofdlettergevoelig. De syntax van MySQL zelf is niet hoofdlettergevoelig. Identifiers, d.w.z. namen van tabellen of andere structuren zijn wel hoofdlettergevoelig. Ook de data die je zelf in het systeem plaatst kan hoofdlettergevoelig zijn, afhankelijk van je instellingen. Behandel alles behalve de sleutelwoorden van MySQL zelf als hoofdlettergevoelig om problemen te vermijden.

Kennisclip voor deze inhoud
Kennisclip bij deze inhoud
Kennisclip voor deze inhoud
Kennisclip voor deze inhoud

Inleiding

Een database (of gegevensbank) is een gestructureerde verzameling elektronische gegevens (of data) die door één of meerdere gebruikers (of users) gelijktijdig kunnen gemanipuleerd worden. De enorme hoeveelheden gegevens van banken worden beheerd in ultramoderne databases.

De database zelf, dat zijn dus de geordende gegevens. Maar gegevens zijn pas waardevol als er iets mee gedaan wordt. Het is de taak van een database management system (DBMS) om gegevens op te vragen, aan te passen, enzovoort.

Het DBMS moet er bovendien over waken dat de integriteit van de database behouden blijft. "Waken over integriteit" betekent bijvoorbeeld dat het juiste telefoonnummer bij de juiste persoon blijft horen, dat er geen personen zonder adres in het systeem geplaatst worden,...

Maar het DBMS doet veel meer dan alleen waken over integriteit. Zonder DBMS is het onmogelijk gegevens in een database onder andere te bekijken, te wijzigen, te verwijderen... Alleen het DBMS weet waar en hoe de gegevens in de database verwerkt moeten worden.

Een DBMS zal uiteraard nooit uit zichzelf de gegevens in een database opzoeken, wijzigen of verwijderen. Om dit te doen is steeds een specifieke opdracht van de gebruiker nodig. Bijvoorbeeld: "Verander het telefoonnummer van Jan Claessens van (02) 568 95 65 in (03) 574 23 84".

Dit soort instructie wordt echter niet in een natuurlijke taal gegeven. De meeste DBMS beschikken over een eigen taal voor instructies. Die taal heeft een specifieke "syntax", een stel regels om instructies te ontleden in hun bestanddelen. Deze regels gelijken een beetje op de regels die wij hanteren wanneer we Nederlands spreken. "Ga op die bank zitten" is een verstaanbare opdracht. We kunnen zeggen wat de taak is van elk woordje in deze Nederlandse zin. "Bank daar zitten ga op" is geen geldige zin. We kunnen als mensen nog wel raden naar de betekenis van deze zin, maar hij volgt de regels van de taal niet. Een machine zou het dan ook veel moeilijker hebben om er iets van te maken.

De regels van de talen waarmee men opdrachten kan geven aan een DBMS zijn dus veel strikter dan de regels voor het Nederlands: ze laten geen ruimte voor fouten. Een komma verkeerd volstaat om de opdracht onverstaanbaar en dus onuitvoerbaar te maken.

Kennisclip voor deze inhoud

DROP

DROP DATABASE

Om een database te verwijderen, gebruik je een statement van de vorm DROP DATABASE, gevolgd door de naam van de databank. Als je niet zeker bent dat deze database bestaat en je een foutmelding wil vermijden, gebruik dan drop database if exists.

DROP TABLE

Hier is de syntax gelijkaardig, maar je moet de database kiezen waaruit je een tabel laat vallen.

Bijvoorbeeld:

USE ApDB;
DROP TABLE IF EXISTS Boeken;

DDL

De belangrijkste commando's van de DDL zijn:

  • CREATE: Hiermee maak je nieuwe structuren aan.

  • ALTER: Hiermee verander je bestaande structuren.

  • DROP: Hiermee wis je bestaande structuren.

ALTER

Hier zien we iets meer in verband met het aanmaken van structuren voor je data. We starten onze database met behulp van onderstaand calibratiescript, dat je 0013__Calibratie.sql mag noemen:

3KB
calibratie.sql
Calibratiescript DDL medium

ALTER TABLE

ALTER TABLE verandert de structuur van een tabel, zonder bestaande data te beschadigen. Je kan het gebruiken om kolommen toe te voegen of te verwijderen. Je kan het ook gebruiken om het soort data in een kolom aan te passen, specifieker of juist breder te maken. Voor deze taken heb je binnenin een ALTER TABLE statement extra clausules nodig.

Maak voor jezelf een cheat sheet van de verschillende clausules die je in ALTER TABLE kan gebruiken!

een kolom schrappen

USE ApDB;
ALTER TABLE Boeken DROP COLUMN Voornaam;

Script bijhouden

Voer eerst het calibratiescript van dit deel uit. Pas het script eerst aan om je persoonlijke database te gebruiken in plaats van ApDB.

Sla het script om de voornaam te verwijderen op wanneer je klaar bent. Geef het de naam 0014__AlterBoeken.sql.

een kolom toevoegen

Om een kolom toe te voegen maak je gebruik van de ADD clausule bij het ALTER statement. Je zou hier misschien het woordje CREATE verwachten. Zoals je ondertussen weet, gebruik je in sql namelijk het keyword CREATE om een structuur aan te maken. Maar binnenin ALTER is het dus ADD! Je moet m.a.w. aangeven dat je iets, in dit geval een kolom, wil toevoegen aan de tabel.

-- herstel de kolom
-- deze mag tot 150 (mogelijk internationale) karakters bevatten
USE ApDB;
ALTER TABLE Boeken ADD COLUMN Commentaar VARCHAR(150) CHAR SET utf8mb4;

Je merkt in bovenstaand script dat er opgave wordt gegeven van een CHAR SET. Standaard is utf8mb4 van toepassing voor MySql, maar we vermelden ze hier uitdrukkelijk, omdat er soms ook andere karaktersets worden gebruikt.

Script bijhouden

Pas dit script aan om naast de kolom Commentaar ook de kolom Voornaam terug toe te voegen en een kolom Familienaam toe te voegen, beide VARCHAR(100) en niet verplicht. Sla je script opnieuw op wanneer je klaar bent. Geef het de naam 0015__AlterBoeken.sql.

beperkingen toevoegen

Het is goed om van meet af aan de integriteit van de database te denken. We gaan ervan uit dat de familienaam moet ingevuld worden. Zelfs al is de auteur onbekend, moet dan zoiets als "onbekend" worden ingevuld. Om een kolom verplicht te maken voegen we een constraint toe. In volgende code zijn OldColumnName, NewColumnName en NewColumnType placeholders voor de namen van twee kolommen en een datatype met de nodige constraints:

ALTER TABLE TableName CHANGE OldColumnName NewColumnName NewColumnType;

Via de NOT NULL constraint kunnen we zorgen dat een kolom een waarde moet bevatten, maar die regel mogen we enkel opleggen als er momenteel geen rijen zonder waarde in die kolom zijn.

Eerst moeten we ervoor zorgen dat de nieuw toegevoegde kolom voor iedere rij een waarde krijgt, tot nu is deze waarde NULL.

Het UPDATE-commando, hieronder, hebben we tot nu nog niet gezien en je kan dit gewoon copy/pasten om vervolgens uit te voeren.

USE ApDB;
set sql_safe_updates = 0;
UPDATE Boeken SET Familienaam = "Niet gekend";
set sql_safe_updates = 1;

Vervolgens gaan we de kolom Familienaam qua structuur wijzigen en een beperking opleggen.

USE ApDB;
ALTER TABLE Boeken CHANGE Familienaam Familienaam VARCHAR(200) CHAR SET utf8mb4 NOT NULL;

Script bijhouden

Sla een script met bovenstaande UPDATE en ALTER achter elkaar opnieuw op wanneer je klaar bent. Geef het de naam 0016__AlterBoeken.sql.

tabelnamen wijzigen

Het zou misschien te gemakkelijk geweest zijn als het wijzigen van tabelnamen in MySQL met het ALTER statement kon worden uitgevoerd. Om de naam van een tabel te wijzigen kunnen we het ALTER statement niet gebruiken. Er bestaat daarvoor een apart RENAME statement. De generieke vorm is als volgt. Let op het gebruik van backticks rond de naam van de tabellen. Die zijn verplicht in het geval dat de naam van de tabel overeenkomt met een gereserveerd woord van MySQL:

RENAME TABLE `OldTableName` TO `NewTableName`;

In ons voorbeeld:

USE ApDB;
RENAME TABLE `Boeken` TO `MijnBoeken`;

Deze wijziging hoef je niet op te slaan. Het is maar een voorbeeld.

Voorbeeld:

https://www.sqltutorial.org/sql-cheat-sheet/

Sleutels voor identificatie

Sleutels in MySQL: motivatie

De tabellen die je tot hiertoe gemaakt hebt in MySQL, groeperen heel veel informatie in één record. Informatie groeperen is een belangrijk doel van databanken, maar het gebeurt beter pas wanneer de groepering zelf nodig is. Anders krijg je databanken die meer plaats innemen dan nodig, die makkelijker fouten zullen bevatten en die moeilijker te navigeren zijn. Het belangrijkste principe om de data pas te groeperen wanneer dat nodig is, is dat van een sleutel. Hier bekijken we het binnen MySQL.

Met een sleutel kan je elke rij in een tabel aanduiden zonder te verwijzen naar de eigenlijke informatie in die rij. We wijzen eerst op enkele problemen met de aanpak die we tot hier gehanteerd hebben en leggen dan uit hoe sleutels deze problemen kunnen oplossen.

Een eerste problematische tabel

Soms hebben twee rijen dezelfde waarden in bepaalde velden, maar gaat het toch over verschillende data. Dit is een eerste probleem dat we kunnen oplossen met attributen die we aanduiden als sleutels.

Een quizvraag voor de gamers: wat hebben de games God of War, Doom en Tomb Raider met elkaar gemeen? Antwoord: het zijn allemaal remakes van oude games met dezelfde titel en dezelfde ontwikkelaar.

Als je een tabel van (oude en nieuwe) games aanmaakt, met als kolommen hun titel en ontwikkelaar, zou een stukje van de tabel er dus als volgt kunnen uitzien:

Titel

Ontwikkelaar

Doom

ID Software

Doom

ID Software

Tomb Raider

Crystal Dynamics

Tomb Raider

Crystal Dynamics

God of War

SCE Santa Monica

God of War

SCE Santa Monica

Voor SQL is er geen verschil tussen de oude en de nieuwe versies van deze games, maar het gaat in werkelijkheid wel om verschillende zaken. Je zou het jaar van uitgave als kolom kunnen toevoegen om elke rij uit elkaar te houden. Of de spelconsole waarop het spel uitkwam. Of misschien is de uitgever steeds veranderd. Dan kan je elke rij uniek maken door die toe te voegen. Maar niets verhindert een ontwikkelaar om twee gelijknamige games meteen na elkaar uit te geven bij eenzelfde uitgever.

Het idee om elke rij uniek te maken is goed, maar je volgt best een voorzichtige aanpak. Dit kan door kolommen toe te voegen die een rij uniek kunnen identificeren. Een stel kolommen waarmee je een rij uniek kan identificeren, kan dan aangeduid worden als primaire sleutel of primary key. Deze verzameling kolommen (of ene kolom) hoeft niet noodzakelijk "leesbare" informatie te bevatten. Vaak is het gewoon een getal, zoals in dit voorbeeld, waarin de kolom Id een primaire sleutel is:

Titel

Ontwikkelaar

Id

Doom

ID Software

1

Doom

ID Software

2

Tomb Raider

Crystal Dynamics

3

Tomb Raider

Crystal Dynamics

4

God of War

SCE Santa Monica

5

God of War

SCE Santa Monica

6

Je kan in SQL uitdrukken dat een bepaalde kolom wordt gebruikt als primaire sleutel, zodat je nooit per ongeluk twee rijen kan aanmaken met eenzelfde waarde in deze kolom.

Sleutels voor efficiënt gebruik van ruimte

Tabellen zoals we ze eerder soms hebben gezien, zijn ook niet bruikbaar voor (middel)grote systemen omwille van een efficiëntieprobleem. Sleutels zullen je ook toestaan op grotere schaal te werken.

Veronderstel dat je, in opdracht van Game Mania, een databank met videogames moet opstellen. Ze hebben momenteel volgende producten in hun inventaris en willen deze in hun nieuwe databank opslaan:

Titel

Platform

Anthem

PS4

Anthem

XBox One

Anthem

Windows

Sekiro: Shadows Die Twice

PS4

Sekiro: Shadows Die Twice

XBox One

Sekiro: Shadows Die Twice

Windows

Devil May Cry 5

PS4

Devil May Cry 5

XBox One

Mega Man 11

PS4

Mega Man 11

XBox One

Mega Man 11

Nintendo Switch

Mega Man 11

Windows

Mass Effect: Andromeda

PS4

Mass Effect: Andromeda

XBox One

Mass Effect: Andromeda

Windows

Dark Souls 3

PS4

Dark Souls 3

XBox One

Dark Souls 3

Windows

Hier is elke rij wel verschillend, maar toch is er een probleem. Denk eraan dat we in onze definities zo precies mogelijk uitdrukken of iets CHAR, VARCHAR,... is, hoe veel karakters er in passen,... Dat is omdat een databank zuinig moet zijn voor goede performantie. Bovenstaande tabel is dat niet: er zijn heel veel stukken lange tekst die regelmatig terugkomen en die nemen elke keer heel wat bytes in.

Zuinig zijn is bovendien niet alleen belangrijk voor performantie, maar helpt ook fouten te voorkomen. Hoe vaker we een waarde volledig moeten uitschrijven, hoe groter de kans dat we eens een fout maken.

Het zou al zuiniger zijn elke game en elk platform aan te duiden met een uniek identificatienummer. Dat bespaart heel veel ruimte tegenover wanneer we telkens de volledige tekst uit te schrijven. We kunnen bijvoorbeeld het volgende afspreken voor de titels:

  • Anthem: 1

  • Sekiro: Shadows Die Twice: 2

  • Devil May Cry 5: 3

  • Mega Man 11: 4

  • Mass Effect: Andromeda: 5

  • Dark Souls 3: 6

Voor de platformen:

  • PS4: 1

  • XBox One: 2

  • Windows: 3

  • Nintendo Switch: 4

Dan krijgen we voor de hele tabel:

Titel

Platform

1

1

1

2

1

3

2

1

2

2

2

3

3

1

3

2

4

1

4

2

4

4

4

3

5

1

5

2

5

3

6

1

6

2

6

3

Merk op dat we onze mappings van games / platformen op getallen ook in twee tabelvoorstellingen met telkens 2 kolommen (de game/het platform en het volgnummer) kunnen gieten. Dat is dan ook wat we zullen doen. We zullen de volgnummers aanduiden als primary keys die automatisch ophogen. Eens we dat gedaan hebben, kunnen we bijna op een heel efficiënte wijze data gaan combineren (met behulp van JOIN-operaties). We zullen tabellen gelijkaardig aan die hierboven gebruiken om te verwijzen naar primaire sleutels in andere tabellen. Zo'n verwijzing zal een vreemde sleutel (foreign key) heten.

CREATE

Opgelet!

Onder Unix (macOS en Linux) zijn databasenamen hoofdlettergevoelig (in tegenstelling tot SQL trefwoorden). Dit geldt overigens ook voor de tabelnamen. Onder Windows is deze beperking standaard niet van toepassing, maar het is een goede gewoonte te doen alsof het wel zo is. Volg daarom de gemaakte afspraken wat betreft naamgeving heel nauwkeurig. Als de code op jouw Windowsmachine werkt maar niet op onze server omdat de code de afspraken niet volgt, bevat ze een fout!

Aanmaken van je eerste database

Je kan geen nieuwe databases aanmaken als je verbindt met de gedeelde server. Je kan het wel doen als je MySQL installeert op je eigen systeem of in een virtuele machine.

Je zou het volgende SQL statement gebruiken om een database te maken:

CREATE DATABASE ApDB;

Het creëren van een database volstaat niet om die vervolgens te kunnen gebruiken. Je moet in een script expliciet opgeven dat je een bepaalde database wilt gebruiken met de instructie USE:

USE ApDB;

Een database moet slechts één keer gemaakt worden, maar je moet vooraleer die te gebruiken ze telkens weer selecteren. Dat doe je met de USE instructie zoals in het voorgaande voorbeeld.

Aanmaken van je eerste tabellen

Het niveau onder dat van de databank is het niveau van de tabel. Een tabel bevat typisch informatie over één entiteit, d.w.z. één soort interessante data. Dat is bijvoorbeeld bijvoorbeeld een tabel Boeken in een bibliotheeksysteem. We vertrekken van een voorstelling voor boeken en personen.

We willen volgende gegevens in het systeem bijhouden:

Gebruik eerst USE om je database te activeren. Eerst leggen we vast we welke tabellen en welke datatypes we nodig hebben:

Negeer de "1 more" onder "Geboortejaar". Negeer ook het gele sleuteltje. Die zaken komen later. Om de tabel "Personen" aan te maken, schrijven we:

CREATE TABLE Personen(Voornaam VARCHAR(50), Familienaam VARCHAR(50), Geboortejaar INT);

Enkel aanmaken wat niet bestaat

Via het CREATE-commando maak je een nieuwe structuur aan met een bepaalde naam. Als die naam al bestaat, levert dat een foutmelding. Daarom moeten we voorzichtig omspringen met het CREATE commando. We doen dit door onze CREATE enkel uit te voeren als de naam die we willen gebruiken (voor een database of een tabel of een andere structuur) nog niet gebruikt wordt. Hiervoor vervangen we bijvoorbeeld CREATE TABLE MyTable (MyColumn VARCHAR(100)); door CREATE TABLE IF NOT EXISTS MyTable (MyColumn VARCHAR(100));. Dit vermijdt dat we op een foutmelding botsen. Het kan wel een waarschuwing opleveren, maar dat is op zich niet erg.

Primaire sleutel toevoegen/verwijderen

Primaire sleutel toevoegen/verwijderen voor een bestaande tabel

We vertrekken hier van volgend script, 0048__CalibrateDB.sql:

CREATE DATABASE IF NOT EXISTS `ApDB` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `ApDB`;
-- MySQL dump 10.13  Distrib 5.7.28, for Linux (x86_64)
--
-- Host: localhost    Database: ApDB
-- ------------------------------------------------------
-- Server version    8.0.17

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

--
-- Table structure for table `Boeken`
--

DROP TABLE IF EXISTS `Boeken`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `Boeken` (
  `Voornaam` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
  `Familienaam` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `Titel` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
  `Stad` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
  `Verschijningsjaar` varchar(4) DEFAULT NULL,
  `Uitgeverij` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
  `Herdruk` varchar(4) DEFAULT NULL,
  `Commentaar` varchar(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
  `Categorie` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `IngevoegdDoor` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Boeken`
--

LOCK TABLES `Boeken` WRITE;
/*!40000 ALTER TABLE `Boeken` DISABLE KEYS */;
INSERT INTO `Boeken` VALUES ('Aurelius','Augustinus',NULL,NULL,NULL,NULL,NULL,NULL,'Metafysica',NULL),('Diderik','Batens','Logicaboek','','1999','','','','Metafysica',''),('Stephen','Hawking','The Nature of Space and Time',NULL,NULL,NULL,NULL,NULL,'Wiskunde',NULL),('Stephen','Hawking','Antwoorden op de grote vragen',NULL,NULL,NULL,NULL,NULL,'Filosofie',NULL),('William','Dunham','Journey through Genius: The Great Theorems of Mathematics',NULL,NULL,NULL,NULL,NULL,'Wiskunde',NULL),('William','Dunham','Euler: The Master of Us All',NULL,NULL,NULL,NULL,NULL,'Geschiedenis',NULL),('Evert Willem','Beth','Mathematical Thought',NULL,NULL,NULL,NULL,NULL,'Filosofie',NULL),('Jef','B','Het Boek',NULL,NULL,NULL,NULL,NULL,'Filosofie',NULL),('Mathijs','Degrote','Leren werken met SQL',NULL,NULL,NULL,NULL,NULL,'Informatica',NULL);
/*!40000 ALTER TABLE `Boeken` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `Honden`
--

DROP TABLE IF EXISTS `Honden`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `Honden` (
  `Naam` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `Leeftijd` tinyint(4) NOT NULL,
  `Geslacht` enum('mannelijk','vrouwelijk') DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Honden`
--

LOCK TABLES `Honden` WRITE;
/*!40000 ALTER TABLE `Honden` DISABLE KEYS */;
INSERT INTO `Honden` VALUES ('Rose',1,'vrouwelijk'),('Lacy',7,'vrouwelijk'),('Phoebe',4,'vrouwelijk'),('Camilla',14,'vrouwelijk'),('Betsy',7,'vrouwelijk'),('Lena',8,'vrouwelijk'),('Ella',8,'vrouwelijk'),('Samantha',15,'vrouwelijk'),('Sophia',7,'vrouwelijk'),('Abby',4,'vrouwelijk'),('Lily',10,'vrouwelijk'),('Biscuit',11,'vrouwelijk'),('Nori',8,'vrouwelijk'),('Sam',5,'vrouwelijk'),('Mika',9,'vrouwelijk'),('Baby',10,'vrouwelijk'),('Blondie',14,'vrouwelijk'),('Leia',10,'vrouwelijk'),('Mackenzie',10,'vrouwelijk'),('Trixie',11,'vrouwelijk'),('Hannah',9,'vrouwelijk'),('Kallie',5,'vrouwelijk'),('Maya',14,'vrouwelijk'),('Inez',15,'vrouwelijk'),('Gemma',8,'vrouwelijk'),('Priscilla',9,'vrouwelijk'),('Zoe',8,'vrouwelijk'),('Camilla',1,'vrouwelijk'),('Fiona',9,'vrouwelijk'),('Marley',11,'vrouwelijk'),('Betsy',12,'vrouwelijk'),('Bailey',8,'vrouwelijk'),('Gia',5,'vrouwelijk'),('Peanut',4,'vrouwelijk'),('Fern',4,'vrouwelijk'),('Tootsie',13,'vrouwelijk'),('Summer',11,'vrouwelijk'),('Gidget',3,'vrouwelijk'),('Brandy',1,'vrouwelijk'),('Peaches',1,'vrouwelijk'),('Sophie',11,'vrouwelijk'),('Cookie',14,'vrouwelijk'),('Ivy',1,'vrouwelijk'),('Mackenzie',10,'vrouwelijk'),('Sammie',9,'vrouwelijk'),('Sandy',8,'vrouwelijk'),('Callie',12,'vrouwelijk'),('Samantha',10,'vrouwelijk'),('Lola',2,'vrouwelijk'),('Angel',14,'vrouwelijk'),('Edie',12,'vrouwelijk'),('Diamond',5,'vrouwelijk'),('Bonnie',1,'vrouwelijk'),('Cinnamon',8,'vrouwelijk'),('Ella',12,'vrouwelijk'),('Brooklyn',14,'vrouwelijk'),('Miley',7,'vrouwelijk'),('Pebbles',5,'vrouwelijk'),('Hazel',3,'vrouwelijk'),('Peaches',7,'vrouwelijk'),('Bean',6,'vrouwelijk'),('Bianca',10,'vrouwelijk'),('Brandy',9,'vrouwelijk'),('Cleo',8,'vrouwelijk'),('Sam',9,'vrouwelijk'),('Precious',2,'vrouwelijk'),('Star',13,'vrouwelijk'),('Tessa',15,'vrouwelijk'),('Callie',6,'vrouwelijk'),('Daisy',15,'vrouwelijk'),('Darlene',9,'vrouwelijk'),('Madison',5,'vrouwelijk'),('Biscuit',4,'vrouwelijk'),('Lacy',8,'vrouwelijk'),('Destiny',4,'vrouwelijk'),('Olivia',6,'vrouwelijk'),('Allie',15,'vrouwelijk'),('Khloe',13,'vrouwelijk'),('Dolly',14,'vrouwelijk'),('Bonnie',6,'vrouwelijk'),('Blossom',7,'vrouwelijk'),('Jenna',14,'vrouwelijk'),('Violet',12,'vrouwelijk'),('Bean',13,'vrouwelijk'),('Anna',12,'vrouwelijk'),('Betty',12,'vrouwelijk'),('Destiny',3,'vrouwelijk'),('Nina',3,'vrouwelijk'),('Tilly',14,'vrouwelijk'),('Dana',10,'vrouwelijk'),('Ruby',14,'vrouwelijk'),('Fiona',3,'vrouwelijk'),('Brutus',8,'mannelijk'),('Nero',5,'mannelijk'),('Otto',13,'mannelijk'),('Rascal',9,'mannelijk'),('Kane',1,'mannelijk'),('Odie',9,'mannelijk'),('Ralph',9,'mannelijk'),('Tank',14,'mannelijk'),('Taz',2,'mannelijk'),('Kobe',5,'mannelijk'),('Dodge',4,'mannelijk'),('Aries',11,'mannelijk'),('Ned',11,'mannelijk'),('Alex',9,'mannelijk'),('Bo',10,'mannelijk'),('Eli',5,'mannelijk'),('Porter',2,'mannelijk'),('Duke',6,'mannelijk'),('Carter',13,'mannelijk'),('Casper',14,'mannelijk'),('Brutus',2,'mannelijk'),('Buddy',12,'mannelijk'),('Barkley',9,'mannelijk'),('Theo',5,'mannelijk'),('Maverick',12,'mannelijk'),('Buddy',9,'mannelijk'),('Taz',5,'mannelijk'),('Harvey',11,'mannelijk'),('Scout',5,'mannelijk'),('Rudy',13,'mannelijk'),('Trapper',15,'mannelijk'),('Buster',10,'mannelijk'),('Rocco',4,'mannelijk'),('Vinnie',1,'mannelijk'),('Murphy',13,'mannelijk'),('George',9,'mannelijk'),('Milo',11,'mannelijk'),('Kobe',2,'mannelijk'),('AJ',3,'mannelijk'),('Cash',11,'mannelijk'),('Eli',6,'mannelijk'),('Dane',9,'mannelijk'),('Theo',13,'mannelijk'),('Cash',7,'mannelijk'),('Nelson',3,'mannelijk'),('Luke',10,'mannelijk'),('Harvey',4,'mannelijk'),('Riley',6,'mannelijk'),('Tyson',9,'mannelijk'),('Gage',5,'mannelijk'),('Iggy',2,'mannelijk'),('Marley',7,'mannelijk'),('Fritz',15,'mannelijk'),('Bailey',14,'mannelijk'),('Porter',3,'mannelijk'),('King',10,'mannelijk'),('Snoopy',10,'mannelijk'),('Lewis',15,'mannelijk'),('Levi',1,'mannelijk'),('Leo',10,'mannelijk'),('Vince',2,'mannelijk'),('Trapper',13,'mannelijk'),('Kobe',11,'mannelijk'),('Simba',11,'mannelijk'),('Zeus',3,'mannelijk'),('Flash',15,'mannelijk'),('Watson',6,'mannelijk'),('Benji',3,'mannelijk'),('Frankie',15,'mannelijk'),('Dane',3,'mannelijk'),('Finn',1,'mannelijk'),('Coco',8,'mannelijk'),('Bailey',11,'mannelijk'),('Storm',11,'mannelijk'),('Griffin',2,'mannelijk'),('Zeus',13,'mannelijk'),('Boomer',15,'mannelijk');
/*!40000 ALTER TABLE `Honden` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `Kledingstukken`
--

DROP TABLE IF EXISTS `Kledingstukken`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `Kledingstukken` (
  `Nummer` int(11) NOT NULL,
  `Type` enum('polo','broek','trui') DEFAULT NULL,
  `Formaat` enum('small','medium','large') DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Kledingstukken`
--

LOCK TABLES `Kledingstukken` WRITE;
/*!40000 ALTER TABLE `Kledingstukken` DISABLE KEYS */;
INSERT INTO `Kledingstukken` VALUES (1,'polo','small'),(2,'polo','medium'),(3,'polo','large'),(4,'broek','small'),(5,'broek','medium'),(6,'broek','large'),(7,'trui','small'),(8,'trui','medium'),(9,'trui','large');
/*!40000 ALTER TABLE `Kledingstukken` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

-- Dump completed on 2019-11-26  9:41:44

de basis

We voegen een Id kolom toe aan de tabel Boeken die we als primaire sleutel gaan gebruiken.

Om een primaire sleutel toe te voegen aan een reeds bestaande tabel, gebruik je de DDL ALTER TABLE instructie in combinatie met een DDL ADD instructie:

ALTER TABLE Boeken ADD Id INT AUTO_INCREMENT PRIMARY KEY;

Sla die instructie op in 0049__AlterBoeken.sql.

Je kan ook nagaan of de primaire sleutel is toegevoegd door het volgende statement uit te voeren:

SHOW COLUMNS FROM Boeken;

Het feit dat een kolom een primaire sleutel is, is een constraint. Men spreekt van een constraint als iets een beperking is. Als je een rij zou toevoegen met een Id waarvan de waarde reeds in een andere rij bestaat, krijg je een foutmelding. Een ander (en reeds gekend) voorbeeld van een constraint is NOT NULL. Deze constraint is zwakker dan de PRIMARY KEY constraint (omdat NULL niet geschikt is om een rij te identificeren), dus je hoeft ze nooit toe te voegen aan een kolom die dient als primaire sleutel.

AUTO_INCREMENT

Het is beter om de Id door SQL zelf te laten toekennen. Zo hoef je niet telkens na te kijken welke waarde beschikbaar is voor Id. Om dat te doen, gebruik je de eigenschap AUTO_INCREMENT. Als je een nieuwe tabel maakt voeg je de eigenschap toe na de declaratie van de kolom. Zorg ervoor dat je op die kolom een primary key constraint hebt staan:

USE ApDB;
CREATE TABLE Personen (
    Id INT AUTO_INCREMENT PRIMARY KEY,
    Voornaam varchar(255) char set utf8mb4 NOT NULL,
    Familienaam varchar(255) char set utf8mb4,
    Leeftijd int
);

Sla op als 0050__CreatePersonen.sql.

Je kan de beginwaarde zelf bepalen. Bijvoorbeeld, als je de boeken wil nummeren vanaf 5 in plaats van 1 (de default):

ALTER TABLE Boeken AUTO_INCREMENT = 5;

Dit kan van pas komen als je al wat data hebt en SQL alleen voor de nieuwe data zelf de nummers wil laten genereren.

Eerder hebben we AUTO_INCREMENT al gebruikt voor boeken. Bekijk eens wat er gebeurd was als je AUTO_INCREMENT in dat script achterwege had gelaten.

Primary key constraints verwijderen

Een constraint behoort tot de definitie van de tabel, dus moet je DROP gebruiken:

USE ApDB;
ALTER TABLE Boeken DROP PRIMARY KEY;

Datatypes

Soorten datatypes

MySQL ondersteunt drie brede soorten datatypes, met per soort verschillende concretere vormen. Deze drie soorten zijn:

  1. string types (ofwel "tekst")

  2. numerieke types (ofwel "getallen")

  3. temporele types (datums en/of tijdstippen)

In deze cursus beperken we ons tot vier brede types: varchar, int, float en datetime. Dit is een vereenvoudigde weergave, bedoeld om de concepten aan te leren. Er zijn nog andere basistypes en je kan ook bepaalde extra beperkingen opleggen. Je kan alle details terugvinden in de (uitstekende) officiële MySQL documentatie.

String types

VARCHAR

VARCHAR stelt een stuk tekst met een bepaalde maximumlengte voor. Zo is een kolom met type VARCHAR(50) beperkt tot maximum 50 karakters.

strings schrijven

Als je data van dit type hebt, zet je het tussen enkele of dubbele aanhalingstekens. Bijvoorbeeld 'Do,Re,Mi,Fa,Sol,La,Si'. Als je data zelf aanhalingstekens bevat, gebruik dan het andere type voor de gehele string.

Let op! Alles wat tussen aanhalingstekens staat is tekst. Zelfs cijfers. Met andere woorden: '42' is tekst en 42 is een getal. Met tekst kan je niet rekenen, met een getal wel. MySQL probeert wel te raden wat je bedoelt als je bijvoorbeeld '42' + '1' schrijft, maar niet elke taal doet dit. Daarom spreken we af dat je MySQL niet laat raden, maar altijd het juiste datatype gebruikt.

numerieke types

INT en varianten

INT dient voor de opslag van gehele getallen. Standaard kan een INT positieve en negatieve gehele getallen voorstellen. Je kan met INT geen kleiner getal opslaan dan -2147483648 en geen groter getal dan 2147483647.

FLOAT

Dit type dient om kommagetallen bij benadering op te slaan. Dit wil zeggen dat (vaak héél kleine) afrondingen toegestaan zijn wanneer je data in het systeem plaatst.

getallen schrijven

Deze data zet je niet tussen quotes. Je gebruikt een punt in plaats van een komma.

temporele types

Temporele types worden gebruikt om tijdstippen voor te stellen. Hierbij wordt volgende notatie gebruikt om een formaat voor te stellen:

  • Y: cijfer van een jaar

  • M: cijfer van een maand

  • D: cijfer van een dag

  • H: cijfer van een uur

  • m: cijfer van een minuut

  • S: cijfer van een seconde

DATETIME

Een DATETIME is een waarde die een specifiek ogenblik in de tijd voorstelt. Met andere woorden ongeveer een combinatie van een dag en een moment van de dag. Het formaat is YYYY-MM-DD HH:MM:SS en het bereik ligt tussen het jaar 1000 en 9999.

datums schrijven

Je schrijft datums alsof het strings waren in een afgesproken formaat, dus wel tussen quotes.

defaultwaarden

Je kan er bij het aanmaken of wijzigen van een kolom voor zorgen dat je een kolom niet uitdrukkelijk hoeft in te vullen. Dat doe je door, na de naam van de kolom, het woordje default noteren, gevolgd door een waarde van dat type. Bijvoorbeeld:

Dit voegt een nieuwe kolom toe voor het ISBN-nummer aan een bestaande tabel boeken. Dit is een tekstkolom van maximum 25 symbolen. Als de gebruiker géén ISBN invult, krijgt een boek automatisch de waarde ABC123 in die kolom.

Enum

Vaak kan je in een bepaalde kolom maar een beperkt aantal mogelijke waarden invullen. Een datatype zoals INT of VARCHAR is dan niet op zijn plaats, omdat die heel veel mogelijke waarden toelaten. Dat zorgt voor inefficiëntie en een grotere kans op foute invoer.

Voor dergelijke kolommen is het beter enumeraties te gebruiken, of ENUMs, om het met de woordenschat van MySQL te zeggen. Een ENUM is een stuk tekst met een waarde uit een op voorhand aangegeven stel mogelijkheden.

Veronderstel dat je een database met stukken kledij bijhoudt, bijvoorbeeld deze:

Als de winkel alleen polo's, broeken en truien verkoopt en als er maar drie formaten bestaan, stel je de tweede en derde kolom best voor met een enumeratie.

Dat gaat als volgt:

Noem die code 0026__CreateKledingstukken.sql.

Vervolgens kan je data aanmaken alsof de tweede en derde kolom tekst bevatten, met 0027__InsertKledingstukken.sql:

Wat niet gaat, is dit (hoef je niet op te slaan):

Dat komt omdat we alle mogelijke waarden al hebben vastgelegd.

Enumeraties hebben verschillende voordelen:

  • Ze zijn zuiniger in gebruik van opslagruimte dan strings die dezelfde tekst voorstellen.

  • Ze zorgen voor meer leesbare invoer en uitvoer dan getallen.

Let wel op! Enumeraties lijken op strings, maar ze worden anders gesorteerd. De volgorde waarin waarden van een enum gesorteerd worden, is de volgorde waarin de elementen gedeclareerd zijn.

Dit kan je afleiden uit volgend script (0028__SelectKledingstukken.sql):

Wat zou dit geven als formaat een VARCHAR was?

Kennisclip
Kennisclip
ALTER TABLE Boeken ADD COLUMN ISBN VARCHAR(25) DEFAULT ("ABC123");

artikelnummer

soort kledij

formaat

1

polo

small

2

polo

medium

3

polo

large

4

broek

small

5

broek

medium

6

broek

large

7

trui

small

8

trui

medium

9

trui

large

USE ApDB;
CREATE TABLE Kledingstukken (
Nummer INT NOT NULL,
Soort ENUM('polo','broek','trui'),
Formaat ENUM('small','medium','large')
);
USE ApDB;
INSERT INTO Kledingstukken
VALUES
(1, 'polo', 'small'),
(2, 'polo', 'medium'),
(3, 'polo', 'large'),
(4, 'broek', 'small'),
(5, 'broek', 'medium'),
(6, 'broek', 'large'),
(7, 'trui', 'small'),
(8, 'trui', 'medium'),
(9, 'trui', 'large');
USE ApDB;
INSERT INTO Kledingstukken
VALUES
(10,'hemd','extra large');
USE ApDB;
SELECT *
FROM Kledingstukken
ORDER BY Formaat;

Primaire sleutel in een nieuwe tabel

Het is niet erg handig om telkens de primaire sleutel achteraf toe te voegen. Als je een nieuwe tabel maakt, kan je in één keer een kolom aanduiden als primaire sleutel:

USE ApDB;
DROP TABLE IF EXISTS Boeken;

CREATE TABLE Boeken(
    Id INT AUTO_INCREMENT PRIMARY KEY,
    Voornaam varchar(50) char set utf8mb4,
    Familienaam varchar(80) char set utf8mb4,
    Titel varchar(255) char set utf8mb4,
    Uitgeverij varchar(255) char set utf8mb4,
    Stad varchar(50) char set utf8mb4,
    Verschijningsdatum varchar(4),
    Herdruk varchar(4),
    Commentaar varchar(2000) char set utf8mb4,
    Categorie varchar(120) char set utf8mb4
);

Deze hoef je niet uit te voeren.

DML

De belangrijkste commando's van de DML zijn:

  • INSERT: Hiermee voeg je data in

  • SELECT: Hiermee vraag je data op

  • UPDATE: Hiermee pas je data aan

  • DELETE: Hiermee verwijder je data

Kennisclip voor deze inhoud

Vreemde sleutels

Concept

Wanneer we in één record via een bepaalde kolom verwijzen naar (de sleutel van) een ander record, spreken we over een vreemde sleutel of foreign key. Hij is "vreemd" omdat hij verwijst naar een ander record, mogelijk (maar niet noodzakelijk) in een andere tabel.

Gebruik (nieuwe tabel)

Als de primary key van een tabel waar je naar verwijst een INT is, definieer je een kolom met een foreign key ook als INT. Na de oplijsting van de kolommen voeg je dan een constraint toe. Hierbij spelen drie zaken mee:

  1. de naam van de constraint

  2. de kolom die moet dienen als foreign key

  3. de kolom die dienst doet als primary key in de tabel waarnaar verwezen wordt

Dat ziet er bijvoorbeeld zo uit, als je een tabel met boeken koppelt aan hun auteur (veronderstel even dat een boek één auteur heeft):

CREATE TABLE Boeken (
  Id INT AUTO_INCREMENT PRIMARY KEY,
  Personen_Id INT, -- = persoon die bij dit boek hoort
  CONSTRAINT fk_Boeken_Personen FOREIGN KEY (Personen_Id)
  REFERENCES Personen(Id)
);

Deze hoef je niet uit te voeren.

Dit wil zeggen: "maak een tabel voor boeken met een uniek identificatienummer (de primary key); boeken kunnen ook verwijzen naar specifieke records in de tabel die personen voorstelt; deze verwijzingen worden voorgesteld met de kolom Personen_Id in de tabel voor boeken"

Hierbij gebruiken we enkele afspraken:

  • De naam van de kolom die dienst doet als foreign key is de naam van de tabel waarnaar verwezen wordt, gevolgd door _Id.

  • De naam van een foreign key constraint is altijd fk_, gevolgd door de naam van de tabel waarop de constraint toegepast is, gevolgd door de naam van de tabel waarnaar verwezen wordt.

Prent het heel goed in: een kolom met als naam Id dient om een rij uit de tabel te identificeren waartoe de kolom behoort. Een kolom met als naam (Tabelnaam)_Id (waarbij de tabelnaam vanalles kan zijn) dient om een rij uit een andere tabel te identificeren, die hoort bij een rij uit deze tabel. Je zou nooit mogen twijfelen over hoe je een sleutelkolom moet noemen. Dit is een afspraak die wij steeds zullen volgen.

Gebruik (bestaande tabel)

Als je al een tabel hebt en deze wil uitbreiden met een foreign key, doe je dat via de DDL ALTER-instructie. Als je bijvoorbeeld al een tabel voor boeken had zoals hierboven, maar zonder de kolom Personen_Id, zou je dit schrijven:

ALTER TABLE Boeken
ADD COLUMN Personen_Id INT, -- = persoon die bij dit boek hoort
ADD CONSTRAINT fk_Boeken_Personen
  FOREIGN KEY (Personen_Id)
  REFERENCES Personen(Id);

Sla op als 0051__AlterBoeken.sql en voer uit.

Labo

Oefeningen

Tip

Als je tabellen maakt, zit het niet altijd meteen juist. Vaak probeer je een bepaalde structuur voor je data en verbeter je deze regelmatig. Om je de structuur van je data te bekijken, moet je in MySQL Workbench eerst je databanken refreshen. Daarna kan je je databank openklappen en via rechtermuisklik de structuur van je tabel controleren.

nummers

Schrijf een script dat een tabel Nummers voor muzieknummers toevoegt aan je databank met volgende karakteristieken:

"Variabele lengte" betekent dat de tekst niet altijd de maximale hoeveelheid ruimte in beslag neemt.

Sla op als 0019__Oefening.sql.

huisdieren

Doe hetzelfde voor een tabel Huisdieren met huisdieren, met volgende kenmerken:

Je hoeft niet in te stellen dat 300 de maximale leeftijd is. Je moet alleen zorgen dat 300 zeker past in de kolom in kwestie, zonder dat er daarbij opslagruimte verspild wordt. Kies dus het kleinste datatype dat groot genoeg of te groot is.

Sla op als 0020__Oefening.sql.

data nummers

Plaats volgende data in je tabel met muzieknummers:

1. het nummer "John the Revelator" van de groep "Larkin Poe". Het genre is "Blues" en het verschijningsjaar is 2017.

2. het nummer "Missionary Man" van de groep "Ghost". Het genre is "Metal" en het verschijningsjaar is 2016.

Sla op als 0021__Oefening.sql.

data huisdieren

Plaats volgende data in je tabel met huisdieren:

  1. Ming, 9 jaar oud, is de hond van Christiane

  2. Bientje, 12 jaar oud, is de kat van Esther

  3. Misty, 7 jaar oud, is de hond van Vincent

Sla op als 0022__Oefening.sql.

zoekopdracht huisdieren

Toon met een SELECT de duo's met huisdieren en baasjes in het formaat H+B, gerangschikt volgens de leeftijd van het huisdier.

De juiste oplossing toont volgende rijen in MySQL Workbench, in exact deze volgorde:

Sla op als 0023__Oefening.sql.

een kolom voor klassificatie

Klassificatie van boeken is een wetenschap op zich. Er zijn veel systemen voor, maar ze gebruiken allemaal een code van een vrij klein aantal letters om aan te geven in welke cluster een bepaald boek thuishoort.

Schrijf zelf een script dat een verplichte kolom met naam Categorie toevoegt aan de bestaande tabel met boeken. De categorie van een boek wordt uitgedrukt in maximaal 40 lettertekens. Dit zijn "gewone" lettertekens die je zou aantreffen in een typische Engelstalige tekst. Deze kolom heeft ook een defaultwaarde, namelijk "Zonder categorie".

Noem je script 0017__Oefening.sql.

een kolom voor de klasseur

Het is handig om bij te houden wie een boek in het systeem heeft geplaatst. Daarom willen we een extra kolom met de naam IngevoegdDoor die maximaal 255 karakters lang is en zeker internationale karakters moet kunnen bevatten. Deze kolom is niet verplicht.

Noem je script 0018__Oefening.sql.

observaties (structuur)

We willen wetenschappelijke observaties van het grondwaterniveau bijhouden. Maak hiervoor een tabel GrondwaterObservaties met twee verplichte kolommen: één kolom voor het tijdstip van de observatie (dag, maand, jaar, uur, minuten én seconden) en één kolom voor het grondwaterniveau uitgedrukt als kommagetal. Het grondwaterniveau hoeft niet met perfecte precisie voorgesteld te worden en je hoeft er niet veel opslagruimte voor te gebruiken. Bepaal zelf het meest geschikte kolomtype.

Noem je script 0024__Oefening.sql

observaties (data)

Plaats volgende informatie in je nieuwe tabel:

Noem je script 0025__Oefening.sql

aankopen (structuur en data)

In een winkelsysteem willen we alle aankopen registreren. Maak daarom een tabel Aankopen met drie stukjes informatie, die allemaal verplicht zijn:

  • de naam van het artikel, uitgedrukt als verplichte tekst van maximum 100 karakters

  • het aantal stuks, uitgedrukt als positief getal dat past in één byte

  • de aankoopprijs per stuk, uitgedrukt als exact kommagetal dat bestaat uit vijf cijfers, waarvan twee na de komma

Plaats, in hetzelfde script, volgende gegevens in deze tabel:

Noem je script 0029__Oefening.sql

bestellingen auto's

Maak een tabel Bestellingen die kan dienen voor het bestelsysteem van een autodealer. Deze bevat drie (verplichte) zaken:

  • het tijdstip waarop de bestelling geplaatst is

  • het model - dit kan alleen een van onderstaande opties zijn (tip: gebruik een enum datatype)

    • Qivic

    • Nexus

    • Yugo

  • de kleur - dit kan alleen een van onderstaande opties zijn

    • zwart

    • geel

    • rood

Noem je script 0030__Oefening.sql

In heb je gezien dat er verschillende redenen zijn om rijen uniek te identificeren. Dit is alleen zinvol als we de primaire sleutelattributen ook ergens anders vermelden. Met andere woorden, als we in kolom A van tabel B een primaire sleutel plaatsen, is het logisch dat we in kolom C van tabel D over deze sleutel spreken.

We hebben dat ook gedaan in het tweede voorbeeld op (het voorbeeld met games), door een tabel te maken die de dubbele voorkomens van lange stukken tekst vervangt door hun identificatienummers.

Je hebt hier de concat functie nodig, uitgelegd op .

Kennisclip
het voorbeeld met games
de eerder genoemde pagina

Kolomnaam

Type

Maximale lengte

Variabele lengte

Internationale tekens

Verplicht

Titel

tekst

100

JA

zeker nodig

JA

Artiest

tekst

100

JA

zeker nodig

JA

Genre

tekst

50

JA

niet zeker nodig

NEE

Jaar

tekst

4

NEE

niet zeker nodig

NEE

Kolomnaam

Type

Maximale lengte

Variabele lengte

Internationale tekens

Verplicht

Naam

tekst

100

JA

zeker nodig

JA

Leeftijd

een geheel positief getal

tot 300 jaar

NVT

NVT

JA

Soort

tekst

50

JA

niet zeker nodig

JA

Baasje

tekst

100

JA

zeker nodig

JA

(wat hier staat is niet belangrijk)

M+V

M+C

B+E

tijdstip

meting

4 februari 2020 om 8u35m9s

80.2

7 februari 2020 om 10u12m13s

76.4

12 februari 2020 om 11u13m52s

83.5

naam

aantal stuks

aankoopprijs per stuk

paar schoenen

2

60.99

waterfles

1

8.99

de pagina over SELECT

INSERT

Aanmaken van je eerste concrete data

Aan alleen structuur heb je niets. We willen ook data bijhouden in ons systeem. Met andere woorden: rijen, niet alleen tabellen.

Er is een vaste syntax om data in je databank te plaatsen, waar je even aan zal moeten wennen. Het is een goed idee om met een stub te beginnen, een skelet statement. Dit zorgt er voor dat je fouten in de syntax snel opmerkt. Voor een tabel Boeken ziet dit er zo uit, voor een enkel nieuw record:

INSERT INTO Boeken (
-- hier moet je gebruikte kolomnamen invullen
)
VALUES (
-- hier moet je waarden voor de gebruikte invullen
);

Meer data in een keer

Je hoeft geen INSERT statement te schrijven per stukje data. Je kan meerdere rijen tegelijk invoegen door ze te scheiden met komma's:

INSERT INTO Boeken ( Voornaam, Titel )
VALUES
('Gerard', 'Heideggers vraag naar de techniek'),
('Diderik', 'Logicaboek');

DELETE

Net zoals het DROP statement verwijdert het DELETE statement objecten uit de database. Het DROP statement verwijdert een tabel uit de database, het DELETE statement verwijdert hele rijen uit de tabel maat laat de tabelstructuur staan.

Meestal willen we specifieke records verwijderen en geen volledige tabellen leegmaken. Daarom ondersteunt DELETE dezelfde WHERE-clausule als SELECT. Als je geen WHERE clausule gebruikt, worden alle rijen uit de tabel verwijderd en blijft alleen de structuur van de tabel over. Wees daar dus voorzittig mee want als de rijen gedeletet zijn kan je ze niet meer terughalen. Net als bij UPDATE verbiedt MySQL standaard bepaalde "onveilige" operaties. Ook hier gebruik je SET SQL_SAFE_UPDATES = 0 en SET SQL_SAFE_UPDATES = 1 voor, respectievelijk na de operatie.

De syntax van DELETE lijkt erg op die van SELECT, maar in plaats van bepaalde rijen te tonen, zal MySQL ze gewoon wissen. Je kan ook geen specifieke kolommen wissen, dus je schrijft DELETE FROM Boeken en niet DELETE * FROM Boeken of DELETE Voornaam FROM Boeken.

De gelijkenis tussen SELECT en DELETE is erg handig. Voer altijd eerst een SELECT * uit in plaats van een DELETE en dan weet je steeds welke gegevens precies gewist zullen worden.

Relaties-voorstellen

Relatietypes voorstellen

Afhankelijk van hoe entiteiten aan elkaar gekoppeld kunnen zijn, delen we de koppelingen tussen deze entiteiten op in categorieën:

  • een-op-een-relaties, d.w.z. één entiteit van een entiteittype hoort bij exact één entiteit van een gekoppeld type

  • een-op-veel-relaties, d.w.z. één entiteit van een entiteittype hoort niet bij 0, 1 of meerdere entiteiten van een gekoppeld type

  • veel-op-veel-relaties (ook wel M-op-N relaties genoemd)

Er is nog een indeling in relaties: identificerende tegenover niet-identificerende relaties. Dit onderscheid heeft niet veel impact op het basisgebruik van een databank. Met de werkwijze die wij volgen, kan je steeds niet-identificerende relaties gebruiken, tenzij het om veel-op-veel relaties gaat.

een-op-een relaties

De simpelste verbanden zijn één-op-één verbanden. Dat wil zeggen: precies twee rijen nemen deel aan de relatie. Normaal zijn dit rijen van verschillende entiteittypes, al is het niet verplicht.

Een voorbeeld: een sportclub organiseert een jaarlijks etentje en alle leden krijgen precies één taak. Eén lid zorgt bijvoorbeeld voor bestek, een ander voor frisdrank, een ander voor onderleggers, enzovoort. De club gebruikt een database om de taken en de leden bij te houden en de taken zijn elk jaar dezelfde. Bijvoorbeeld:

  • taken:

    • bestek voorzien

    • frisdrank meebrengen

    • aardappelsla maken

  • leden:

    • Yannick

    • Bavo

    • Max

Onderstaande figuur stelt deze indeling voor in een ERD getekend in de editor van MySQL Workbench:

In dit geval is het logisch om een aparte tabel (Taken) voor taken en een aparte tabel (Leden) voor leden te gebruiken. Het is onhandig om uit te leggen dat één rij in de databank een lid en een taak voorstelt, omdat de tabellen best zo goed mogelijk overeenstemmen met duidelijke concepten. Het is logischer de leden en de taken als aparte entiteiten te beschouwen en een relatie tussen beide vast te leggen.

Er zijn wel systemen die je dwingen om dit soort relatie voor te stellen door Taken en Personen samen te smelten tot één tabel.

voorstelling van één-op-één relaties

Om een relaties tussen rijen van de tabellen vast te leggen, maken we de rijen eerst identificeerbaar met een primaire sleutel. Bijvoorbeeld, voor de taken:

Omschrijving

Id

bestek voorzien

1

frisdrank meebrengen

2

aardappelsla maken

3

Voor de leden:

Naam

Id

Yannick

1

Bavo

2

Max

3

Zet de structuur van de twee tabellen om in SQL-tabellen met een script 0052__CreateTakenLeden.sql. Elke normale kolom bestaat uit een reeks van maximaal 45 karakters en is verplicht. De Id-kolom stel je voor met een INT die automatisch ophoogt. Elke kolom heeft dezelfde naam die gebruikt wordt in de hoofdingen hierboven.

Vul de twee tabellen, Taken en Leden in met een script 0053__InsertTakenLeden.sql.

Als Bavo bestek voorziet, Yannick frisdrank meebrengt en Max aardappelsla maakt, kunnen we dat als volgt bijhouden in een aparte tabel die alleen vreemde sleutels bevat:

Leden_Id

Taken_Id

2

1

1

2

3

3

In de praktijk wordt er normaal niet voor gekozen om deze relatie in een aparte tabel vast te leggen. Dat zou gaan, maar het is gewoon een beetje te veel van het goede. Je hebt geen aparte tabel nodig. Eén van de twee tabellen wordt uitgebreid met een foreign key. Er zijn twee mogelijkheden:

Omschrijving

Id

Leden_Id

bestek voorzien

1

2

frisdrank meebrengen

2

1

aardappelsla maken

3

3

of

Naam

Id

Taken_Id

Yannick

1

2

Bavo

2

1

Max

3

3

Beide zijn even goed. Typisch wordt (in geval van een niet-identificerende relatie en die gebruiken wij altijd) gekozen om de foreign key in de tabel te zetten met het kleinste aantal kolommen, om alles een beetje in evenwicht te houden. Hier hebben beide even veel kolommen dus het maakt helemaal niet uit.

Pas je tabel Leden aan zodat ze de tweede mogelijkheid van hierboven implementeert in een script 0054__AlterLeden.sql. Je moet eerst de kolom toevoegen, dan invullen, dan verplicht maken.

Dit ziet er zo uit:

USE ApDB;
SET SQL_SAFE_UPDATES = 0;
ALTER TABLE Leden
ADD COLUMN Taken_Id INT, -- d.w.z. de taak die bij dit lid hoort
ADD CONSTRAINT fk_Leden_Taken
FOREIGN KEY (Taken_Id) -- dit is de kolom (uit de eigen tabel) waarmee we verwijzen
REFERENCES Taken(Id); -- dit is hetgeen waar we naar verwijzen (kolom andere tabel)
UPDATE Leden
SET Taken_Id = 2
WHERE Naam = 'Yannick';
UPDATE Leden
SET Taken_Id = 1
WHERE Naam = 'Bavo';
UPDATE Leden
SET Taken_Id = 3
WHERE Naam = 'Max';
ALTER TABLE Leden
CHANGE Taken_Id Taken_Id INT NOT NULL;
SET SQL_SAFE_UPDATES = 1;

Je zou de data nu kunnen combineren, maar daar heb je een JOIN-operatie voor nodig. Die komt later.

een-op-veel relaties

Een een-op-veel (of 1-op-N) verband is een verband dat je heel vaak tegenkomt op websites met een achterliggende databank. Bij dit soort verband stemt een rij uit een bepaalde tabel A overeen met meerdere rijen uit een tabel B. In de omgekeerde richting stemt een rij uit tabel B maar met één rij van tabel A overeen. Bijvoorbeeld, als je tweets bijhoudt in een databank, kan één persoon meerdere tweets hebben, maar één tweet kan oorspronkelijk slechts van één persoon komen. Hier moet je je inbeelden dat personen bijgehouden worden in tabel A en tweets in tabel B.

In een database van een webshop kan één persoon meerdere bestellingen plaatsen, maar één bestelling kan slechts van één klant komen. Hier geldt: personen in A, bestellingen in B.

Hier zijn enkele tweets die we als voorbeeld zullen gebruiken, voorafgegaan door de handle van de gebruiker die ze geschreven heeft:

@NintendoEurope: Don't forget -- Nintendo Labo: VR Kit launches 12/04!
@NintendoEurope: Splat it out in the #Splatoon2 EU Community Cup 5 this Sunday!
@NintendoEurope: Crikey! Keep an eye out for cardboard crocs and other crafty wildlife on this jungle train ride! #Yoshi
@Xbox: You had a lot to say about #MetroExodus. Check out our favorite 5-word reviews.
@Xbox: It's a perfect day for some mayhem.
@Xbox: Drift all over N. Sanity Beach and beyond in Crash Team Racing Nitro-Fueled.

Zoals in het geval van de 1-op-1 relatie, kunnen we deze relatie tussen gebruikers en tweets voorstellen in een tabel:

user

tweet

1

1

1

2

1

3

2

4

2

5

2

6

Dit is opnieuw iets meer dan we nodig hebben. We kunnen een foreign key van één tabel toevoegen aan een andere. Maar, in tegenstelling tot de precieze 1-op-1-relatie, mogen we niet kiezen. We zetten de foreign key in de tabel die niet aan de "exact-1"-kant van de relatie zit. Zorg er ook voor dat de vreemde sleutel nooit NULL is met een constraint.

Voer dit zelfstandig uit voor de reeks tweets hierboven. Volg de reeds afgesproken afspraken: één tabel Users voor users (met een kolom Handle), één tabel Tweets voor tweets (met een kolom Bericht), beide voorzien van primaire sleutels, met de vreemde sleutel aan de "N-kant". Stel gebruikersnamen en tweets voor met kolommen van variabele lengte (tot 144 tekens), zonder internationale tekens. De @ maakt geen deel uit van een gebruikersnaam. Zet de SQL-code die je nodig hebt om de (lege) tabellen te maken in een script 0055__CreateUsersTweets.sql. Zet de code die je nodig hebt om de vreemde sleutel toe te voegen in 0056__AlterTweets.sql. Zet ten slotte de code om de tabellen in te vullen in een script 0057__InsertUsersTweets.sql. Begin met een INSERT voor de users, doe dan pas die voor de tweets.

Voor het laatste script bespaart onderstaande gedeeltelijke SQL je het copy-pasten van de tweets.

('Don''t forget -- Nintendo Labo: VR Kit launches 12/04!',1),
('Splat it out in the #Splatoon2 EU Community Cup 5 this Sunday!',1),
('Crikey! Keep an eye out for cardboard crocs and other crafty wildlife on this jungle train ride! #Yoshi',1),
('You had a lot to say about #MetroExodus. Check out our favorite 5-word reviews.',2),
('It''s a perfect day for some mayhem.',2),
('Drift all over N. Sanity Beach and beyond in Crash Team Racing Nitro-Fueled.',2)

speciaal geval: een-op-max-een-relaties

Een een-op-max-een relatie is een relatie waarbij één entiteit A gelinkt is aan hooguit één andere entiteit B. Het kan ook zijn dat A aan geen enkele B gelinkt is. Deze stel je voor zoals een 1-op-N relatie, dus met de vreemde sleutel in de tabel aan de niet-exact-1-kant.

USE ApDB;
SELECT Handle, Bericht
FROM Users
INNER JOIN Tweets
ON Users_Id = Users.Id;

veel-op-veel relaties

Een auteur kan meerdere boeken hebben en een boek kan verschillende auteurs hebben. Een game kan op verschillende platformen uitgebracht zijn en voor elk platform zijn er verschillende games beschikbaar. Een student volgt verschillende vakken en in elk vak zitten verschillende studenten. Dit zijn allemaal voorbeelden waar één rij uit een tabel A gekoppeld kan zijn aan meerdere rijen uit een tabel B en één rij uit dezelfde tabel B gekoppeld kan zijn aan meerdere rijen uit dezelfde tabel A. We zeggen dan ook dat er een veel-op-veel of M-op-N-relatie bestaat tussen de entiteiten A en B.

Bij 1-op-1-relaties mochten we de vreemde sleutel in tabel A of B zetten (en sommige systemen vereisen zelfs dat je de tabellen gewoon samensmelt). Bij 1-op-max-1 of 1-op-N relaties zetten we de vreemde sleutel in de tabel die niet precies één keer gekoppeld was. Dit werd vooral gedaan om geen overbodige tabellen toe te voegen. We konden in principe de takenverdeling voor het etentje ook als volgt voorstellen, met een aparte tabel:

Leden_Id

Taken_Id

2

1

1

2

3

3

We deden dit alleen anders omdat we het met een tabel minder (en dus in totaal ook een kolom minder) konden. Voor een M-op-N-relatie is deze voorstelling echter onze beste optie.

voorbeeld

Een game kan beschikbaar zijn op meerdere platformen en op elk platform zijn er natuurlijk meerdere games beschikbaar. Bijvoorbeeld:

  • Anthem: beschikbaar op PS4, XBox One, Windows

  • Sekiro: beschikbaar op PS4, XBox One, Windows

  • Devil May Cry 5: beschikbaar op PS4, XBox One

  • Mega Man 11: beschikbaar op PS4, XBox One, Windows, Nintendo Switch

Veronderstel dat Anthem ID 1 heeft, Sekiro 2, enzovoort. Veronderstel ook dat PS4 ID 1 heeft, Xbox One ID 2, Windows ID 3 en Nintendo Switch ID 4. Dan kunnen we voorstellen welke games uitgebracht zijn op welke platformen als volgt:

Games_Id

Platformen_Id

1

1

1

2

1

3

2

1

2

2

2

3

3

1

3

2

4

1

4

2

4

3

4

4

Je hebt hier drie tabellen nodig: een voor games, een voor platformen, een voor de koppeling. De tabel Games heeft naast de Id één kolom: Titel, een stuk tekst van maximaal 50 karakters dat nooit leeg mag zijn en mogelijk Unicode karakters bevat. Voor Platformen is er een gelijkaardige structuur, maar de naam van de kolom die niet als sleutel wordt gebruikt is Naam. Noem de tabel die de koppeling afhandelt Releases. Volg de conventie voor de naam van de kolommen die naar beide andere tabellen verwijzen. Sla de DDL-instructies op als 0059__CreateGamesPlatformenReleases.sql. Voeg de DML-instructies toe als 0060__InsertGamesPlatformenReleases.sql.

Voor 0059 zou je dit moeten hebben:

CREATE TABLE Platformen(Naam varchar(50) CHARSET utf8mb4 NOT NULL, Id INT AUTO_INCREMENT PRIMARY KEY);
CREATE TABLE Games(Titel varchar(50) CHARSET utf8mb4 NOT NULL, Id int auto_increment primary key);
CREATE TABLE Releases(Games_Id INT NOT NULL,
                      Platformen_Id INT NOT NULL,
              CONSTRAINT fk_Releases_Games FOREIGN KEY (Games_Id) REFERENCES Games(Id),
              CONSTRAINT fk_Releases_Platformen FOREIGN KEY (Platformen_Id) REFERENCES Platformen(Id));

Dit stemt overeen met een diagram in Workbench dat er zo uitziet:

Voor 0060:

INSERT INTO Platformen(Naam)
VALUES
('PS4'),
('Xbox One'),
('Windows'),
('Nintendo Switch');
INSERT INTO Games(Titel)
Values
('Anthem'),
('Sekiro: Shadows Die Twice'),
('Devil May Cry 5'),
('Mega Man 11');
-- je zou dit typisch niet met de sleutels doen
-- hier nemen we echter over uit de cursus
INSERT INTO Releases(Games_Id,Platformen_Id)
values
(1,1),
(1,2),
(1,3),
(2,1),
(2,2),
(2,3),
(3,1),
(3,2),
(4,1),
(4,2),
(4,3),
(4,4);
USE ApDB;
SELECT Games.Titel, Platformen.Naam
FROM Releases
INNER JOIN Platformen ON Releases.Platformen_Id = Platformen.Id
INNER JOIN Games ON Releases.Games_Id = Games.Id

Relaties met attributen

Attributen horen meestal bij entiteiten, maar kunnen ook bij relaties horen. Bovenstaande tabel Releases geeft bijvoorbeeld aan welk spel op welk platform verschenen is, maar wat als we de releasedatum willen bijhouden? Deze hoort niet in de tabel Games. Hij hoort ook niet in de tabel Platformen. Hij hoort bij de combinatie van een game en een platform, d.w.z. bij de relatie die wordt voorgesteld met de tabel Releases. Daarom kunnen we de tabel ook uitbreiden met een kolom Releasedatum. Voor het leesgemak stellen we de games en de platformen niet voor via hun Id-attribuut.

Titel

Naam

Releasedatum

Anthem

PS4

22 februari 2019

Anthem

XBox One

22 februari 2019

Anthem

Windows

22 februari 2019

Sekiro: Shadows Die Twice

PS4

22 maart 2019

Sekiro: Shadows Die Twice

XBox One

22 maart 2019

Sekiro: Shadows Die Twice

Windows

22 maart 2019

Devil May Cry 5

PS4

8 maart 2019

Devil May Cry 5

XBox One

8 maart 2019

Mega Man 11

PS4

2 oktober 2018

Mega Man 11

XBox One

2 oktober 2018

Mega Man 11

Windows

2 oktober 2018

Mega Man 11

Nintendo Switch

2 oktober 2018

In een ERD stellen we dit als volgt voor:

In dit geval is Releases niet gewoon een tabel die een relatie voorstelt, maar wel een associative entity: een relatie tussen Games en Platformen die eigen kenmerken bezit, zodat je ze eigenlijk ook als een entiteit zou kunnen zien.

Voeg zelf de nodige info toe. Hiervoor volg je volgende stappen:

  1. Voeg een kolom van type DATE toe aan de tabel Releases. Deze kan nog niet verplicht zijn. Noem het script 0062__AlterReleases.sql.

  2. Kopieer het script dat games en hun releaseplatform weergeeft naar een nieuw script, 0063__UpdateReleases.sql.

  3. Pas voor de gecombineerde tabel de datum aan volgens de gegevens hierboven. Je kan in deze tabel een SET uitvoeren op Releasedatum.

    • Gebruik WHERE Games.Titel = ... AND Platformen.Naam = ... in plaats van eerst de sleutels af te lezen!

  4. Maak de kolom voor de releasedatum verplicht via 0064__AlterReleases.sql, zodat nieuwe games altijd een releasedatum moeten krijgen. Doe dit door de kolom aan te passen naar DATE NOT NULL.

Hier komen geen nieuwe ideeën aan bod, maar je moet de eerdere stappen goed begrijpen om deze stappen te doorlopen.

Verdere soorten relaties

Tabellen kunnen meer dan twee entiteiten verbinden. Voor releases van games kan je bijvoorbeeld een spel, een uitgever en een platform aan elkaar linken met een M-op-N-op-K relatie. Dit is wel niet vaak nodig, dus denk altijd even goed na voor je dit doet. De keuze berust vooral op een goede analyse en goed overleg met de klant! In deze cursus zal je nooit een ternaire (d.w.z. tussen drie entiteiten) of hogere relatie nodig hebben. En, zoals eerder gezegd, gaan we niet verder in op het onderscheid tussen identificerende en niet-identificerende relaties.

Vergelijkingen

overzicht operatoren

Om waarden te vergelijken, maakt MySQL gebruik van enkele operatoren die erg lijken op wat je mogelijk kent uit andere programmeertalen:

Symbool

Omschrijving

=

Gelijk aan

<

Kleiner dan

>

Groter dan

<=

Kleiner of gelijk

>=

Groter of gelijk

<>

Verschillend

Voor "gelijk aan" heb je maar één = nodig, waar veel programmeertalen == gebruiken. Dat komt omdat = in die andere talen gebruikt wordt voor toekenning. In SQL is duidelijk uit de context dat je met = een vergelijking bedoelt.

operatoren in combinatie met collations

Voor getallen betekenen bovenstaande operatoren precies wat je zou verwachten. Je moet in het achterhoofd houden dat FLOAT en DOUBLE niet helemaal precies zijn, maar voor de rest betekenen ze wat je verwacht uit de wiskunde.

Voor tekst is het wat complexer. Je kan wel degelijk iets als het volgende schrijven:

USE ApDB;
SELECT Familienaam, Titel FROM Boeken WHERE Familienaam < 'B';

Dit SELECT statement retourneert alle rijen uit de tabel Boeken waarvan de Familienaam een waarde heeft die "kleiner" is dan 'B'. Wanneer is een string "kleiner" dan een andere string? Om op die vraag te antwoorden moet je eerst weten dat de namen in een bepaalde, gewoonlijk alfabetische volgorde (in het Engels collation order) worden gerangschikt. "Alle namen kleiner dan 'B'" wil dan zeggen "alle namen die na het sorteren voor 'B' komen."

De collation is een stel regels voor het vergelijken van tekens in een tekenset. De tekenset bepaalt dat A een symbool is dat je kan weergeven en bepaalt welke bytes nodig zijn om dit symbool voor te stellen. De collation legt vast dat "A" voor "B" komt en of "A" voor, na of op dezelfde plaats als "a" komt.

Als de familienaam Beth in je tabel voorkomt, wordt deze niet in de selectie opgenomen. Om ook die rij te selecteren, zou je kunnen denken om de expressie Familienaam <= 'B' te gebruiken:

USE ApDB;
SELECT Familienaam, Titel FROM Boeken WHERE Familienaam <= 'B';

Dit zal niet werken. Dat komt omdat we de boeken opvragen waarvan de familienaam van de auteur kleiner of gelijk is aan 'B'. De tekst 'Beth' is niet gelijk aan 'B' en is zeker niet kleiner! In het algemeen mag je dit onthouden: als een string A een prefix is van een string B, d.w.z. als string B letterlijk begint met de inhoud van A en dan nog meer tekst bevat, dan zal A altijd voor B gesorteerd worden. Zo wordt 'schoen' gesorteerd voor 'schoenmaker', omdat 'schoen' een prefix is van 'schoenmaker'. Maar er wordt éérst naar de letters gekeken en dan pas naar de lengte van de tekst. Zo wordt 'schoeisel' voor 'schoen' gesorteerd, omdat 'i' voor 'n' komt.

Ter verduidelijking: als we 'B' als familienaam toevoegen, wordt het resultaat wel geselecteerd:

USE ApDB;
INSERT INTO Boeken (Familienaam,Titel,Voornaam,Categorie)
VALUES
('B','Het Boek','Jef','Filosofie');
SELECT Titel, Familienaam FROM Boeken
   where Familienaam <= 'B';

Stel dat je alle titels wilt van de auteurs wilt waarvan de familienaam begint met een' A' of een 'B'. Dan moet je de WHERE clausule verfijnen. Je zou dit kunnen proberen:

USE ApDB;
SELECT Familienaam, Titel FROM Boeken where Familienaam <= 'Bz';

Maar hier zijn verschillende problemen mee. Als 'Bz' een prefix is van de naam van een auteur (niet erg waarschijnlijk, maar het zou kunnen), zal dit niet werken. Nog lastiger: dit is erg afhankelijk van de collation. Het is niet in elke collation zo dat 'Bz' voor 'Bé' komt, bijvoorbeeld. Sommige collations sorteren eerst de gebruikelijke 26 letters van het alfabet en pas daarna de letters met accenten.

Volgend script is beter (je mag er in het algemeen wel vanuit gaan dat 'B' voor 'C' komt):

USE ApDB;
SELECT Familienaam, Titel FROM Boeken where Familienaam < 'C'

Hoe weet je precies wat er met accenten en hoofdletters gebeurt? Dat hangt ervan af of de gebruikte collation hoofdlettergevoelig (case sensitive) en/of accentgevoelig (accent sensitive) is voor de kolom die je aan het beschouwen bent.

Probeer dit eens uit:

INSERT INTO Boeken (
   Voornaam,
   Familienaam,
   Titel,
   Categorie
)
VALUES
(
   'Emile',
   'Bréhier',
   'Cours de Philosophie',
   'Filosofie'
),
(
   'Andre',
   'Breton',
   'Nadja',
   'Roman'
);

SELECT Voornaam, Familienaam, Titel FROM Boeken
   where Familienaam <= 'Breton';

Als de collation voor Familienaam accentongevoelig is, zal je sowieso beide nieuwe records zien. Anders worden de zaken complexer. Dan moet je kijken naar het gebruikte collation algoritme en de accentgevoeligheid. Dan moet je bijvoorbeeld weten of de é pas ergens na de z komt of na de e maar voor de f. Als je de collation van de kolommen van de tabel Boeken te weten wil komen, kan je in MySQL Workbench rechtsklikken op de tabel in kwestie en dan via "Table Inspector" naar "Columns" gaan om de collation te achterhalen. Er zijn véél collations, maar onthoud vooral volgende vuistregels:

  • als ze _bin bevat (al dan niet met hoofdletters geschreven), worden de bytevoorstellingen van de karakters vergeleken

    • dit is zowat de strengste definitie van gelijkheid die je kan hebben

  • als ze _as bevat, is de collation accentgevoelig

  • als ze _ai bevat, is de collation accentongevoelig

  • als ze _cs bevat, is de collation hoofdlettergevoelig

  • als ze _ci bevat, is de collation hoofdletterongevoelig

Als je dus bijvoorbeeld enkel een boek van Breton wil, zonder boeken van "Bréton" of "breton", schrijf je:

SELECT * FROM Boeken WHERE Familienaam COLLATE utf8mb4_0900_as_cs = 'Breton';

Als je MySQL geïnstalleerd hebt zoals afgesproken, wordt standaard de tekenset utf8mb4 en de collation utf8mb4_0900_ai_ci gebruikt. Dus met de standaardinstellingen maakt het niet uit of je CHAR SET utf8mb4 toevoegt, maar het is beter expliciet te zijn dan te hopen dat jouw database nog volledig ingesteld is op standaardinstellingen.

LIKE

In veel gevallen weten we maar half wat we willen zoeken. We kennen een deel van de naam, het begin of het einde of iets tussenin. Met de LIKE operator kan je in SQL naar patronen zoeken (pattern matching). Je kan met de LIKE operator naar patronen in tekst zoeken. Je combineert de LIKE operator met jokers (wildcards) om een booleaanse expressie te vormen. De set van jokers bestaat uit symbolen die één of meer ontbrekende tekens voorstellen.

De LIKE operator vergelijkt een tekstuitdrukking in het linkerlid, dat eventueel een wildcard bevat, met een patroon tussen aanhalingstekens in het rechterlid. Een patroon is een veralgemeende tekstuitdrukking. d.w.z. een tekst waarin nog enige vrijheid bestaat.

De betekenis van de wildcards in SQL is als volgt:

  • %: nul, één of meer willekeurige tekens

  • _: exact één willekeurig teken

Om alle boeken te selecteren waarvan de familienaam van de auteur begint met B als de collation niet hoofdlettergevoelig is:

Om alle boeken te selecteren waarvan de familenaam van de auteur eindigt op een s:

Om boeken waarvoor in de titel het woord economie voorkomt, te selecteren:

De LIKE wordt door beginners vaak vergeten. Je komt soms dingen tegen als WHERE Voornaam = 'ma%ijs'. Dat zal geen resultaten opleveren, want niemand heeft letterlijk de voornaam "Ma%ijs".

UPDATE

basisprincipe

Soms maken we fouten bij het ingeven van data. Soms verouderen gegevens. In beide situaties willen we bestaande rijen wel bewaren, maar bepaalde kolomwaarden aanpassen. We kunnen het UPDATE statement hier voor gebruiken.

MySQL staat dit om veiligheidsredenen niet zomaar toe, maar voorlopig zijn we aan het verkennen. Om dit dus toch mogelijk te maken, moet je SET SQL_SAFE_UPDATES = 0 toevoegen voor een "onveilig" commando en achteraf SET SQL_SAFE_UPDATES = 1 toevoegen.

Bijvoorbeeld:

Dit zet de kolom Categorie van alle boeken op "Metafysica".

Je kan ook de inhoud van meer dan één kolom aanpassen. Dat zou je als volgt doen (maar hoef je niet uit te voeren of op te slaan):

Zonder verdere specificatie zet het eerste stukje code de kolom Categorie van alle rijen op 'Metafysica'.

verfijnd aanpassen

Het zou kunnen dat alle boeken in de categorie Metafysica thuishoren. En in dat geval doet het statement precies wat je er van verwacht. Maar meestal willen wie niet alle rijen op dezelfde manier aanpassen. We moeten dus in het UPDATE statement specifiëren in welke rij we de kolom Categorie willen updaten. We moeten één bepaalde rij eruit filteren. Dat doen we met de WHERE clausule. We beperken de update tot de records die we met een SELECT zouden laten zien:

De WHERE clausule bepaalt een voorwaarde die door de database als waar of vals geëvalueerd wordt. De voorwaarde Titel = 'Logicaboek' wordt voor elke rij in de tabel geëvalueerd. Als de evaluatie van de logische expressie voor een bepaalde rij waar oplevert wordt die rij geüpdatet.

De vergelijking van strings in MySQL is standaard niet hoofdlettergevoelig! Je zou dus wel eens rijen kunnen aanpassen zonder dat dat je bedoeling is.

samengestelde constructies

Je kan ook meerdere rijen in één keer updaten. Dat doe je door bijvoorbeeld de logische operator OR te gebruiken:

Waar je een nieuwe waarde instelt, mag je ook weer een expressie gebruiken die een waarde oplevert. Net als in programmeren. Je kan bijvoorbeeld een waarde instellen die berekend wordt door strings aan elkaar te hangen of een substring te bepalen:

Eén taak hoort bij één lid (en dus ook omgekeerd).
Eén user kan meerdere tweets hebben, elke tweet komt van exact één user.

Hoe je de tweets terug koppelt aan de juiste account, lees je ook bij de uitleg rond . We tonen hier alleen dat het mogelijk is. Koppel users aan de juiste tweets met dit script, 0058__SelectUsersTweets.sql:

Diagram voor een veel-op-veel relatie. Je tekent deze als twee één-op-veel relaties.

legt uit hoe je nu toont welke games op welk platform verschenen zijn. Dit wordt hier gedaan in 0061__SelectReleases.sql:

Kennisclip 1-op-1 relaties
Kennisclip 1-op-M relaties
Kennisclip N-op-M relaties
JOIN-operaties bij simpele relaties
JOIN-operaties bij simpele relaties
Kennisclip
USE ApDB;
SELECT Voornaam, Familienaam, Titel
FROM Boeken
WHERE Familienaam LIKE 'b%';
USE ApDB;
SELECT Voornaam, Familienaam, Titel
FROM Boeken
WHERE Familienaam LIKE '%s';
USE ApDB;
SELECT Voornaam, Familienaam, Titel, Verschijningsjaar
FROM Boeken
WHERE Titel LIKE '%economie%';
USE ApDB;
SET SQL_SAFE_UPDATES = 0;
UPDATE Boeken SET Categorie = 'Metafysica';
SET SQL_SAFE_UPDATES = 1;
USE ApDB;
UPDATE Boeken 
SET Categorie = 'Wetenschap', 
    Titel = 'Een boek';
UPDATE Boeken
SET Categorie = 'Wiskundige logica'
WHERE Titel = 'Logicaboek';
UPDATE Boeken
SET Categorie = 'Geschiedenis'
WHERE Familienaam = 'Braudel' or
      Familienaam = 'Bernard' or
      Familienaam = 'Bloch';
UPDATE Boeken SET Categorie = concat('CATEGORIE: ', Categorie);

Logische operatoren

We willen niet alleen rijen kunnen selecteren gebaseerd op één kolom die gelijk is aan een bepaalde waarde. We moeten ook complexere voorwaarden kunnen opstellen. Daarom beschikken over we een hele reeks operatoren die we bovendien kunnen combineren.

wat zijn booleaanse expressies?

Een expressie is een waarde of iets dat je kan uitrekenen om een waarde te krijgen. Een booleaanse expressie is een expressie met een uiteindelijke waarde TRUE, FALSE of (in SQL) NULL.

hoe booleaanse expressies opbouwen?

De bouwstenen zijn:

  • TRUE, FALSE en NULL

  • haakjes (voor groepering)

  • de logische operatoren AND, OR en NOT (en XOR)

  • en andere constructies die een voorwaarde uitdrukken (deze komen later aan bod)

Het gebruik van de ronde haakjes dient om de volgorde van de evaluatie van de operatoren te bepalen. Wat tussen haakjes staat, werk je eerst uit als één geheel, vooraleer je de onderdelen buiten deze haakjes uitrekent.

Voor de logische operatoren gebruiken we waarheidstabellen:

AND

TRUE

FALSE

NULL

TRUE

TRUE

FALSE

NULL

FALSE

FALSE

FALSE

FALSE

NULL

NULL

FALSE

NULL

OR

TRUE

FALSE

NULL

TRUE

TRUE

TRUE

TRUE

FALSE

TRUE

FALSE

NULL

NULL

TRUE

NULL

NULL

NOT

TRUE

FALSE

NULL

FALSE

TRUE

NULL

XOR

TRUE

FALSE

NULL

TRUE

FALSE

TRUE

NULL

FALSE

TRUE

FALSE

NULL

NULL

NULL

NULL

NULL

Om deze te onthouden, doe je er best aan NULL te zien als een onbekend resultaat. Als je niet zeker bent wat de uitkomst is omwille van de aanwezigheid van NULL, zal de uitkomst zelf ook NULL zijn.

Je kan de waarheidstabellen ook nabouwen in Workbench, bv.:

SELECT TRUE OR NULL;

SELECT sorteren

Het is niet genoeg gegevens uit een tabel te kunnen opvragen, je moet ze ook kunnen sorteren of ordenen. We willen lijsten in alfanumerieke volgorde weergeven omdat het gemakkelijker is in een geordende lijst te zoeken.

Om te sorteren gebruiken we de clausule ORDER BY. Het sorteren kan op 2 manieren:

  • oplopend

  • aflopend

In het Engels is dat ascending en descending. In SQL wordt dat afgekort naar ASC en DESC. Stel dat je de namen uit de tabel boeken wil opvragen gesorteerd op de voornaam, de familienaam en titel van de boeken. De query ziet er dan zo uit:

USE ApDB;
-- oplopend sorteren volgens familienaam
-- ascending
SELECT Voornaam, Familienaam, Titel FROM Boeken
   ORDER BY Familienaam ASC, Voornaam, Titel;
-- aflopend sorteren volgens familienaam
-- descending
SELECT Voornaam, Familienaam, Titel FROM Boeken
   ORDER BY Familienaam DESC, Voornaam, Titel

Het eerste veld in de ORDER BY clause wordt dan eerst bekeken, dan het tweede, enzovoort. Als niet aangegeven is of de sortering voor een bepaald veld oplopend of aflopend is, is ze standaard oplopend. Dat wil zeggen dat als je ASC en DESC niet vermeldt na een kolom, de code zich gedraagt alsof er ASC staat.

De eerste bovenstaande SELECT clausule toont dus eerst boeken in alfabetische volgorde van de familienamen van de auteurs. Als twee auteurs dezelfde familienaam hebben (althans volgens de gebruikte collation!), wordt hun voornaam gebruikt om de knoop door te hakken. Dan komt het boek met de voornaam die alfabetisch eerst komt ook eerst in de lijst. Als twee auteurs dezelfde naam hebben, of als het gewoon om twee boeken van dezelfde auteur gaat, wordt de titel gebruikt om de knoop door te hakken.

Voor de tweede query komen de familienamen met een 'Z' voor die met een 'A'. Let op: de voornamen met een 'A' komen wel voor de voornamen met een 'Z'!

Merk op uit bovenstaand voorbeeld: de volgorde van de kolommen in de ORDER BY clausule hoeft niet dezelfde te zijn als de volgorde van de weergave in de SELECT clausule. Het kan perfect zijn dat je wil sorteren op familienaam, maar wel eerst de voornaam wil tonen en dan pas de achternaam. Sterker nog: je kan zelfs ordenen op kolommen die je niet toont in de uitvoer.

SELECT met clausules

Het is niet voldoende om rijen te kunnen opvragen. Je moet ook in staat zijn om specifieke informatie op te vragen, statistische gegevens te genereren, enz. Dit kan door uitdrukkingen en extra clausules te gebruiken in SELECT statements.

Vorm

De algemene vorm van een SELECT statement is:

Hierbij heb je al redelijk wat flexibiliteit: je kan gewoon kolomnamen gebruiken als expressies; je kan functies toepassen op kolomnamen; je kan vaste tekst of getallen mee opnemen in de resultaten. Maar er zijn nog meer mogelijkheden. In het algemeen ziet het SELECT statement er zo uit:

Deze volgorde is vast: FROM komt altijd voor WHERE, enzovoort.

Hiermee kan je:

  • enkel bepaalde rijen weergeven

  • rijen met eenzelfde waarde voor een bepaalde eigenschap groeperen

  • statistische eigenschappen van de getoonde data afdwingen

  • de uitvoer sorteren

In de loop van deze cursus komen al deze clausules aan bod.

Verwerking

Hoewel bovenstaande volgorde bepaalt hoe je een query moet uitschrijven, is dat niet hoe een query wordt uitgevoerd. De verschillende onderdelen worden verwerkt als volgt:

  1. FROM

  2. WHERE

  3. GROUP BY

  4. HAVING

  5. ORDER BY

  6. SELECT

Waarschijnlijk ken je al deze clausules nog niet, maar dat is niet erg. Je kan terug kijken naar deze uitleg telkens je een nieuwe clausule leert. Het idee is als volgt:

  • eerst geef je aan waar je data vandaag komt

  • dan bepaal je, per record, of dat record mee verwerkt zal worden of niet

  • de behouden records kan je groeperen om informatie over een bepaald groepje te tonen

  • per groepje kan je opnieuw bepalen of dat groepje verwerkt zal worden

  • je legt een volgorde vast

  • je toont het eindresultaat

SELECT met WHERE

In de meeste gevallen zijn we niet geïnteresseerd om alle rijen uit een tabel te selecteren. We willen over de mogelijkheid beschikken om alleen de rijen, die aan een bepaalde voorwaarde voldoen, te kunnen selecteren.

De oplossing bestaat erin de WHERE-clausule te gebruiken. De WHERE-clausule bevat een booleaanse expressie. Dit is een expressie die als resultaat voor een bepaalde rij in principe TRUE of FALSE moet opleveren. In tegenstelling tot de meeste programmeertalen gebruikt SQL ook een derde mogelijke waarde: NULL. Deze waarde komt voor wanneer we niet kunnen bepalen of iets waar of niet waar is.

Om de WHERE te gebruiken, zet je hem na de FROM Tabel.

Bijvoorbeeld:

Ofwel (sla volgend script op als 0032__SelectBoeken.sql):

Je krijgt NULL wanneer je bijvoorbeeld vergelijkt met een niet-ingevulde waarde, ook geschreven als NULL. Zelfs NULL is niet gelijk aan NULL. Probeer maar eens alle boeken zonder titel op te vragen met een vergelijking (0033__SelectBoeken.sql):

Maar probeer ook deze eens (0034__SelectBoeken.sql):

Het ligt niet aan de data! Vergelijkingen met NULL via = en <> zijn zinloos! Als je wil controleren of de waarde in een bepaalde kolom ontbreekt, schrijf dan IS NULL in plaats van = NULL!

Functies

Functies in SQL staan je toe een waarde te berekenen in plaats van een vaste waarde of de waarde in een of andere kolom.

SUBSTRING

Deze functie gebruik je om een deel van een stuk tekst over te houden. Je kan bijvoorbeeld dit doen om de eerste twee letters van de familienaam van een auteur te tonen:

Je bent ook niet beperkt tot kolomwaarden, je mag ook gewone constanten gebruiken:

Je kan wel net zo goed dit doen:

Als je alleen het begin van een string wil, kan je ook LEFT gebruiken:

CONCAT

Deze functie gebruik je om stukken tekst aan elkaar te hangen. Je kan dus dit doen om de volledige naam van auteurs te tonen:

Je kan dit ook duidelijker laten weergeven in Workbench met:

LENGTH

Hiermee bereken je de lengte van een stuk tekst. Je kan bijvoorbeeld dit doen:

Dit zal je niet de familienaam van elke auteur tonen, maar wel het aantal letters in hun familienaam.

Net zo kan je dit doen:

Dan zal je als resultaat 3 krijgen.

Dit werkt voor het ASCII-alfabet, maar eigenlijk geeft LENGTH de lengte in bytes. Als je echt het aantal tekens wil, moet je CHAR_LENGTH gebruiken.

Wiskundige operaties

Ook standaard wiskundige operaties zijn functies. Bijvoorbeeld:

SELECT

Inspecteren van je data

Je kan in MySQL Workbench wel rechtstreeks naar je tabellen kijken, maar om gerichter te zoeken, moet je het commando kennen dat achter de schermen wordt uitgevoerd. Dat is het SELECT-commando.

de basis

De simpelste vorm van het SELECT commando toont gewoon alle data in een bepaalde tabel, bijvoorbeeld:

De asterisk is een "wildcard" of "joker". Ze geeft aan dat we alle kolommen willen zien. We kunnen ook kijken naar specifieke kolommen:

Tip: als je de namen van de kolommen niet uit het hoofd kent, typ dan eerst de naam van de tabel, gevolgd door een punt. Dan kan Workbench slim aanvullen:

data ordenen

Er zijn veel manieren om geselecteerde data te groeperen, maar de simpelste is ORDER BY. Als je dit toevoegt aan een SELECT-statement, kan je de rijen in een andere volgorde weergeven. Bijvoorbeeld:

Je kan ook een tweede (en derde,...) kolom gebruiken om knopen door te hakken:

De kolomnamen die je op deze manier gebruikt, hoeven niet getoond te worden in je resultaat.

Let op: de sorteervolgorde zal niet altijd overeenstemmen met wat je intuïtief verwacht. Ze hangt af van de gebruikte methode om stukken tekst te vergelijken, de zogenaamde "collation".

Er is ook een kortere, maar beperktere manier om te sorteren:

Dit sorteert eerst volgens de eerste kolom en dan volgens de tweede.

Labo

Oefeningen

Voor je deze oefeningen maakt, gebruik je onderstaand script om zeker te zijn dat je databank in de gewenste toestand is.

Zie lijn 1 van onderstaand script m.b.t. USE !!!

Sla dit op als 0509__CalibrateDB.sql in je map met scripts.

Deel 1

nummers

Schrijf een script dat volgende nummers toevoegt aan je tabel, Liedjes. Ze worden gegeven in het formaat "titel / artiest / album / jaar":

  • Stairway to Heaven / Led Zeppelin / Led Zeppelin IV / 1971

  • Good Enough / Molly Tuttle / Rise / 2017

  • Outrage for the Execution of Willie McGee / Goodnight, Texas / Conductor / 2018

  • They Lie / Layla Zoe / The Lily / 2013

  • It Ain't You / Danielle Nicole / Wolf Den / 2015

  • Unchained / Van Halen / Fair Warning / 1981

Sla op als 0510__Oefening.sql.

huisdieren

Doe hetzelfde voor een tabel Huisdieren, met volgende gegevens. Ze worden gegeven in het formaat "baasje / naam / leeftijd / soort":

  • Vincent / Misty / 6 / hond

  • Christiane / Ming / 8 / hond

  • Esther / Bientje / 6 / kat

  • Jommeke / Flip / 75 / papegaai

  • Villads / Berto / 1 / papegaai

  • Bert / Ming / 7 / kat

  • Thaïs / Suerta / 2 / hond

  • Lyssa / Фёдор / 1 / hond

Sla op als 0511__Oefening.sql.

nummers tonen

Schrijf een script, 0512__Oefening.sql, dat alle info over liedjes toont.

huisdieren tonen

Schrijf een script, 0513__Oefening.sql, dat de naam en soort van elk dier toont, maar niet de leeftijd of het baasje.

sorteren

Schrijf een script, 0514__Oefening.sql, dat (alleen!) de titels van liedjes toont, gaande van het oudste nummer tot het recentste nummer.

gedetailleerd sorteren

Schrijf een script, 0515__Oefening.sql, dat alle info over huisdieren toont, maar de huisdieren alfabetisch ordent volgens naam en, indien de naam dezelfde is, alfabetisch sorteert volgens soort.

omschrijvingen tonen

Schrijf een script, 0516__Oefening.sql, dat per dier een tabel met één kolom, een omschrijving, toont. Deze heeft volgende vorm, inclusief de juiste hoofding:

afronden

Wanneer je klaar bent, voer dan de gewoonlijke commando's uit om je scripts te delen:

Ze hierna je database terug zoals ze was na de theorieles met het script van het einde van vorige labosessie.

Deel 2

gedetailleerd filteren

Schrijf een script, 0517__Oefening.sql, dat alle nummers (alle kolommen) toont die uitgekomen zijn in een jaar dat eindigt op 1. Tip: gebruik WHERE in combinatie met een functie.

informatie toevoegen

Schrijf een script, 0518__Oefening.sql, dat volgende zaken doet:

  • een niet-verplichte kolom Geluid toevoegen aan de tabel met huisdieren, die tekst (tot 20 karakters uit het Engels) kan bevatten

    • tip: dit is een DDL-statement, dus kijk terug naar de DDL indien nodig

  • het geluid van alle honden aanpast naar "WAF!"

  • het geluid van alle katten aanpast naar "miauwww..."

nog informatie toevoegen

Schrijf een script, 0519__Oefening.sql, dat volgende zaken doet:

  • de niet-verplichte kolom Genre terug toevoegt aan de tabel met nummers (tot 20 karakters uit het Engels)

  • met maximum één UPDATE-operatie alle nummers van Led Zeppelin en Van Halen aanduidt als Hard Rock

wissen

Schrijf een script, 0520__Oefening.sql, dat alle nummers wist (maar de tabelstructuur bewaart).

meer wissen

Schrijf een script, 0521__Oefening.sql, dat alle papegaaien wist.

afronden

Wanneer je klaar bent, voer dan de gewoonlijke commando's uit om je scripts te delen:

Ze hierna je database terug zoals ze was na de theorieles met het script van het einde van vorige labosessie.

Kennisclip
SELECT expressie(s) waarin sleutelwoorden, namen en constanten zitten FROM Tabel
SELECT expression(s) waarin sleutelwoorden, namen en constanten zitten
[FROM Tabel]
[WHERE clause]
[GROUP BY clause]
[HAVING clause]
[ORDER BY clause]
USE ApDB;
SELECT Voornaam, Familienaam, Titel 
FROM Boeken
-- deze vergelijking levert TRUE of FALSE of NULL op
WHERE Familienaam = 'Augustinus';
USE ApDB;
SELECT Voornaam, Familienaam, Titel 
FROM Boeken
WHERE Voornaam = 'Diderik';
USE ApDB;
SELECT Voornaam, Familienaam, Titel 
FROM Boeken
WHERE Titel = NULL;
USE ApDB;
SELECT Voornaam, Familienaam, Titel 
FROM Boeken
-- <> betekent het omgekeerde van =
WHERE Titel <> NULL;
SELECT SUBSTRING(Familienaam,1,2) FROM Boeken;
SELECT SUBSTRING('Hallo',1,2);
SELECT 'Ha';
SELECT LEFT('Hallo',2);
SELECT CONCAT(Voornaam,' ',Familienaam) FROM Boeken;
SELECT CONCAT(Voornaam,' ',Familienaam) AS Naam FROM Boeken;
SELECT Length(Familienaam) FROM Boeken;
SELECT Length('abc');
SELECT 1 + Duurtijd FROM Nummers;
SELECT Duurtijd - 1 FROM Nummers;
USE ApDB;
SELECT * FROM Boeken;
USE ApDB;
SELECT Voornaam, Titel FROM Boeken;
SELECT Boeken.Voornaam, Boeken.Titel FROM Boeken;
USE ApDB;
SELECT * FROM Boeken ORDER BY Titel;
USE ApDB;
SELECT * FROM Boeken ORDER BY Voornaam, Titel;
USE ApDB;
SELECT * FROM Boeken ORDER BY 1, 2;
-- Gebruik i.p.v. ApDb de naam van jouw databank !!!
USE `ApDb`;
-- MySQL dump 10.13  Distrib 8.0.17, for Linux (x86_64)
--
-- Host: localhost    Database: ModernWays
-- ------------------------------------------------------
-- Server version    8.0.17

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

--
-- Table structure for table `Huisdieren`
--

DROP TABLE IF EXISTS `Huisdieren`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `Huisdieren` (
  `Naam` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `Leeftijd` smallint(5) unsigned NOT NULL,
  `Soort` varchar(50) NOT NULL,
  `Baasje` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Huisdieren`
--

LOCK TABLES `Huisdieren` WRITE;
/*!40000 ALTER TABLE `Huisdieren` DISABLE KEYS */;
/*!40000 ALTER TABLE `Huisdieren` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `Liedjes`
--

DROP TABLE IF EXISTS `Liedjes`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `Liedjes` (
  `Titel` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `Artiest` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `Jaar` char(4) DEFAULT NULL,
  `Album` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Liedjes`
--

LOCK TABLES `Liedjes` WRITE;
/*!40000 ALTER TABLE `Liedjes` DISABLE KEYS */;
INSERT INTO `Liedjes` VALUES ('John the Revelator','Larkin Poe','2017','Peach'),('Missionary Man','Ghost','2016','Popestar');
/*!40000 ALTER TABLE `Liedjes` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `Metingen`
--

DROP TABLE IF EXISTS `Metingen`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `Metingen` (
  `Tijdstip` datetime NOT NULL,
  `Grootte` smallint(5) unsigned NOT NULL,
  `Marge` float(3,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Metingen`
--

LOCK TABLES `Metingen` WRITE;
/*!40000 ALTER TABLE `Metingen` DISABLE KEYS */;
/*!40000 ALTER TABLE `Metingen` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

-- Dump completed on 2019-10-13 16:32:53

Omschrijving

Misty de hond

Ming de hond

Bientje de kat

Flip de papegaai

Berto de papegaai

Ming de kat

Suerta de hond

Фёдор de hond

git add --all
git commit -m "Scripts DML"
git push
git add --all
git commit -m "Scripts DML"
git push
5KB
0031__Calibratie.sql
Kennisclip
Kennisclip voor deze inhoud
Kennisclip voor deze inhoud

Aggregaatfuncties

Aggregaatfuncties lijken op de functies die je al kent (substring, concat, length, round). Het verschil is dat, als je een kolomnaam invult, de aggregaatfunctie op heel de kolom wordt toegepast. Anders gezegd, aggregaatfuncties worden niet binnen een rij toegepast, maar over meerdere rijen heen. Voor elke rij wordt er een waarde geproduceerd en een aggregaatfunctie gebruikt al deze waarden samen als invoer.

Op deze pagina geven we een overzicht van de belangrijkste aggregatiefuncties, maar er bestaan er nog.

calibratie

Voor deze voorbeelden gebruiken we volgende calibratiestap, die je moet opslaan als 0041__CreateHonden.sql:

USE ApDB;
CREATE TABLE Honden (
Naam VARCHAR(50) NOT NULL,
Leeftijd INT NOT NULL,
Geslacht ENUM('mannelijk','vrouwelijk') -- "er zijn maar twee mogelijkheden"
);
INSERT INTO Honden (Naam,Leeftijd,Geslacht)
VALUES
("Rose",1,"vrouwelijk"),
("Lacy",7,"vrouwelijk"),
("Phoebe",4,"vrouwelijk"),
("Camilla",14,"vrouwelijk"),
("Betsy",7,"vrouwelijk"),
("Lena",8,"vrouwelijk"),
("Ella",8,"vrouwelijk"),
("Samantha",15,"vrouwelijk"),
("Sophia",7,"vrouwelijk"),
("Abby",4,"vrouwelijk"),
("Lily",10,"vrouwelijk"),
("Biscuit",11,"vrouwelijk"),
("Nori",8,"vrouwelijk"),
("Sam",5,"vrouwelijk"),
("Mika",9,"vrouwelijk"),
("Baby",10,"vrouwelijk"),
("Blondie",14,"vrouwelijk"),
("Leia",10,"vrouwelijk"),
("Mackenzie",10,"vrouwelijk"),
("Trixie",11,"vrouwelijk"),
("Hannah",9,"vrouwelijk"),
("Kallie",5,"vrouwelijk"),
("Maya",14,"vrouwelijk"),
("Inez",15,"vrouwelijk"),
("Gemma",8,"vrouwelijk"),
("Priscilla",9,"vrouwelijk"),
("Zoe",8,"vrouwelijk"),
("Camilla",1,"vrouwelijk"),
("Fiona",9,"vrouwelijk"),
("Marley",11,"vrouwelijk"),
("Betsy",12,"vrouwelijk"),
("Bailey",8,"vrouwelijk"),
("Gia",5,"vrouwelijk"),
("Peanut",4,"vrouwelijk"),
("Fern",4,"vrouwelijk"),
("Tootsie",13,"vrouwelijk"),
("Summer",11,"vrouwelijk"),
("Gidget",3,"vrouwelijk"),
("Brandy",1,"vrouwelijk"),
("Peaches",1,"vrouwelijk"),
("Sophie",11,"vrouwelijk"),
("Cookie",14,"vrouwelijk"),
("Ivy",1,"vrouwelijk"),
("Mackenzie",10,"vrouwelijk"),
("Sammie",9,"vrouwelijk"),
("Sandy",8,"vrouwelijk"),
("Callie",12,"vrouwelijk"),
("Samantha",10,"vrouwelijk"),
("Lola",2,"vrouwelijk"),
("Angel",14,"vrouwelijk"),
("Edie",12,"vrouwelijk"),
("Diamond",5,"vrouwelijk"),
("Bonnie",1,"vrouwelijk"),
("Cinnamon",8,"vrouwelijk"),
("Ella",12,"vrouwelijk"),
("Brooklyn",14,"vrouwelijk"),
("Miley",7,"vrouwelijk"),
("Pebbles",5,"vrouwelijk"),
("Hazel",3,"vrouwelijk"),
("Peaches",7,"vrouwelijk"),
("Bean",6,"vrouwelijk"),
("Bianca",10,"vrouwelijk"),
("Brandy",9,"vrouwelijk"),
("Cleo",8,"vrouwelijk"),
("Sam",9,"vrouwelijk"),
("Precious",2,"vrouwelijk"),
("Star",13,"vrouwelijk"),
("Tessa",15,"vrouwelijk"),
("Callie",6,"vrouwelijk"),
("Daisy",15,"vrouwelijk"),
("Darlene",9,"vrouwelijk"),
("Madison",5,"vrouwelijk"),
("Biscuit",4,"vrouwelijk"),
("Lacy",8,"vrouwelijk"),
("Destiny",4,"vrouwelijk"),
("Olivia",6,"vrouwelijk"),
("Allie",15,"vrouwelijk"),
("Khloe",13,"vrouwelijk"),
("Dolly",14,"vrouwelijk"),
("Bonnie",6,"vrouwelijk"),
("Blossom",7,"vrouwelijk"),
("Jenna",14,"vrouwelijk"),
("Violet",12,"vrouwelijk"),
("Bean",13,"vrouwelijk"),
("Anna",12,"vrouwelijk"),
("Betty",12,"vrouwelijk"),
("Destiny",3,"vrouwelijk"),
("Nina",3,"vrouwelijk"),
("Tilly",14,"vrouwelijk"),
("Dana",10,"vrouwelijk"),
("Ruby",14,"vrouwelijk"),
("Fiona",3,"vrouwelijk"),
("Brutus",8,"mannelijk"),
("Nero",5,"mannelijk"),
("Otto",13,"mannelijk"),
("Rascal",9,"mannelijk"),
("Kane",1,"mannelijk"),
("Odie",9,"mannelijk"),
("Ralph",9,"mannelijk"),
("Tank",14,"mannelijk"),
("Taz",2,"mannelijk"),
("Kobe",5,"mannelijk"),
("Dodge",4,"mannelijk"),
("Aries",11,"mannelijk"),
("Ned",11,"mannelijk"),
("Alex",9,"mannelijk"),
("Bo",10,"mannelijk"),
("Eli",5,"mannelijk"),
("Porter",2,"mannelijk"),
("Duke",6,"mannelijk"),
("Carter",13,"mannelijk"),
("Casper",14,"mannelijk"),
("Brutus",2,"mannelijk"),
("Buddy",12,"mannelijk"),
("Barkley",9,"mannelijk"),
("Theo",5,"mannelijk"),
("Maverick",12,"mannelijk"),
("Buddy",9,"mannelijk"),
("Taz",5,"mannelijk"),
("Harvey",11,"mannelijk"),
("Scout",5,"mannelijk"),
("Rudy",13,"mannelijk"),
("Trapper",15,"mannelijk"),
("Buster",10,"mannelijk"),
("Rocco",4,"mannelijk"),
("Vinnie",1,"mannelijk"),
("Murphy",13,"mannelijk"),
("George",9,"mannelijk"),
("Milo",11,"mannelijk"),
("Kobe",2,"mannelijk"),
("AJ",3,"mannelijk"),
("Cash",11,"mannelijk"),
("Eli",6,"mannelijk"),
("Dane",9,"mannelijk"),
("Theo",13,"mannelijk"),
("Cash",7,"mannelijk"),
("Nelson",3,"mannelijk"),
("Luke",10,"mannelijk"),
("Harvey",4,"mannelijk"),
("Riley",6,"mannelijk"),
("Tyson",9,"mannelijk"),
("Gage",5,"mannelijk"),
("Iggy",2,"mannelijk"),
("Marley",7,"mannelijk"),
("Fritz",15,"mannelijk"),
("Bailey",14,"mannelijk"),
("Porter",3,"mannelijk"),
("King",10,"mannelijk"),
("Snoopy",10,"mannelijk"),
("Lewis",15,"mannelijk"),
("Levi",1,"mannelijk"),
("Leo",10,"mannelijk"),
("Vince",2,"mannelijk"),
("Trapper",13,"mannelijk"),
("Kobe",11,"mannelijk"),
("Simba",11,"mannelijk"),
("Zeus",3,"mannelijk"),
("Flash",15,"mannelijk"),
("Watson",6,"mannelijk"),
("Benji",3,"mannelijk"),
("Frankie",15,"mannelijk"),
("Dane",3,"mannelijk"),
("Finn",1,"mannelijk"),
("Coco",8,"mannelijk"),
("Bailey",11,"mannelijk"),
("Storm",11,"mannelijk"),
("Griffin",2,"mannelijk"),
("Zeus",13,"mannelijk"),
("Boomer",15,"mannelijk");

aggregaatfunctie: COUNT

COUNT krijgt als invoer een lijst rijen en zegt hoeveel niet-NULL elementen er in deze lijst zitten.

De simpelste toepassing is het tellen van al je records, zoals hier in 0042__SelectHonden.sql:

USE ApDB;
SELECT COUNT(Naam)
FROM Honden;

Dit levert slechts één resultaat, ongeacht hoe veel honden er in de database staan. Dit is anders dan bijvoorbeeld LENGTH(Naam). Die laatste zou één resultaat per hond geven. De reden is dat COUNT een aggregaatfunctie is. Ze verzamelt alle waarden in de gegeven kolom Naam, telt hoe veel dat er zijn en toont dat resultaat. Namen die twee keer voorkomen, worden twee keer geteld. Let op: COUNT telt geen NULL-waarden.

Soms wil je gewoon weten hoe veel rijen er in een tabel zijn. Dan gebruik je COUNT(*). Bijvoorbeeld:

USE ApDB;
SELECT COUNT(*)
FROM Honden;

Dit geeft hetzelfde resultaat, want Naam was toch een verplichte kolom.

aggregaatfunctie: SUM

Deze aggregaatfunctie krijgt een expressie en berekent voor de som van het toepassen van deze expressie voor elk record. Een eenvoudig voorbeeld van een expressie is hier (in 0043__SelectHonden.sql) een kolomnaam:

USE ApDB;
SELECT SUM(Leeftijd)
FROM Honden;

Merk op: hier zal je niet zomaar * in plaats van Leeftijd kunnen invullen. Je kan de som nemen van getallen, maar niet van om het even welk soort data. SUM werkt alleen met getallen.

Je kan wel iets algemener een expressie die een getal oplevert invullen, niet gewoon een kolomnaam. Dus je kan ook dit doen:

USE ApDB;
SELECT SUM(Leeftijd+1)
FROM Honden;

Wat levert dit dan? De gezamenlijke leeftijd van alle honden, verhoogd met het aantal honden. Niet het meest praktische voorbeeld, maar je moet kunnen zien waarom dit zo is.

aggregaatfuncties: MIN en MAX

Deze aggregaatfuncties krijgen een expressie en berekenen het minimum of het maximum voor het toepassen van deze expressie voor elk record. Een eenvoudig voorbeeld van een expressie is hier (in 0044__SelectHonden.sql) een kolomnaam:

USE ApDB;
SELECT MAX(Leeftijd)
FROM Honden;

Dit vertelt je wat de leeftijd van de oudste hond in het systeem is.

aggregaatfunctie: AVG

Deze aggregaatfunctie krijgt een expressie en berekent het gemiddelde voor het toepassen van deze expressie voor elk record. Een eenvoudig voorbeeld van een expressie is hier (in 0045__SelectHonden.sql) een kolomnaam:

USE ApDB;
SELECT AVG(Leeftijd)
FROM Honden;

combineren van aggregaatfuncties en niet-geaggregeerde expressies

Onder normale omstandigheden kan je geen combinatie maken van geaggregeerde resultaten en niet-geaggregeerde resultaten. Iets als het volgende gaat dus niet:

USE ApDB;
SELECT COUNT(*), Naam
FROM Honden;

Dit komt omdat je uitvoer nog steeds een tabel is, bestaande uit één aantal rijen en één aantal kolommen. Als je COUNT(*) zou toepassen, zou je één rij overhouden en als je Naam zou opvragen, zou je even veel rijen overhouden als er honden zijn. Dus als deze query zou werken, zou je op basis van de eerste kolom maar één rij mogen hebben en op basis van de tweede kolom zou je er meerdere hebben. Dat gaat niet. Beide kolommen moeten leiden tot hetzelfde aantal rijen.

SELECT met GROUP BY

Eerst hebben we een relationele databank vooral gebruikt om individuele records bij te houden en op te vragen. Via aggregaatfuncties hebben we informatie over alle records gebundeld. Soms zoeken we iets dat in het midden ligt: informatie die niet handelt over individuele records, maar over groepen records. Typisch zijn dit records met dezelfde waarde in bepaalde velden. De oplossing schuilt in een GROUP BY clausule.

basisidee

Met de GROUP BY operator kan je rijen "samenpersen" en de gewenste informatie uit de samengeperste rijen halen. Dit "samenpersen" gebeurt eerst, voor de gewenste informatie wordt geselecteerd. Beeld je in dat er een tussenliggende tabel wordt aangemaakt op basis van de tabel waarin je wenst te zoeken.

Veronderstel dat je onderstaande tabel Honden hebt:

In het eerdere calibratiescript staat veel meer data, maar voor een voorbeeld is het handiger minder data te tonen:

naam

leeftijd

geslacht

Ming

10

mannelijk

Swieber

14

mannelijk

Misty

7

vrouwelijk

Informatie die over groepen records gaat kan dan zijn: "hoeveel mannelijke honden zijn er in het systeem?" of "wat is de gemiddelde leeftijd per geslacht?" Deze vragen kan je niet meteen beantwoorden met de eerdere DML-commando's, maar wel met behulp van GROUP BY. Honden GROUP BY Honden.Geslacht moet je zien als een tijdelijke tabel die er als volgt uitziet:

namen per geslacht

leeftijd per geslacht

geslacht

[Ming,Swieber]

[10,14]

mannelijk

[Misty]

[7]

vrouwelijk

Je moet dit enkel zien als een hulpmiddel om over GROUP BY na te denken! Je kan deze tussenliggende tabel niet zomaar produceren. Meer uitleg volgt verderop.

De kolom na GROUP BY neemt geen nieuwe vorm aan, maar komt in de resultaten nog één keer voor per waarde. Er is dus precies één rij met de waarde "mannelijk" en één rij met de waarde "vrouwelijk". De andere kolommen veranderen eigenlijk van datatype: de kolom voor de naam bevat een sequentie van VARCHAR(50) per rij in plaats van een VARCHAR(50) per rij. De kolom voor de leeftijd bevat een sequentie van TINYINT in plaats een TINYINT per rij, wat hier aangegeven is door de verschillende waarden tussen rechte haakjes te zetten. Er is gekozen voor deze notatie omdat dit lijkt op het gebruik van lijsten in de meeste programmeertalen. De kolom voor het geslacht bevat nog steeds waarden uit een ENUM, omdat GROUP BY nu juist zo werkt dat er precies één waarde is voor elke rij.

Je kan bij gebruik van GROUP BY de data die in het voorbeeld tussen rechte haakjes staat (onder default omstandigheden) niet rechtstreeks tonen. Je kan er wel een aggregatiefunctie op toepassen. Als je wil weten wat de gemiddelde leeftijd per geslacht is, schrijf je dit (0046__SelectHonden.sql):

USE ApDB;
SELECT AVG(Leeftijd), Geslacht
FROM Honden
GROUP BY Geslacht;

Als je GROUP BY gebruikt, wordt een aggregatiefunctie dus niet meer over heel de kolom toegepast, maar per groep. Geslacht mag je wel tonen want daar heb je op gegroepeerd, dus die data staan niet tussen rechte haakjes.

Een speciaal geval is COUNT(*). Dit vertelt je hoe veel elementen er gegroepeerd zijn.

USE ApDB;
SELECT COUNT(*), Geslacht
FROM Honden
GROUP BY Geslacht;

De tussenliggende tabel is een hulpmiddel om hierover na te denken; volgende code zou niet werken:

USE ApDB;
SELECT Leeftijd
FROM Honden
GROUP BY Geslacht;

Ze levert: "Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'ApDB.Honden.Leeftijd' which is not functionally dependent on columns in GROUP BY clause". Het probleem is dus dat je data groepeert en dan geen aggregaat toepast op de data die in het voorbeeld hierboven tussen rechte haakjes wordt getoond.

uitbreiding naar meerdere kolommen

GROUP BY hoeft niet gevolgd te worden door één kolom, maar kan door meerdere kolommen gevolgd worden. In dat geval groepeer je records per unieke combinatie van kolomwaarden. Je kan bijvoorbeeld dit doen (0047__SelectHonden.sql):

USE ApDB;
SELECT COUNT(*), Geslacht, Leeftijd
FROM Honden
GROUP BY Geslacht, Leeftijd
ORDER BY Leeftijd, Geslacht;

Dit toont je hoeveel mannelijke en hoeveel vrouwelijke honden er zijn van elke leeftijd die in het systeem voorkomt. Er zijn na uitvoering van het calibratiescript bijvoorbeeld 6 vrouwelijke honden van 1 jaar oud en 4 mannelijke honden van 1 jaar oud. We kunnen ter controle ook dit even doen (0048__SelectHonden.sql):

USE ApDB;
SELECT COUNT(*), Leeftijd
FROM Honden
GROUP BY Leeftijd;

Dit toont ons dat er 10 (dus 6 vrouwelijke en 4 mannelijke) honden zijn van 1 jaar oud. Anders gezegd: hoe meer kolommen je vermeldt na GROUP BY, hoe meer onderverdelingen je zal zien.

handige aggregaatfunctie: group_concat

Eerder zeiden we: "Honden GROUP BY Honden.geslacht moet je zien als een tijdelijke tabel die er als volgt uitziet:"

namen per geslacht

leeftijd per geslacht

geslacht

[Ming,Swieber]

[10,14]

mannelijk

[Misty]

[7]

vrouwelijk

Je kan deze tabel niet echt op deze manier tonen, maar je kan ze wel bijna verkrijgen door middel van de group_concat (aggregaat)functie. Deze aggregeert alle waarden in één groep door middel van concatentie (met andere woorden: ze plakt ze allemaal achter elkaar).

Probeer maar eens volgende query: select group_concat(Naam), group_concat(Leeftijd), Geslacht from Honden group by Honden.Geslacht

SELECT met HAVING

Met de WHERE-clausule kon je voorwaarden uitdrukken voor het selecteren (of updaten of wissen) van records. Je kan met WHERE echter geen voorwaarden uitdrukken op kolommen die vermeld zijn in de GROUP BY-clause of op geaggregeerde waarden. Met andere woorden, dit gaat niet:

USE ApDB;
SELECT Geslacht
FROM Honden
GROUP BY Geslacht
WHERE AVG(Leeftijd) > 4;

Ook dit zal niet werken:

USE ApDB;
SELECT AVG(Leeftijd)
FROM Honden
GROUP BY Geslacht
WHERE Geslacht = 'mannelijk';

Dat komt omdat WHERE rij per rij bekijkt en beslist of die rij behouden of verworpen wordt.

Als je voorwaarden op gegroepeerde data wil uitdrukken, maak je gebruik van HAVING (0050__SelectHonden.sql) en (0051__SelectHonden.sql):

-- een voorbeeld met een gegroepeerde kolom
USE ApDB;
SELECT AVG(Leeftijd)
FROM Honden
GROUP BY Geslacht
HAVING Geslacht = 'mannelijk';
-- een voorbeeld met een geaggregeerde waarde
USE ApDB;
SELECT Geslacht
FROM Honden
GROUP BY Geslacht
HAVING AVG(Leeftijd) > 4;

In het kort: voor voorwaarden op kolommen in een GROUP BY of op geaggregeerde waarden gebruik je HAVING, voor andere voorwaarden gebruik je WHERE.

Er bestaan queries die je hetzelfde resultaat opleveren, of je nu WHERE of HAVING gebruikt. Dat betekent niet dat ze inwisselbaar zijn. Wat achter de schermen gebeurt, is wel degelijk verschillend. Soms zijn er gewoon verschillende werkwijzen die tot hetzelfde resultaat leiden.

IN

IN voor verkorte notatie

Soms wordt de SQL-basissyntax wat langdradig. Een voorbeeld daarvan is als je verschillende waarden tegelijk wil onderzoeken. Het kan bijvoorbeeld zijn dat je enkel in bepaalde honden geïnteresseerd bent, waarvan je de naam al kent. Bijvoorbeeld Bailey, Cookie, Lola, Iggy, Snoopy en Leo.

Een logge manier om hun gegevens op te vragen is deze, die je al kent:

We moeten steeds het gedeelte Naam = herhalen. Dit komt omdat de WHERE-clausule een booleaanse expressie moet bevatten.

Gelukkig is er een andere manier om sneller de gewenste booleaanse expressie te vormen (0052__SelectHonden.sql):

Je schrijft dus een lijst van alle mogelijkheden met ronde haakjes en met elementen die gescheiden worden door komma's. De IN-operator wil dan zeggen dat de gevraagde waarde voorkomt in de lijst. Dit doet hetzelfde als de eerdere code, maar kost veel minder typwerk.

Als we uitdrukkelijk rekening willen houden met hoofdletters en accenten, kunnen we ook hier de gebruikte collation aanpassen (0053__SelectHonden.sql):

De IN is niet specifiek gelinkt aan WHERE, maar is gewoon een andere operator om een booleaanse expressie te bekomen. Je kan dus ook dit doen (0054__SelectHonden.sql), met HAVING in plaats van WHERE:

Je kan zelfs gewoon dit doen:

Misschien vraag je je af of er ook een kortere notatie is voor pattern matching met LIKE. Min of meer, maar dit behandelen we niet in deze cursus. Het concept hierachter is een "reguliere expressie".

(ondertiteling beschikbaar)

(ondertiteling beschikbaar)

(ondertiteling beschikbaar)

In MySQL kan je in een HAVING-clausule ook verwijzen naar bepaalde andere kolommen, maar wordt dit afgeraden.

Kennisclip
Kennisclip
Kennisclip
volgens de MySQL-documentatie zelf
USE ApDB;
SELECT *
FROM Honden
WHERE Naam = 'Bailey'
OR    Naam = 'Cookie'
OR    Naam = 'Lola'
OR    Naam = 'Iggy'
OR    Naam = 'Snoopy'
OR    Naam = 'Leo'
USE ApDB;
SELECT * FROM Honden
WHERE Naam IN ('Bailey','Cookie','Lola','Iggy','Snoopy','Leo')
USE ApDB;
SELECT * FROM Honden
WHERE Naam COLLATE utf8mb4_0900_as_cs IN ('Bailey','Cookie','Lola','Iggy','Snoopy','Leo')
USE ApDB;
SELECT MAX(Leeftijd)
FROM Honden
GROUP BY Geslacht
HAVING MAX(Leeftijd) IN (13,15);
SELECT 5 in (3,7,9);

BETWEEN

Vaak zijn we geïnteresseerd in een specifieke deel data. Bijvoorbeeld alle personen in een bepaalde leeftijdscategorie. We kunnen dit al schrijven als volgt (in dit geval voor grote puppy's in plaats van personen):

USE ApDB;
SELECT *
FROM Honden
WHERE Leeftijd >= 1 AND Leeftijd <= 2;

Een beetje zoals bij de syntax die we hebben ingekort met IN zitten we hier met overbodig schrijfwerk. We kunnen dit oplossen met BETWEEN ... AND ... (0055__SelectHonden.sql):

USE ApDB;
SELECT *
FROM Honden
WHERE Leeftijd BETWEEN 1 AND 2;

Let op: hier is AND op zich geen booleaanse operator, maar BETWEEN ... AND ... is wel een tweedelige booleaanse operator. Met andere woorden, 1 AND 2 hierboven levert je geen booleaanse waarde op, terwijl de meeste voorkomens van AND dat wel doen. Omdat BETWEEN ... AND ... een booleaanse operator is, kan je ook dit doen:

SELECT 'c' BETWEEN 'a' AND 'e';

En je kan (zoals overal waar je een string gebruikt) de gebruikte collation wijzigen:

SELECT 'C' COLLATE utf8mb4_0900_bin BETWEEN 'a' AND 'e';

JOINS

In dit deel leer je data over verschillende tabellen heen te combineren. Er is nog veel meer te leren over de DML, maar dit is het meest gevorderde gebruik van de DML in deze cursus.

SELECT DISTINCT

Met GROUP BY kan je er voor zorgen dat bepaalde waarden worden samengenomen. Op die manier kan je bijvoorbeeld (voor de dataset met honden) elk geslacht één keer tonen. Dat is nuttig, want vaak zijn we niet geïnteresseerd in dubbels. We hebben echter gezien dat na groepering enkel gegroepeerde of geaggregeerde waarden kunnen tonen.

Er is een andere (en meer expliciete) manier om dubbels te vermijden: SELECT laten volgen door het sleutelwoordje DISTINCT. Hierdoor worden alle dubbele resultaten verwijderd. "Dubbel" wordt hier geïnterpreteerd als "gelijk volgens de collation van de kolom in kwestie".

Volgende query geeft je alle hondennamen (0056__SelectHonden.sql) precies één keer:

USE ApDB;
SELECT DISTINCT Naam
FROM Honden;

Na DISTINCT kan je schrijven wat je bij een gewone SELECT zou kunnen schrijven, dus dit gaat ook:

USE ApDB;
SELECT DISTINCT Naam, Geslacht
FROM Honden;

Als je ergens heel expliciet dubbele rijen wel wil weergeven, schrijf je ALL in plaats van DISTINCT. Dit is het standaardgedrag van MySQL, maar het kan nuttig zijn dit te schrijven om je collega-programmeurs duidelijk te maken dat je de dubbels echt wil bijhouden. Commentaar wordt niet zo aandachtig gelezen als code.

JOINs bij simpele relaties

CROSS JOIN

De "domste" manier om data uit meerdere tabellen te combineren tot data die uit één tabel lijkt te komen, is de "cross join". Deze "plakt" elke rij uit tabel A aan elke rij uit tabel B. Veronderstel bijvoorbeeld volgende tabellen voor taken en voor personen:

omschrijving

Id

bestek voorzien

1

frisdrank meebrengen

2

aardappelsla maken

3

voornaam

Id

Taken_Id

Yannick

1

2

Bavo

2

1

Max

3

3

Deze kunnen we op deze manier combineren en tonen:

SELECT *
FROM Taken
CROSS JOIN Leden
ORDER BY Taken.Id, Leden.Id;

Dit levert dan een resultaat dat er als volgt uitziet:

omschrijving

Taken.Id

voornaam

Leden.Id

Taken_Id

bestek voorzien

1

Yannick

1

2

bestek voorzien

1

Bavo

2

1

bestek voorzien

1

Max

3

3

frisdrank meebrengen

2

Yannick

1

2

frisdrank meebrengen

2

Bavo

2

1

frisdrank meebrengen

2

Max

3

3

aardappelsla maken

3

Yannick

1

2

aardappelsla maken

3

Bavo

2

1

aardappelsla maken

3

Max

3

3

Dit bevat nuttige informatie, maar ook rijen waar we niets aan hebben. De interessante rijen zijn die, die een persoon koppelen aan een taak. Dat zijn de rijen waarin Taken.Id gelijk is aan Taken_Id (afkomstig uit Leden).

Je kan dus personen koppelen aan hun taak via:

SELECT *
FROM Taken
CROSS JOIN Leden
WHERE Taken.Id = Taken_Id;

Hier moet je Taken.Id schrijven omdat zowel Taken als Leden een kolom Id hebben. Door de tabelnaam toe te voegen, maak je duidelijk over welke kolom het precies gaat.

INNER JOIN

Dit laatste voorbeeld werkt in MySQL, maar het wordt typisch anders geschreven. Meestal zal CROSS JOIN vervangen worden door INNER JOIN, terwijl WHERE vervangen wordt door ON. Wanneer we twee tabellen willen koppelen zodat samenhorende rijen uit tabel A en tabel B één nieuwe rij opleveren, zullen we deze conventie volgen.

Het resultaat zal er dus zo uitzien:

SELECT *
FROM Taken
INNER JOIN Leden
ON Taken.Id = Taken_Id;

Het resultaat is hetzelfde, maar in dit scenario wordt INNER JOIN verkozen. ON is ook geen synoniem voor WHERE, want het kan alleen gebruikt worden in een JOIN-statement.

Om precies te zijn: ON kan gebruikt worden in een JOIN-statement die geen CROSS JOIN-statement is. Er bestaan nog andere soorten JOINs dan CROSS en INNER, maar die zijn voor een latere cursus.

Labo

Zie apTunes project

Onderdeel "informatie voor de eigenaars"

Je kan DISTINCT ook gebruiken binnen sommige aggregatiefuncties. Bijvoorbeeld: COUNT(DISTINCT Naam) om het aantal verschillende namen te weten te komen. Over welke aggregatiefuncties het gaat, vind je terug.

Om de integriteit van de database te bewaren, normaliseren we de gegevens in de database. Door normalisering geraakt de informatie echter verspreid over meer dan één tabel. De gebruiker heeft daar echter niet veel aan. Primaire en vreemde sleutels zeggen de gebruiker niets. Anders gesteld, een tabel vol getallen zoals onderaan op is op zich niet leesbaar. We moeten dus een manier vinden om de informatie weer voor te stellen alsof die uit één tabel komt.

In MySQL is er eigenlijk tussen een CROSS JOIN met een WHERE clause en een INNER JOIN met een ON clause, die we dadelijk zullen bekijken. We kiezen voor de ene of de andere vorm om uit te drukken wat we bedoelen, maar het resultaat zal hetzelfde zijn.

hier
Kennisclip
de pagina over primary keys
geen verschil

JOINs via tussenliggende tabel

We weten wel hoe we leden aan taken kunnen koppelen door middel van INNER JOIN, maar die aanpak werkt niet wanneer er meer dan twee tabellen nodig zijn om de data terug te combineren, zoals in het geval van releases van games op platformen.

Om entiteiten gekoppeld via een M-op-N-relatie aan elkaar te koppelen, moet je eerst de informatie langs de M-kant koppelen met de tabel die de relatie voorstelt en vervolgens de informatie langs de N-kant koppelen.

Onze tabel met info over releases van games was:

Games_Id

Platformen_Id

1

1

1

2

1

3

2

1

2

2

2

3

3

1

3

2

4

1

4

2

4

3

4

4

We kunnen deze tonen met informatie over de game als volgt:

SELECT Games_Id, Platformen_Id, Titel
FROM Releases
INNER JOIN Games
ON Games_Id = Games.Id;

Dit voegt langs de rechterkant gewoon de details over de uitgebrachte game. Het is dus een meer uitgebreide versie van de tabel Releases. Daarom kunnen we dezelfde techniek herhalen en enkel de interessante info tonen:

SELECT Titel, Naam
FROM Releases
INNER JOIN Games
ON Games_Id = Games.Id
INNER JOIN Platformen
ON Platformen_Id = Platformen.Id;

Kennisclip

Een databank ontwerpen

We werken voortaan met het relationele databankmodel

Het is belangrijk dat wanneer een database wordt ontworpen er voldoende doordacht wordt nagedacht over de gewenste structuur van de te onwikkelen database.

Enkele aandachtspunten kunnen dan zijn:

  • Welke gegevens wenst u op te slaan?

  • Moeten die gegevens aan bepaalde criteria voldoen?

  • Waarvoor moeten die gegevens achteraf gebruikt worden?

  • ...

Enkele begrippen

Tabel __Gegevens worden opgeslagen in een tabel. Deze tabel heeft steeds een naam, of tabelnaam. Eén tabel bevat normaal gesproken samenhorende gegevens. (bv. personen, facturen, …) of entiteiten.

Record __Een tabel bestaat uit rijen of records. Eén record bevat alle gegevens van de zgn. rij. (bv. alle gegevens betreffende een persoon)

Kolommen __Iedere kolom omvat één aanduiding van een gegeven binnen een tabel. (bv. voornaam van een persoon, adres, enz...) Iedere kolom heeft uiteraard een unieke naam, of veldnaam.

Sleutels __Ieder record behoeft één veld (kolom) dat ervoor zorgt dat dat record uniek kan worden aangegeven. Dit veld noemt men de primaire sleutel. Bijvoorbeeld een persoons ID. Alleen de familienaam is waarschijnlijk niet voldoende. Daarentegen zouden we kunnen veronderstellen dat de combinatie van achternaam, voornaam, geboortedatum waarschijnlijk wel uniek is, maar dit is evenwel veel te omslachtig. Het is wel duidelijk dat de velden, hierboven aangehaald, een samengestelde of alternatieve sleutel vormen. Laten we volgend voorbeeld nemen, nl. de postcodes en gemeenten. Deze twee gegevens zijn samenhorend en het is niet echt zinvol om steeds de twee gegevens in een hele reeks tabellen te gaan bijhouden. Daarom kunnen we een afzonderlijke tabel aanmaken waarin de postcodes en gemeenten vervat zijn. Door dan in alle andere tabellen enkel de postcode bij te houden, kunnen we door die postcode perfect de gemeente opzoeken in de tabel met alle postcodes en gemeenten. Men noemt een dergelijke sleutel in een andere tabel dan die van de postcodes en gemeenten een vreemde sleutel.

Enkele aandachtspunten

Wanneer je bv. naam en adresgegevens wil bewaren in een database, mag je dit niet doen door gewoonweg in één veld de naam, voornaam, adres, postcode en gemeente in die volgorde te vermelden. De oplossing ligt er namelijk in dat je per gegeven een afzonderlijke kolom gaat voorzien.

Ook is het niet aan te raden om één veld voor evt. meerdere doeleinden te gebruiken, bv. het veld telefoonnummer gebruik je best niet voor de vermelding van een sis-nummer.

Het komt nog vaak voor dat de opbouw of normalisatie van een gegevensverzameling niet voldoende doordacht is. Volgend voorbeeld zal dit verduidelijken.

Voorbeeld

Een firma wil de gegevens betreffende zijn uitgeschreven facturen bijhouden in een database. Hiervoor zijn volgende gegevens noodzakelijk nl.

  • Factuurnummer: het nummer van de factuur

  • Factuurdatum: de datum van de factuur

  • Artikelnummer: de code van het betreffende artikel dat gefactureerd werd

  • Artikelomschrijving: de omschrijving van het gefactureerde artikel

  • Artikelprijs: de prijs van het gefactureerde artikel

  • Aantal: aantal eenheden van één artikel

  • BTW-percentage: het doorgerekende BTW-tarief uitgedrukt in percentage

  • Klantnummer: het nummer van de gefactureerde klant

  • Klantnaam: de naam van de klant vermeld op de factuur

  • Klantadres: het adres van de klant vermeld op de factuur

Bovenvermelde gegevens kunnen we dus in één tabel onderbrengen, hetgeen hetvolgende geeft.

Zoals je wel zal bemerken zijn er een aantal zgn. problemen, nl.

  • Er worden een aantal gegevens herhaald, nl. de factuurnummer, de factuurdatum, klantnummer, klantnaam en klanadres worden evenwel per gefactureerd artikel herhaald. Dit is dus een overtolligheid van gegevens.

  • Zonder dat er een factuur werd opgesteld kan geen nieuwe klant worden toegevoegd.

  • Wanneer we bijvoorbeeld de artikelomschrijving van een bepaald artikelnummer willen aanpassen, dienen we het hele bestand te doorlopen om deze aanpassingen door te voeren.

  • Wanneer we de inhoud van een bepaalde factuur zouden verwijderen (dit bv. ter annulatie), dan kan het zijn dat alle informatie van een klant mee wordt verwijderd. Bijvoorbeeld, wanneer we de inhoud van factuurnummer 112 zouden wissen, dan verdwijnt ook alle informatie aangaande de klant JANSSENS Jan.

Om ervoor te zorgen dat o.a. bovenstaande problemen worden opgelost, kunnen we de onderscheiden gegevens gaan spreiden over verschillende tabellen. Let wel, we moeten er wel voor zorgen dat er steeds geen verbanden tussen de onderliggende gegevens verloren gaat.

Zoals hierboven vermeld, bekomen we bij de uitsplitsing tot vier afzonderlijke tabellen. Niettegenstaande de uitsplisting in deze vier tabellen ontstaat er geen gegevensverlies. Geen gevensverlies, even verklaren, o.a.

  • bij tabel A is er een verband tussen de factuurnummer en de klantnummer. We weten hierbij dus perfect welke factuur voor welke klant bestemd is omdat we de informatie aangaande de

    betreffende klantnummer kunnen gaan opzoeken in tabel B en dit d.m.v. de klantnummer.

  • Ook weten we welke artikels er gefactureerd werden. In tabel D wordt de factuurnummer in combinatie met de artikelnummer vermeld en hierdoor weten we welk artikel(s) moet gefactureerd

    worden. De prijs van het artikel en omschrijving vinden we dan waar terug in tabel C, waarbij we door middel van het artikelnummer betreffend item kunnen opzoeken.

Zoals je wel zal bemerken zijn de problemen opgelost, nl.

  • Er worden geen gegevens meer herhaald per gefactureerd artikel . De overtolligheid van gegevens is dus opgelost.

  • Een nieuwe klant kan zonder problemen worden opgenomen in de tabel van de klanten (tabel B).

  • Wanneer we bijvoorbeeld de artikelomschrijving van een bepaald artikelnummer willen aanpassen, dienen we dat maar op één plaats door te voeren, nl. in de tabel van de artikelen (tabel C).

  • Wanneer we de inhoud van een bepaalde factuur zouden verwijderen (dit bv. ter annulatie), dan wordt de informatie van een bepaalde klant niet mee gewist.

UPDATE van JOIN

JOIN is niet alleen handig om informatie samen te tonen. Deze operatie kan ook gebruikt worden om aanpassingen door te voeren waarvoor we informatie uit een andere tabel nodig hebben.

Volgend script werd in de oefeningen gebruikt om nummers via een ID te linken aan een artiest, nadat artiesten in een aparte tabel waren geplaatst. De liedjes hebben dus nog géén ID, maar bevatten wel nog de naam van de artiest:

use ApDB;
set sql_safe_updates = 0;
-- "Nummers" betekent hier "Liedjes"
update Nummers inner join Artiesten
on Artiesten.Naam = Nummers.Artiest
set Nummers.Artiesten_Id = Artiesten.Id;
set sql_safe_updates = 1;

Eerst moet je dus kijken naar wat een gewone SELECT van deze samengevoegde tabel zou opleveren. Hij zou informatie over een nummer plaatsen naast informatie over een artiest met dezelfde naam.

Onthoud heel goed: het is niet omdat we vaak schrijven ON X.Id = X_Id dat dat de manier is om JOIN te gebruiken. Dit is niet meer of niet minder dan een vergelijking van de waarden in twee kolommen.

Voor uitvoering zien rijen uit de tabel met liedjes er dus zou uit:

Rijen uit de tabel met artiesten zien er zo uit:

Een combinatie van de twee tabellen kan je bekijken via (dus zoals in de update hierboven, maar de informatie wordt alleen getoond):

select * from Nummers inner join Artiesten
on Artiesten.Naam = Nummers.Artiest; -- niet op ID!

Deze ziet er zo uit:

Dus de kolom Artiesten_Id is nog leeg, maar we kunnen het volgende zeggen:

  • ze is afkomstig uit Nummers

  • de waarde die we wensen in te vullen is wel zichtbaar in deze JOIN, want ze staat in de kolom Id afkomstig uit Artiesten

We weten dus wat we zouden moeten invullen voor al deze NULL-waarden en we weten waar die info dan zou moeten worden opgeslagen. De UPDATE is dan ook toegelaten en werkt zoals gewenst.

Waarom een databank?

  • Compactheid: Normaal gesproken (zeer waarschijnlijk) neemt een database minder plaats in dan bijvoorbeeld al de gegevens op papier.

  • Snelheid: De gegevens worden in normale omstandigheden sneller aangepast/opgezocht/gewijzigd (o.a. door de gecentraliseerde opslag).

  • Up to date: Doordat de aanpassingen normaal minder werk vragen dan wanneer men nog zou spreken van een papieren opslag van gegevens, zullen deze gegevens sneller (en waarschijnlijk ook correcter) worden aangepast.

  • Uniek: Het is de bedoeling van een database om gegevens zo uniek mogelijk bij te houden. Anders gezegd, het is de bedoeling dat bepaalde gegevens slechts één keer worden bijgehouden in de database. Dit heeft uiteraard als bijzonder voordeel dat er in die gevallen zeker geen verwarring zal bestaan omtrent hun betekenis/waarde.

  • Gemeenschappelijk: Afhankelijk van een hele reeks factoren kunnen bepaalde of alle gegevens gemeenschappelijk zijn. Meerdere gebruikers kunnen er dus tegelijkertijd mee werken.

  • Veilig: Een database kan beveiligd worden, bv. enkel de DBA (database administrator) kan alles. De andere gebruikers zijn in bepaalde mate beperkt in hun toegang en mogelijkheden op de database; Aan de gewone gebruikers kunnen bijvoorbeeld enkel leesrechten worden toegestaan of beperkte schrijfrechten. Vaak gaat het dan nog enkel om schrijfrechten voor bepaalde gegevens.

  • Backup van de gegevens: Door het veilig stellen van gegevens op externe dragers (harde schijf, cassette,...) is het makkelijk een kopie te maken.

  • Standaardisatie: De voorstelling van gegevens ligt in veel soorten databanken vast. Het is dus niet aan de gebruiker om zelf een geschikt formaat te kiezen bijvoorbeeld een datum voor te stellen.

  • Integriteit: Dit door een controle op de invoer van gegevens in te bouwen. Een databank kan bijvoorbeeld vereisen dat een bepaalde waarde een getal voorstelt tussen 1 en 50, een datum, een stuk tekst van maximaal 100 karakters, enzovoort.

  • Relationele integriteit: Dit is een specifiekere vorm van integriteit. Doordat er bepaalde relaties (verbindingen/verbanden) bestaan tussen de onderliggende gegevens, kunnen deze verbanden gecontroleerd worden. "Relationele integriteit" betekent dat al deze verbanden gerespecteerd worden. Bijvoorbeeld: elke aankoop is gelinkt aan een klant; er is geen aankoop die niet door een klant is uitgevoerd.

Let op: er bestaan verschillende soorten databanken en deze hebben niet allemaal dezelfde voordelen in dezelfde mate. Maar in het algemeen kan je wel stellen dat dit voordelen zijn ten opzichte van een papieren administratie of een verzameling Office bestanden.

Kennisclip voor deze inhoud

ERM

Entity Relationschip Model

apTunes project

Relationele databases vormen regelmatig de ruggegraat van (web)applicaties. We zullen dat in de verdere labo-oefeningen demonstreren. Voor de verdere oefeningen zullen we voornamelijk werken met een database genaamd apTunes. Dit is een database die je zou kunnen gebruiken om een streamingdienst (vergelijkbaar met Spotify, Deezer,...) te ondersteunen.

apTunes moet allerlei functionaliteit ondersteunen die je gewend bent van echte streamingdiensten, onder meer:

  • voor de eindgebruiker:

    • zoeken op titel, artiest, genre, etc. van een nummer

    • bijhouden van een persoonlijke collectie nummers, albums, playlists

    • nummers opslaan als favorieten

    • enzovoort

  • voor de eigenaar:

    • de populairste nummers binnen een bepaalde periode opvragen

    • per artiest het aantal afgespeelde nummers in een bepaalde periode opvragen

    • enzovoort

Tegen het einde van de cursus ken je genoeg MySQL om de databasekant van een prototype van een dergelijke streamingdienst te schrijven.

We zeggen uitdrukkelijk "prototype". Je zal de nodige data kunnen bijhouden en opzoeken, maar in een echte applicatie moet je ook rekening houden met performantie, beveiliging en stabiliteit. Die zaken laten we voor later.

Het startpunt

We starten vanaf een voorstelling die we steeds beter en beter zullen maken:

Schrijf zelf code die deze twee tabellen aanmaakt en noem ze aptunes__0001.sql. Merk op dat de duurtijd uitgedrukt wordt in seconden.

Mogelijkheden verkennen

Voer je een foute instructie uit? Voer dan in volgorde elk script uit dat in het vet en cursief staat tot vlak voor het script waarbij het fout liep en corrigeer dan je script.

Als gevraagd wordt om "een nummer" of "alle nummers" te tonen, lees dan "alle kolommen uit de tabel Nummers" in hun oorspronkelijke volgorde.

  • Voer bovenstaand script uit en zet het in je map met scripts met de naam aptunes__0002.sql.

  • Schrijf een script, aptunes__0003.sql, dat alle nummers van Led Zeppelin in het systeem toont volgens titel.

  • Schrijf een script, aptunes__0004.sql, dat alle nummers van Ghostface Killah en alle nummers van Blues Pills samen toont, eerst volgens artiest en dan volgens titel. Dus eerst komen alle nummers van Blues Pills in alfabetische volgorde en dan alle nummers van Ghostface Killah in alfabetische volgorde.

  • Wis alle nummers die geen releasedatum hebben. Noem je script aptunes__0005.sql.

  • Schrijf een script, aptunes__0006.sql, dat alle nummers die geen klassieke muziek zijn toont. Bekijk de data om te achterhalen hoe je klassieke nummers kan herkennen.

  • Schrijf een script aptunes__0007.sql dat de titels van alle nummers toont die strikt minder dan 2 minuten duren en daarnaast ook toont hoe veel minder. Je zou dus iets als dit kunnen zien als "Kortjakje" 55 seconden duurt en "'k Zag twee beren" 40 seconden duurt:

Denk nu even na over een aantal zoekopdrachten die je met dit model nog niet zou kunnen doen. Noteer ze voor jezelf. Bespreek tijdens het labo klassikaal.

Structuur verbeteren

Voor onze streamingdienst is het handiger te werken met een vaste set van genres. Anders krijgen we zo veel subgenres dat het niet meer handig is genres te gebruiken. Vandaar volgende wijziging:

  • Voeg met script aptunes__0008.sql een kolom GenreEnum toe. Deze is voorlopig niet verplicht en kan alleen volgende waardes bevatten: Klassiek, Pop, Jazz, Metal, Rap, Electro, Folk, Wereldmuziek, Blues, Rock. Het is niet de bedoeling dat je de oude kolom Genre verwijdert of dat je de nieuwe kolom al invult! Verderop zullen we vertalen van de oude kolom naar de nieuwe!

  • Vul met script aptunes__0009.sql de kolom GenreEnum in. Doe dit door gebruik te maken van de LIKE operator om volgende "vertalingen" toe te passen:

    • alles met de (hoofdletterongevoelige) substring "klass" in de oude kolom wordt "Klassiek" in de nieuwe kolom

    • alles dat eindigt op de (hoofdletterongevoelige) substring "rock" in de oude kolom wordt "Rock" in de nieuwe kolom

    • "rap" en "hiphop" (hoofdletterongevoelig) worden gegroepeerd onder "Rap"

    • alles met het woord "metal" (hoofdletterongevoelig) in wordt "Metal"

    • alles dat eindigt op "blues" (hoofdletterongevoelig) wordt "Blues"

  • Test met script aptunes__0010.sql of alle nummers nu een (nieuw) genre hebben door de nummers zonder genre te selecteren.

  • Verwijder met script aptunes__0011.sql de oude kolom Genre en hernoem GenreEnum naar Genre. Maak hierbij het nieuwe genre ook een verplichte kolom.

We merken ook dat het niet erg nuttig is om een exacte datum bij te houden per nummer. We willen alleen het jaar. Je kan het jaar uit een datum halen door er de YEAR-functie op toe te passen.

  • Maak met script aptunes__0012.sql een kolom ReleaseJaar aan, vul deze automatisch in op basis van de releasedatum die je al hebt, maak de nieuwe kolom verplicht en verwijder ten slotte de kolom ReleaseDatum. Dit zal niet werken voor de nummers van Debussy, omdat ze te oud zijn om met het YEAR-datatype voor te stellen. Vul daarom het jaar van de uitvoering in voor deze nummers: 1985.

Functionaliteit toevoegen

Nu willen we weten wat de prijs is die de artiest verdient (in eurocent) wanneer een nummer gestreamd wordt. We zullen dit bijhouden met een kolom Royalties. Deze heeft type TINYINTen is unsigned. Er is een vuistregel voor het vastleggen van royalties, maar voor sommige nummers is er een speciale regeling. Omdat er alleen in gehele bedragen in eurocent wordt gewerkt, heb je de functie ROUND nodig om een getal af te ronden.

  • Voeg met script aptunes__0013.sql deze kolom toe. Vul ze in als volgt:

    • Klassieke nummers krijgen normaal 1 eurocent per 60 seconden, dus je stelt de royalties in als de duurtijd gedeeld door 60 (afgerond).

    • Rocknummers en metalnummers krijgen 1 eurocent per 20 seconden.

    • Rap krijgt 1 eurocent per 15 seconden.

    • Nummers van Led Zeppelin krijgen 1 eurocent per 10 seconden.

  • Er is een wijziging in de prijsstructuur! Verhoog met script aptunes__0014.sql alle royalties met 20%. Rond opnieuw af om een geheel bedrag te krijgen.

Informatie voor de eigenaars

De eigenaars van onze streamingdienst willen statistieken over de artiesten.

Gebruik eerst volgend script aptunes__0015.sql om meer data in het systeem te plaatsen:

Nu er redelijk wat data is, moeten we die gaan samenvatten eerder dan rij per rij te bekijken.

  • Schrijf een script aptunes__0016.sql dat toont hoe veel rocknummers er in het systeem zijn.

  • Schrijf een script aptunes__0017.sql dat voor elk genre toont hoe veel nummers er zijn. Het formaat van de uitvoer is als volgt en de genres zijn gesorteerd volgens de volgorde van de enum die de genres voorstelt:

  • Toon het releasejaar van het oudste nummer in het systeem. De uitvoer is één rij met één kolom. De titel van deze kolom maakt niet uit. Noem je script aptunes__0018.sql.

  • Herschrijf volgende query zonder DISTINCT zodat je toch nog hetzelfde resultaat krijgt en noem je script aptunes__0019.sql: SELECT DISTINCT Artiest FROM Nummers;

  • Toon alle nummers die minstens 5 minuten duren volgens titel. Noem je script aptunes__0020.sql.

  • Toon alle genres waarvan de nummers gemiddeld minstens 5 minuten duren, in de volgorde die is vastgelegd voor de genre enum. Noem je script aptunes__0021.sql.

  • Toon per decennium het aantal uitgebrachte nummers, maar enkel als dat aantal hoger ligt dan 450. Om dit klaar te spelen, moet je afronden tot het dichtste tiental. Dat kan je doen met TRUNCATE(getal,-1), bijvoorbeeld TRUNCATE(1973,-1) levert 1970. Je output moet ook stijgen volgens het decennium. Je resultaat zou er bijvoorbeeld zo moeten uitzien:

  • Het blijkt dat erg lange klassieke nummers niet erg winstgevend zijn voor onze dienst. Toon daarom alfabetisch alle artiesten die klassieke nummers hebben, maar enkel als hun klassieke nummers ook gemiddeld langer dan 8 minuten duren. Noem je script aptunes__0023.sql. Tip: afhankelijk van hoe je dit doet, heb je de meeste of zelfs alle clausules nodig.

Normalisatie van de apTunes databank

De apTunes database, zoals ze ontwikkeld is met scripts 1 tot 23, is niet erg efficiënt ontworpen. We zullen ze herstructureren met relationele concepten.

We willen ons ontwerp verbeteren, zodat we uiteindelijk volgende relaties krijgen:

Op deze figuur staat "Liedjes" in plaats van "Nummers". De eerstvolgende opdrachten gebruiken nog "Nummers", maar dat wordt verderop aangepast.

Dit ERD toont alleen de entiteittypes en de relatietypes. De precieze velden ervan moet je zelf kunnen afleiden uit de informatie waarvan je vertrekt.

We zullen eerst de 1-op-N relaties voorstellen.

  • Maak een script dat een tabel Artiesten aanmaakt voor artiesten. Noem dit aptunes__0024.sql. Zorg ervoor dat elke artiest die we toevoegen automatisch genummerd wordt.

  • Maak een script om de data over artiesten in te vullen in deze tabel. Je mag veronderstellen dat twee artiesten met dezelfde naam dezelfde artiest zijn.

    • Hiervoor kan je in een INSERT query het gedeelte VALUES (...) vervangen door een subquery waarvan de resultaten overeenstemmen met de values die je zou invullen. Met andere woorden: INSERT INTO EenTabel (IngevuldeKolom1, IngevuldeKolom2) (select KolomWaarde1, KolomWaarde2 from EenAndereTabel); Noem je script aptunes__0025.sql.

  • Maak een script om Nummers te voorzien van een foreign key kolom die verwijst naar de tabel Artiesten. Volg hierbij alle afspraken rond foreign keys! Voorlopig kan je deze niet verplicht maken. Noem je script aptunes__0026.sql.

  • het script om de artiesten te linken hoef je op dit moment niet te begrijpen, maar krijg je hieronder (aptunes__0027.sql)

  • het script om de verwijzing naar een Artiest verplicht te maken en kolom Artiest uit Nummers te verwijderen is aptunes__0028.sql

  • het script om Albums te maken is aptunes__0029.sql (schrijf je zelf, moet alleen info zuiver over het album bevatten die nu in Nummers staat)

  • het script om data te migreren naar Albums is aptunes__0030.sql (schrijf je zelf)

  • het script om Albums te voorzien van een foreign key waarmee je naar de artiest verwijst is aptunes__0031.sql (schrijf je zelf)

  • het script om de albums te linken aan artiesten krijg je hieronder (aptunes__0032.sql)

  • het script om gebruikers toe te voegen krijg je hieronder (aptunes__0033.sql)

Veel-op-veel (M-op-N) relaties

Vanaf hier spreken we over "Liedjes" in plaats van "Nummers". "Nummers" werd verwarrend omdat we ook getallen ("nummers") als sleutelwaarden gebruiken.

Fris het vorige deel op indien je ergens niet kan volgen, want deze leerstof bouwt rechtstreeks voort op de vorige. We werken hier verder naar het ontwerp volgens het ERD.

Voer eerst het calibratiescript op DigitAP uit.

  • Het script om GebruikerHeeftAlbum toe te voegen noem je aptunes__0035.sql. Naast de sleutelkolommen voorzie je ook een kolom DatumToevoeging om het tijdstip toe te voegen waarop dit album toegevoegd is aan de bibliotheek. Dit is een verplichte datetime.

  • Het script om de data toe te voegen (aptunes__0036.sql) vind je hieronder terug.

  • Het script om LiedjeOpAlbum toe te voegen noem je aptunes__0037.sql. Het tracknummer hou je bij in een kolom TrackNummer, het past in een int.

    • In deze tabel sla je volgende informatie op met een eigen script aptunes__0038.sql. (Om dit te doen zoek je met de hand de Id van het nummer en van het album op in hun tabellen en INSERT je hun combinatie in de nieuwe tabel LiedjeOpAlbum. De werkwijze is dus dezelfde als in script 36.)

      • Het nummer met titel Stairway to Heaven is het vierde nummer op het album Led Zeppelin IV

      • Het nummer met titel Problem Child is het tweede nummer op het album met titel Let There Be Rock

  • Het script om GebruikerHeeftLiedje toe te voegen noem je aptunes__0039.sql. Je hebt ook een kolom Favoriet nodig (van type int) om bij te houden of een nummer een favoriet nummer van die gebruiker is. Als het een favoriet nummer is, vul je 1 in, anders 0.

    • In deze tabel sla je volgende informatie op met een eigen script aptunes__0040.sql. De werkwijze is dezelfde als eerder.

      • tuneBoY5 heeft het nummer Little Sun, maar het is geen favoriet

      • musicfan111 heeft het nummer Eat the Rich en het is een favoriet

Joins

  • Toon de titel en de artiest van alle nummers. Maak hierbij gebruik van een inner join. Noem dit script aptunes__0041.sql.

  • Toon deze zelfde gegevens nu enkel voor nummers waarvan de titel begint met de letter "A". Noem dit script aptunes__0042.sql.

  • Toon de titel van elk album naast de naam van de artiest. Noem dit script aptunes__0043.sql.

  • Toon hoe veel nummers Led Zeppelin heeft. Noem dit script aptunes__0044.sql.

  • Toon de titel van elk nummer naast het Id van het album waarop dat nummer staat. Noem dit script aptunes__0045.sql. Hiervoor heb je de tabel NummerOpAlbum nodig. Je hebt nog maar één join nodig.

  • Toon per Id van een album hoe veel nummers er op dat album staan. Je hoeft nog niet de titel te tonen van het album. Noem dit script aptunes__0046.sql. Je kan dit zonder join.

  • Toon elk nummer naast de titel van het album waarop het nummer staat. Noem dit script aptunes__0047.sql. Nu heb je twee joins nodig.

  • Toon hoe veel albums elke gebruiker heeft. Noem dit script aptunes__0048.sql. Je moet dus naast de naam van elke gebruiker een getal zien staan (het aantal albums dat hij heeft).

  • Toon alle combinaties van een gebruiker en een album in de collectie van die gebruiker. Toon hierbij ook de datum waarop de gebruiker het album heeft aangekocht. Noem dit script aptunes__0049.sql. Je moet dus een gebruikersnaam, titel en datum naast elkaar zien.

  • Toon alle combinaties van gebruikers en favoriete losse nummers. In deze dataset heeft maar één gebruiker een favoriet los nummer, maar je query zou ook moeten werken als er veel meer data is. Noem dit aptunes__0050.sql. Je moet dus een gebruikersnaam en liedjestitel naast elkaar zien.

Het entity-relationship-model of entity-relationship diagram (ERD) is een of voor het inzichtelijk maken van een . Het is een visuele weergave van de , en regels die gelden of aanwezig zijn in een logisch . Deze diagrammen worden gemaakt bij het ontwerpen van een om inzicht te krijgen in de benodigde informatie en de verbanden tussen de gegevens.

Decennium
Aantal nummers
model
diagram
conceptueel datamodel
entiteiten
relaties
ontwerp
informatiesysteem
https://nl.wikipedia.org/wiki/Entity-relationshipmodel

titel

(wat hier staat is niet belangrijk)

Kortjakje

65

'k Zag twee beren

80

Genre

(titel maakt niet uit)

Genre 1

(aantal nummers in genre 1)

Genre 2

(aantal nummers in genre 2)

...

...

1970

...

1990

...

2000

...

Oefeningen 0015 t/m 0023
1KB
aptunes__0002.sql
aptunes__0002.sql
245KB
aptunes.sql
174B
aptunes__0027.sql
aptunes__0027.sql
236B
aptunes__0032.sql
aptunes__0032.sql
417B
aptunes__0033.sql
12KB
aptunes__0036.sql
428KB
calibratiejoins.sql

Constraints (voorwaarden, beperkingen)

Cardinaliteit Er zijn vier soorten:

  • één op één

  • één op n

  • n op één

  • n op n

Attributen die horen bij een één op n-, of bij een n op één-relatie, kunnen, indien gewenst, verschoven worden naar de entity set aan de n-kant van de relationship set. Bij een n op n-relatie kan een attribuut dat bij de relatie hoort, niet verschoven worden naar één van de entity-sets. Bij een één op één relatie kan men een attribuut verschuiven naar keuze verschuiven naar een deelnemende entity set.

Existence dependency

De "ondergeschikte" entiteit moet steeds horen bij een "dominante" entiteit.

  • Voorbeeld: de entity set lening en de entity set afbetalingen. Elke afbetaling moet bij een concrete lening horen.

  • Gevolg: als we een lening uit de database verwijderen, moeten we alle afbetalingen voor die lening mee verwijderen. Het is wel mogelijk om een bepaalde afbetaling te verwijderen, zonder de lening waarvoor ze bedoeld was, mee te verwijderen.

  • Gevolg: de deelname van de entity set afbetalingen aan de relatie leningen-afbetalingen, is "totaal". De deelname van de entity set leningen aan de relatie leningen-afbetalingen is "partieel": het is mogelijk een lening te hebben waarvoor (nog) geen afbetalingen gebeurd zijn.

Vormen van integriteit

Constraints dwingen verschillende vormen van "integriteit" af.

Cardinaliteit en existence dependencies worden in de eerste plaats gelinkt aan "referentiële integriteit". Dit wil zeggen dat entiteiten op een correcte manier naar elkaar verwijzen. Het is om referentiële integriteit te bewaren dat een database management systeem niet toestaat dat je een vreemde sleutelwaarde ingeeft die verwijst naar een niet-bestaande entiteit. Let op: het is vaak wel mogelijk om een vreemde sleutelwaarde NULL te gebruiken.

Breder dan alleen referentiële integriteit is data-integriteit. Dit wil zeggen dat de data geen ongeldige waarden bevat. Niets kan je volledig beschermen tegen foutieve invoer, maar (onder andere) volgende types checks bieden een betere data-integriteit:

  • checks op NULL

  • checks op toegelaten waarden

Er zijn nog specifieke vormen van data-integriteit naast referentiële integriteit. Deze worden behandeld in de cursus Databanken.

Discussiepunten bij ontwerp in ERM

Keuze tussen entity set of attribuut

  • voorbeeld: neem de entity set "employee" en het attribuut "telephonenumber". Men kan evengoed twee entity sets maken, "employee" en "telephone" met daartussen een relationship set.

  • "Telephone" kan dan bovendien extra attributen krijgen (plaats, type, kleur,...).

  • Voordeel van afzonderlijke entity set:

  • aantal is niet naar onder of boven begrensd

  • telefoon kan gemakkelijker toegewezen worden aan verschillende personen tegelijk

  • eenvoudig toevoegen van extra attributen

  • Voordelen van attribuut: eenvoud

  • Opmerking: een zwakke entity set is steeds equivalent met een samengesteld, herhalend attribuut van de overeenkomstige sterke entity set.

Keuze tussen entity set of relationship set

  • Voorbeeld: een lening kunnen we zien als een entity set op zich, maar ook als een relationship set tussen klanten en filialen, met ln_nr en bedrag als attributen van de relationship set.

  • Nadelen: als de relationship set geen 1 op 1-relatie is, en attributen heeft, dan ontstaat duplicatie van gegevens.

  • In het geval een begrip duidelijk twee entiteittypes verbindt, maar ook eigen attributen bezit, wordt gekozen voor een "associatieve entiteit". Een voorbeeld hiervan kan bijvoorbeeld "lesmoment" zijn, wanneer er entiteittypes voor leerkrachten en voor vakken zijn. Een lesmoment verbindt een leerkracht en een vak, maar voegt toe op welk tijdstip de leerkracht in kwestie het vak geeft.

Keuze tussen binaire of meervoudige relationship sets

Een relationship tussen meer dan twee entity sets kan steeds herleid worden tot meerdere relationship sets die binaire zijn (dwz. tussen twee entity sets).

Herleiden van ERD's tot tabellen

Elk E-R Diagram kan steeds herleid worden tot een aantal tabellen. Deze herleiding kan de basis vormen van het ontwerp voor een relationele database.

Strong (sterke) entity sets

  • De namen van de enkelvoudige attributen vormen de titels voor de kolommen in een tabel.

  • Elke rij is de voorstelling van een entity uit de entity set, met in elke kolom de waarde voor het betreffende attribuut.

  • Berekenbare attributen schrappen we.

  • Samengestelde attributen splitsen we eerst op in enkelvoudige attributen.

  • Herhalende attributen plaatsen we in een aparte tabel. We zullen deze tabel van een vreemde sleutel voorzien om naar de tabel voor het entiteittype te verwijzen.

  • De attributen die de sleutel van de entity set vormen, worden ook de sleutel voor de tabel.

Wanneer we SQL schrijven, zullen we typisch een eenvoudig sleutelattribuut Id voorzien omdat dat makkelijker werkt, maar wanneer we met ERD's bezig zijn, proberen we eerst een sleutel te vormen aan de hand van een verzameling attributen.

Weak (zwakke) entity sets

  • Als kolomtitels nemen we de attributen van de zwakke entity set, aangevuld met (de attributen van) de primaire sleutel van de bijhorende sterke entity set.

  • Samengestelde en herhalende attributen handelen we af zoals eerder.

Relationship set

  • Als kolomtitels nemen we de attributen van de sleutels van de participerende entity sets, aangevuld met de attributen van de relationship zelf.

  • Om botsingen te vermijden gebruiken we de naam van de participerende entitye set als prefix, bijvoorbeeld Klant_Nummer en Bank_Nummer in een relatie KlantBij.

Overbodige tabellen

De tabel die de relatie voorstelt tussen een sterke en een zwakke entity set, is reeds vervat in de tabel die de zwakke entity set voorstelt. We kunnen die tabel weglaten.

Tabellen combineren

  • Als de cardinaliteit van een relationship set 1 op n is, kunnen we de tabel die de relationship set voorstelt weglaten, door de sleutel van de tabel aan de 1-kant op te nemen als foreign key in de tabel aan de n-kant.

  • Als de cardinaliteit van een relationship set 1 op 1 is, kunnen we kiezen in welke tabel we een foreign key opnemen.

  • Eventuele attributen van de relationship set zelf moeten mee opgenomen worden in de tabel waar de foreign key geplaatst wordt.

  • Als de relatie recursief was (d.w.z. tussen entiteiten van hetzelfde type, bijvoorbeeld om uit te drukken dat een persoon baas is over een andere persoon), moet de titel van de foreign key-kolom verwijzen naar de rolnaam.

  • Totale participatie: dan mag de foreign key niet onbepaald (NULL) zijn.

Berekenbare attributen

Berekenbare attributen moeten we schrappen.

Samengestelde attributen

Samengestelde attributen moeten we opsplitsen in enkelvoudige.

Herhalende attributen

Voor een herhalend attribuut maken we een nieuwe tabel aan, bestaande uit de primaire sleutel van de oorspronkelijke entity set en het herhalende attribuut.

Specialisatie/generalisatie

Er is geen "beste", algemene oplossing. Men kan wel verschillende strategieën volgen:

  • Eén tabel voor de algemene entity set, met enkel de algemene attributen; per gespecialiseerde entity set een nieuwe tabel bestaande uit de sleutel van de algemene entity set, en de gespecialiseerde attributen.

    • Op deze manier kan men niet garanderen dat de specialisatie gescheiden of totaal is.

  • Eén tabel met kolommen voor alle attributen die voorkomen; praktisch zal dit veronderstellen dat er een bepaald attribuut bestaat dat aanduidt tot welke gespecialiseerde entity set een entiteit behoort.

    • Nadeel: er kunnen veel onbepaalde waarden zijn, en men moet hun aanwezigheid programmatorisch afdwingen.

  • Eén tabel voor elk van de gespecialiseerde entity sets, zonder tabel voor de algemene entity set; de tabellen van de gespecialiseerde entity sets bevatten naast de gespecialiseerde attributen, alle algemene attributen.

    • Dit kan alleen als de specialisatie totaal is.

    • Wanneer de specialisatie niet gescheiden is, treedt redundantie op van de gegevens uit de algemene entity set.

    • Voordeel: alle gegevens over een entiteit zitten samen in één tabel.

Bibliografie

Vandenbulcke, J. en Lemahieu, W. O., Databasesystemen voor de praktijk. Zevende druk, Den Haag, Ten Hagen en Stam, 2000, 559 p.

Ullman, Jeffrey D., Principles of Database Systems. Rockville, Computer Science Press, 1982.

Silberschatz, Abrahan; Korth, Henry; Sudanshar, S., Database System Concepts. New York, McGraw-Hill 1996.

Gillenson, Mark L., Fundamentals of database management systems, John Wiley & Sons. Nederlandse vertaling bij Academic Service, 2004.

Van Der Lans, Rick, Introduction to SQL. Addison-Wesley, 2000. Kent, William, ‘A simple guide to five normal forms in relational database theory’. Computing Practices, February 1983, vol. 26, Nr. 2.

Keys (Sleutels)

Sleutel van een entity set

  • Elke entity set moet een verzameling attributen hebben die toelaat om een entity uit de entity set uniek te onderscheiden.

  • De kleinste verzameling van attributen waarmee dit kan, noemen we de "sleutel".

  • Er kunnen meerdere sleutels mogelijk zijn, we noemen die dan "kandidaat-sleutels".

  • We kiezen dan een "primaire sleutel" uit deze kandidaat-sleutels.

In SQL-databanken wordt typisch een eenvoudig numeriek sleutelattribuut Id toegevoegd. Dit neemt weinig ruimte in beslag, is makkelijk automatisch toe te kennen en uit te schrijven.

Sleutel van een relationship set

  • Een relatie tussen twee entity sets wordt uniek bepaald door de sleutels van elk van de twee entiteiten waartussen de relatie ligt. Voorbeeld: (klantnummer, rekeningnr) is een sleutel voor de relatie "houder van" tussen klanten en rekeningen.

  • Indien we een één op n-relatie hebben, volstaat de sleutel van de entity set aan de n-kant

  • Als we een één op één-relatie hebben, volstaat de sleutel van een van beide entity sets naar keuze.

  • De sleutel van een relationship set wordt niet aangeduid in het diagram.

Weak (zwakke) entity sets

  • Als een entity set onvoldoende attributen heeft om een sleutel te vormen, noemen we de entity set "zwak".

  • Dit kan alleen als de zwakke entity set ondergeschikt is aan een dominante sterke entity set.

  • De kleinste verzameling attributen die toelaat om onderscheid te maken tussen de verschillende ondergeschikte entiteiten van dezelfde dominante entiteit, noemen we de "discriminator".

  • De sleutel van de zwakke entity set kan dan gevormd worden door de primaire sleutel van de sterke entity set, en de "discriminator" van de zwakke entity set.

  • Opmerking:existence dependency leidt niet automatisch tot een zwakke entity set.

Een voorbeeld is een entity type Hotelkamer met een kolom Nummer. Nummer op zich kan niet als sleutel dienen. Een entiteit van dit type kan enkel uniek geïdentificeerd worden wanneer ze gelinkt wordt aan een hotel.

Basisbegrippen

Entity, Entity Set, Extension

  • Entity (entiteit): een reëel "ding" of "object", dat we kunnen onderscheiden van alle andere dingen of objecten.

  • Entiteiten kunnen concreet zijn (personeelslid, klant, product,...) of abstract (lening, afspraak, op te nemen verlofdag,...)

  • Een "entity set" (letterlijk vertaald: verzameling van entiteiten, misschien beter: "entiteitstype") beschrijft alle mogelijke entiteiten van dezelfde soort.

  • De concrete samenstelling van een entity set zal regelmatig wijzigen (personeelsleden bij of weg, klanten

  • Voorstelling:

Attributes, Values, Domain

  • Een entiteit wordt voorgesteld door een verzameling "attributes", beschrijvende eigenschappen, die gedeeld worden door alle entiteiten uit de gegeven entity set.

  • Het feit dat alle entiteiten een zelfde attribuut hebben, wijst op informatie van hetzelfde type (gewicht, kleur, personeelsnummer,...). Maar elke entiteit kan wel een andere "value" (waarde) hebben voor dit attribuut (65Kg, groen, INF7089,...).

  • De toegelaten values voor een gegeven attribuut uit een entity set, vormen het "domain" (domein).

  • Attributen kunnen samengesteld zijn. Bv. adres kan als een samengesteld attribuut gezien worden.

  • Attributen kunnen herhalend zijn. Bv. telefoonnummer, persoon ten laste,...

  • Null value. Dit is een speciale waarde, gebruikt voor "onbekend" of "niet toepasbaar". De null is verschillend van een 0 (nul) of een lege string ("").

  • Berekenbaar attribuut: bv. als het attribuut geboortedatum bestaat, kan het attribuut leeftijd berekend worden uit geboortedatum en huidige datum.

  • Voorstelling:

Relation, Relationship set

  • Een relation is een verband tussen verschillende entiteiten. bv. de relatie "ontlener" tussen de entiteit "Janssens" uit de entity set klanten en de entiteit lening nr 0001 uit de entity set leningen.

  • Een relationship set (relatie) is de verzameling van alle gelijkaardige relaties tussen entiteiten uit dezelfde entity sets.

  • In bovenstaande relatie speelt Janssens de rol van "lener" en 0001 de rol van "lening". Rollen zijn alleen echt van belang bij relaties van een entity set naar zichzelf, "recursieve" relaties.

  • Voorbeeld: de relatie "is manager van" van de entity set "personeel" naar zichzelf.

  • We moeten hier een onderscheid kunnen maken tussen de entiteit die de rol van manager heeft, en de entiteit(en) die de rol van "gemanaged personeelslid" hebben. Ander voorbeeld: de relatie "is een onderdeel van" in de entity set "wisselstukken".

  • Een relatie kan eveneens attributen hebben.

  • Voorbeeld: de relatie "houder" tussen "klanten" en "rekening" kan het attribuut "laatste verwerking" hebben, met de datum van de laatste opname of storting.

  • Relaties hoeven niet steeds binair te zijn, dwz. tussen twee entity sets. Er zijn ook relaties mogelijk tussen drie, vier,... entity sets. Dit noemt men de "graad" van een relationship set.

  • Voorstelling:

Labo oefeningen

Let op! Deze tool ondersteunt verschillende notaties! Gebruik de icoontjes vanaf dat voor "Entity" tot en met dat voor "Recursive Relationship (M:N)". Deze notatie lijkt sterk op die in [dit voorbeeld op Wikipedia], maar gebruikt stippellijnen om entiteittypes en relatietypes te verbinden (met als uitleg "optional participation") wanneer het voorbeeld op Wikipedia gewone lijnen gebruikt. Het gebruikt volle lijnen waar het voorbeeld op Wikipedia dubbele lijnen gebruikt 😩.

Op draw.io trek je een stippellijn tussen een entiteittype en een relatietype als een entiteit niet hoeft deel te nemen aan een relatie. Bijvoorbeeld: regio in een game hoeft geen personages te bevatten, want het kan zijn dat alle spelers in een andere regio zitten. Langs de andere kant bevindt elk personage zich altijd wel in een regio, dus je trekt op draw.io een volle lijn tussen het entiteittype voor personages en het relatietype dat personages en regio's verbindt.

Hogeschool

Afspraken:

  • Alle tekstattributen (voornamen, familienamen, namen van vakken en opleidingen) definieer je als VARCHAR(100).

  • De semesters definieer je als TINYINT UNSIGNED.

  • Alle attributen op dit schema zijn verplicht.

  • Gebruik de onderlijnde attributen als primary keys. Studentennummer en personeelsnummer mag je als automatisch ophogende INT definiëren; de sleutelattributen Naam definieer je als tekst met daarop de constraint PRIMARY KEY. In dit geval hoef je de primary key dus niet Id te noemen.

  • Vreemde sleutels volgen dezelfde afspraken als eerder.

Noem het script dat deze tabellen toevoegt 0564__Oefening.sql.

Vakantieclub

Teken een entity relationship diagram (ERD) voor volgende situatie.

Vakantieclub SummerParks wil zijn klanten regelmatig mailen met promoties en informatie over vakantieverblijven, en dit gericht op het voorkeurprofiel van de klant.

  1. Van elke klant houden ze naam, voornaam, adres, telefoon en een uniek klantennummer bij.

  2. Van elk vakantieverblijf houden ze naam, regio, prijscategorie en profielen (sportief, kindvriendelijk, cultureel, ...) bij. Er is ook een unieke code per vakantieverblijf.

  3. Van elke klant houden ze ook bij welk profiel zijn voorkeur heeft.

Omdat je wil dat de profielen voor klanten en vakantieverblijven in overeenstemming zijn, maak je best een entiteit voor profielen.

Reservaties

Teken een entity relationship diagram (ERD) voor volgende situatie.

  1. In een bedrijf moeten personeelsleden regelmatig vergaderingen organiseren. Hiervoor zijn een aantal lokalen voorzien. Men moet deze vooraf reserveren. Men kan ook bepaalde apparatuur (beamers, overheadprojectoren, laptops, geluidsinstallatie,...) lenen.

  2. Elk lokaal heeft een uniek lokaalnummer en een omschrijving. Per lokaal houden we verder ook bij voor hoeveel personen het maximaal geschikt is, en in welke bedrijfsvestiging het lokaal gelegen is.

  3. Elk apparaat heeft een uniek nummer en een omschrijving. We houden ook het serienummer van de producent bij en een aankoopwaarde.

  4. Personeelsleden hebben een uniek personeelsnummer, een naam en voornaam, en een telefoonnummer. We houden ook bij op welke dienst ze werkzaam zijn.

  5. Het secretariaat wil voor elke reservatie bijhouden wie welk lokaal en welke apparatuur gereserveerd heeft, de datum met begin- en eindtijdstip, en een korte motivatie.

  6. Voor apparatuur moet de ontlener bovendien een borg moet betalen bij het secretariaat. De borg is voor elk afzonderlijk apparaat. Het secretariaat ontvangt en registreert de borg. Bij terugbrengen van het apparaat krijgt men de borg terug. Ook die terugbetaling wordt geregistreerd.

Je kan zelf kiezen of je een entiteittype gebruikt om een bedrijfsvestiging voor te stellen. In een grotere applicatie lijkt dat aangewezen, maar als oefening is het diagram al groot genoeg... Idem voor een dienst waarop personeel werkt.

draw.io ondersteunt geen notatie voor overerving, maar die zou hier wel van pas kunnen komen. Je kan, om dit op te lossen, een "note" element toevoegen aan je diagram en zeggen welke entiteittypes specifieke gevallen zijn van andere entiteittypes.

Voorbeeld

Informatisering van een bank

  • Onze bank heeft verschillende filialen, één per stad. Elk filiaal heeft een unieke naam. De bank houdt van elk filiaal bij, welke eigen bezittingen het heeft.

  • Klanten van de bank hebben een uniek nummer. De bank bewaart volgende gegevens over haar klanten: naam, adres.

  • Klanten kunnen rekeningen openen bij de bank, of leningen afsluiten.

  • Sommige klanten hebben een persoonlijke contactpersoon bij die bank.

  • Personeelsleden bij de bank hebben een uniek nummer. De bank bewaart volgende gegevens over haar personeelsleden: naam, adres, telefoon, namen van de personen ten laste, manager van het personeelslid, aanvangsdatum van tewerkstelling, ancieniteit, filiaal van tewerkstelling.

  • Er zijn twee soorten rekeningen, zichtrekening en spaarrekening.

  • Elke rekening kan meerdere houders hebben, en een klant kan meerdere rekeningen hebben.

  • Elke rekening heeft een uniek rekeningnummer. De bank bewaart per rekening volgende gegevens: saldo en datum van laatste verrichting per rekeninghouder.

  • Voor een spaarrekening wordt ook de interestvoet bewaard, voor een zichtrekening het maximum krediet.

  • Een lening wordt steeds toegestaan door een bepaald filiaal. Meerdere klanten kunnen samen een lening afsluiten, en één klant kan meerdere leningen hebben.

  • Een lening wordt gekenmerkt door een uniek nummer. Per lening bewaart de bank volgende gegevens: geleend bedrag en aflossingen. Elke aflossing heeft een uniek volgnummer, althans voor de lening waarvoor ze dient. Een aflossing van een andere lening kan hetzelfde volgnummer hebben. Per aflossing bewaart de bank volgende gegevens: datum, bedrag.

Aanvullende begrippen

Specialisatie en generalisatie; overerving van attributen

  • Voorbeeld: zichtrekening en spaarrekening. Deze hebben de gemeenschappelijke attributen van elke rekening (rekeningnummer, saldo, datum laatste verrichting) maar ook een specifiek attribuut: krediet en interestvoet. Dit noemen we "specialisatie". Het omgekeerde proces is generalisatie.

  • De entity sets zichtrekening en spaarrekening "erven" de attributen van de entity set rekeningen.

Voorlopig moet je specialisatie en generalisatie enkel op het conceptuele niveau kennen. Dat wil zeggen dat je deze zaken in een ERD moet kunnen noteren, maar je moet ze niet kunnen voorstellen in een tabelstructuur. In het algemeen spring je ook best spaarzaam om met overerving.

Design constraints

  • attribuut-bepaald: een entity kan tot een specialisatie behoren op basis van een attribuut in de veralgemeende entity set, bv. het attribuut "rekeningstype", dat de waarde "zicht" of "spaar" kan hebben.

  • user-bepaald: de gebruiker kan zelf een entity indelen in de specialisatie naar keuze.

  • gescheiden of overlappend: het kan zijn dat een entiteit slechts tot één gespecialiseerde entity set mag behoren, of tot meerdere tegelijk. Het is bijvoorbeeld niet mogelijk dat een rekening tegelijkertijd een spaarrekening én een zichtrekening is, maar dat wil niet zeggen dat dit voor andere entiteittypes ook altijd zo is.

  • totaal of partieel: het kan zijn dat een entiteit moet behoren tot een gespecialiseerde set, maar het kan ook zijn dat dit niet hoeft.

Aggregatie

Aggregatie in een ERM houdt in dat een relatie tussen twee entiteittypes wordt behandeld als een entiteit op zich. Aggregatie wordt oa. gebruikt om relaties tussen relaties te leggen.

Dit is een n op n relatie, het attribuut filiaal hoort dus bij de relationship set, en kan niet verplaatst worden naar een van de entity sets.

Door aggregatie kunnen we het attribuut filiaal verzelfstandigen tot entity set.

In deze laatste afbeelding is er een associatieve entiteit Lening aanwezig.

Om zelf tekeningen te maken, kan je gebruik maken van . Kies bij het aanmaken voor een blanco diagram en kies dan het onderdeel "Entity Relation".

Zet onderstaand ERD om naar databasetabellen volgens .

Entity Relationship Diagram voor de hogeschool.
draw.io
de procedure in de cursus

NoSQL

Studiemateriaal (essentieel)

Inleiding

Soorten databanken

Hiërarchische databank

  • Hebben een boomstructuur

  • Dit geeft redelijk wat beperkingen.

    • Een parent kan meerdere children hebben

    • Maar een child slechts 1 parent.

    • Dit leidt tot kopieën en problemen in integriteit

  • Steeds minder gebruikt

  • Vb: windows registry

Relationele databank

  • Bestaat uit tabellen die met elkaar gekoppeld worden dmv relaties

  • Relaties worden gemaakt door ‘sleutels’(keys) Primary Key(PK)

    Elke tabel heeft 1 PK (deze identificeert een entry)

    Foreign Key(FK) Elke tabel kan 0 of meerdere FK hebben

  • Deze legt een relatie naar een PK in een andere tabel

  • Niet bepaald overzichtelijker, maar wel veel flexibeler en efficiënter

  • Relationele databanken hebben enkele nadelen Schaalbaarheid voor heel grote databanken Opsplitsen van DB in verschillende DB’s werkt moeilijk Complexe SQL code omdat objecten in OO niet overeenkomen met tabellen in DB Bv. personeel(object) heeft adres en bedrijfswagen waarbij de data verspreid zijn over verschillende tabellen

NoSQLdatabank

Er zijn 4 soorten no-sql databanken:

  • Document databank Bv. MongoDB

  • Key Value databank Bv. Redis

  • Graph databank Bv. Neo4J

  • Column databank Bv. Google bigtable

NoSQL – Document databank

  • Database bevat ‘documenten’ (json objecten) Bv.: { Id:1, Name: ‘Janssens’ FirstName:’Jan’}

  • We kunnen queries schrijven, maar niet in SQL Query taal afhankelijk van databank Bv.: db.Klant.Find(Name:’Janssens’)

  • Documenten zullen veel data bevatten Bv. in aankoop db zullen alle aankopen in klanten document zitten en niet in aparte tabellen

  • Als server maximale capaciteit bereikt gewoon een extra server Geen schaal problemen omdat er geen relaties zijn over databanken

NoSQL – Key-value databank

  • Vergelijkbaar met document databank Maar geen queries mogelijk Enkel informatie opvragen via de sleutel(Key) Dit gaat bijzonder snel

  • Bv.: Twitter geschiedenis

NoSQL – Graph databank

  • Ontworpen om ‘sociale eigenschappen’ van personen op te slaan, bvb: persoon a is bevriend met persoon b Persoon a houd van product x

  • Zeer handig voor sociale media

NoSQL – (wide) Column databank

  • Gebruikt tabellen, rijen en kolommen, maar anders dan een relationele databank.

  • De namen en formaat van de kolommen kan verschillen van rij tot rij in binnen dezelfde tabel.

  • Er wordt ook wel eens gesproken over een twee dimenionale key-value store.

NoSQL Vaak worden combinaties gebruikt van meerdere soorten DB’s en dit afhankelijk van het doel.

Extra info Tweakers – NoSQL, maar wat is het dan wel?

Volgende pagina’s geven ook een inzicht over wat en waarom.

Blockchain

Dit is geen te kennen leerstof.

Join

Situatie

Om de integriteit van een database te bewaren, normaliseren we de gegevens in de database. Ruwweg betekent dit dat we tabellen splitsen in kleinere, zinvollere tabellen om ontdubbeling te vermijden. Door de normalisering geraakt de informatie echter verspreid over meer dan één tabel. De gebruiker heeft daar echter niet veel aan. Primaire - en vreemde sleutels zeggen de gebruiker niets. We moeten dus een manier vinden om de informatie weer voor te stellen alsof die uit één tabel komt.

Oplossing

  1. INNER JOIN: wanneer we gebruik gaan maken van de INNER JOIN zullen de records uit beide tabellen worden gehaald welke aan elkaar zijn verbonden.

  2. LEFT [OUTER] JOIN: met deze clausule kunnen we alle records ophalen uit de tabel die aan de linkerkant van de JOIN staat gespecificeerd, dus ook de rijen uit de linkse tabel die niet aan de join voorwaarde voldoen worden ook in het resultaat weergegeven.

  3. RIGHT [OUTER] JOIN: met deze clausule kunnen we alle records ophalen uit de tabel die aan de rechterkant van de JOIN staat gespecificeerd, dus ook de rijen uit de rechtse tabel die niet aan de join voorwaarde voldoen worden ook in het resultaat weergegeven.

  4. FULL [OUTER] JOIN: met deze clausule kunnen we alle records ophalen uit de tabel die zowel aan de linkerkant en aan de rechterkant van de JOIN staan gespecificeerd, dus ook de rijen uit de linkse en rechtse tabel die niet aan de join voorwaarde voldoen worden ook in het resultaat weergegeven

Startpunt

Als je wil mee volgen, gebruik dan onderstaand calibratiescript. Let op: dit maakt dezelfde tabelstructuur aan als de voorbeelden, maar gebruikt andere data.

COALESCE

De functie COALESCE (Engels voor "samensmelten") in SQL retourneert de eerste niet-NULL expressie tussen de argumenten. Met deze functie kan je eenvoudig aangeven wat erin een NULL kolom moet worden getoond:

Ter info, hieronder een lijstje met ranking van de meest populaire databases met bijzondere aandacht voor het database model ().

Verschil tussen een no sql / document database en een relationele database wordt heel duidelijk uitgelegd in volgend filmpje.

Verdiepend Introduction to NoSQL (Martin Fowler)

https://db-engines.com/en/ranking
http://tweakers.net/reviews/2354/nosql-maar-wat-is-het-dan-wel.html
http://tweakers.net/reviews/2354/2/nosql-maar-wat-is-het-dan-wel-relaties.html
http://tweakers.net/reviews/2354/3/nosql-maar-wat-is-het-dan-wel-bigtable-en-cassandra.html
http://tweakers.net/reviews/2354/6/nosql-maar-wat-is-het-dan-wel-tot-slot.html
https://www.youtube.com/watch?v=Jt_w2swkXAk
https://www.youtube.com/watch?v=qI_g07C_Q5I
drop database if exists `ApDB`;
CREATE DATABASE  IF NOT EXISTS `ApDB` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `ApDB`;
-- MySQL dump 10.13  Distrib 8.0.19, for Linux (x86_64)
--
-- Host: localhost    Database: ModernWays
-- ------------------------------------------------------
-- Server version    8.0.17

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

--
-- Table structure for table `Boeken`
--

DROP TABLE IF EXISTS `Boeken`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `Boeken` (
  `Titel` varchar(200) DEFAULT NULL,
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `Personen_Id` int(11) DEFAULT NULL,
  PRIMARY KEY (`Id`),
  KEY `fk_Boeken_Personen` (`Personen_Id`),
  CONSTRAINT `fk_Boeken_Personen` FOREIGN KEY (`Personen_Id`) REFERENCES `Personen` (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Boeken`
--

LOCK TABLES `Boeken` WRITE;
/*!40000 ALTER TABLE `Boeken` DISABLE KEYS */;
INSERT INTO `Boeken` VALUES ('Norwegian Wood',1,10),('Kafka on the Shore',2,10),('American Gods',3,16),('The Ocean at the End of the Lane',4,16),('Pet Sematary',5,17),('Good Omens',6,18),('The Talisman',7,17);
/*!40000 ALTER TABLE `Boeken` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `Personen`
--

DROP TABLE IF EXISTS `Personen`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `Personen` (
  `Voornaam` varchar(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `Familienaam` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `AanspreekTitel` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `Straat` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `Huisnummer` varchar(5) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `Stad` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `Commentaar` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `Biografie` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Personen`
--

LOCK TABLES `Personen` WRITE;
/*!40000 ALTER TABLE `Personen` DISABLE KEYS */;
INSERT INTO `Personen` VALUES ('Samuel','Ijsseling',1,NULL,NULL,NULL,NULL,NULL,NULL),('Jacob','Van Sluis',2,NULL,NULL,NULL,NULL,NULL,NULL),('Emile','Benveniste',3,NULL,NULL,NULL,NULL,NULL,NULL),('Evert W.','Beth',4,NULL,NULL,NULL,NULL,NULL,NULL),('Rémy','Bernard',5,NULL,NULL,NULL,NULL,NULL,NULL),('Robert','Bly',6,NULL,NULL,NULL,NULL,NULL,NULL),('timothy','gowers',7,NULL,NULL,NULL,NULL,NULL,NULL),(NULL,'?',8,NULL,NULL,NULL,NULL,NULL,NULL),(NULL,'Ovidius',9,NULL,NULL,NULL,NULL,NULL,NULL),('Haruki','Murakami',10,NULL,NULL,NULL,NULL,NULL,NULL),('David','Mitchell',11,NULL,NULL,NULL,NULL,NULL,NULL),('Nick','Harkaway',12,NULL,NULL,NULL,NULL,NULL,NULL),('Thomas','Ligotti',13,NULL,NULL,NULL,NULL,NULL,NULL),('Neil','Gaiman',16,NULL,NULL,NULL,NULL,NULL,NULL),('Stephen','King',17,NULL,NULL,NULL,NULL,NULL,NULL),('Terry','Pratchett',18,NULL,NULL,NULL,NULL,NULL,NULL),('Peter','Straub',19,NULL,NULL,NULL,NULL,NULL,NULL);
/*!40000 ALTER TABLE `Personen` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
SELECT Personen.Voornaam, Personen.Familienaam,
       COALESCE (Boeken.Titel, 'Geen boek opgenomen in Boeken') 
FROM Personen
LEFT JOIN Boeken ON Boeken.Personen_Id = Personen.Id
ORDER BY Personen.Familienaam, Personen.Voornaam, Boeken.Titel;
Kennisclip
Kennisclip

LEFT JOIN

Syntax

Als je alle personen wilt tonen ongeacht of ze een boek hebben geschreven of niet kan je een LEFT JOIN gebruiken. In tegenstelling tot bij INNER JOIN maakt het een groot verschil of je A LEFT JOIN B schrijft of B LEFT JOIN A. Met een LEFT JOIN worden alle rijen uit de linkse tabel geselecteerd, of er nu een match is of niet op basis van de sleutelkolommen.

Terug naar het voorbeeld, maar dan met LEFT JOIN:

Simone is nu wel geselecteerd. Maar er is geen boek van haar aanwezig in de tabel Boeken, dus staat Titel op NULL.

Opmerking:

De LEFT JOIN wordt ook wel LEFT OUTER JOIN genoemd. Er is geen verschil.

RIGHT EXCLUDING JOIN

Deze query retourneert alle records in de rechtse tabel (tabel B) die niet overeenkomen met records in de linkertabel (tabel A). Deze join wordt als volgt geschreven:

OUTER JOIN

De OUTER JOIN ziet er als volgt uit:

MySQL ondersteunt deze constructie niet, in tegenstelling tot sommige andere databanken. Maar je kan ze wel nabootsen door een (gewone) left join boven een excluding right join te plaatsen met behulp van UNION ALL, dat resultaten samen neemt:

Selecteer dezelfde kolommen uit beide joins! Het is mogelijk om dat niet te doen, maar dan krijg je mogelijk onzinnige resultaten.

Je kan ook een left excluding join combineren met een gewone right join.

Opmerking

Er wordt ook wel gesproken over FULL OUTER JOIN.

INNER JOIN

Deze is in het eerste semester al aan bod gekomen. We herhalen hem hier en we introduceren een Venn diagram als visueel geheugensteuntje. Let op: het Venn diagram mag je niet lezen als twee verzamelingen records en hun doorsnede. Het heeft hier een andere betekenis!

Lees ook de commentaar in elk gegeven stukje code! Deze bevat zeer belangrijke informatie.

Syntax

Betekenis

We combineren informatie uit twee tabellen: de linkertabel (A) en de rechtertabel (B). We zeggen dat er overlapping is tussen de tabellen als de gebruikte sleutelkolommen dezelfde waarde bevatten. Het stuk van A dat we bekijken (A.Id) matcht met het stuk van B dat we bekijken (B.A_Id) en dat tekenen we als een overlappend gedeelte.

Als je dat begrepen hebt, zou het je niet mogen verbazen dat je net zo goed B INNER JOIN A kan schrijven als A INNER JOIN B. Enkel bij SELECT * maakt dit een (klein) verschil, namelijk dat de kolommen in een andere volgorde zullen staan.

Voorbeeld

Het diagram met de naam A staat voor de tabel Boeken en met de naam B voor Personen. We veronderstellen dat één auteur meerdere boeken kan hebben maar niet omgekeerd, dus de vreemde sleutel moet wel in Boeken staan. Bij elk boek staat dus een nummer dat uitdrukt: "dit boek is geschreven door persoon nummer ..."

Selecteer alle boeken en toon de voornaam en de familienaam van de auteur.

Elke rij uit de tabel Boeken wordt gecombineerd met elke rij uit de tabel Personen en de combinaties waarin een match wordt gevonden tussen Personen_Id en Personen.Id worden overgehouden. Indien er geen match is wordt de gecombineerde rij genegeerd.

Het resultaat is een nieuwe tabel, dus je kan er verder mee werken zoals je altijd gedaan hebt. We ordenen de lijst op Familienaam, Voornaam, Titel.

Je mag trouwens na FROM een haakje openen en voor ORDER BY het haakje weer sluiten als je dat makkelijker vindt.

We voegen een nieuwe persoon toe in de tabel Personen:

We selecteren alle personen geordend op Familienaam en Voornaam:

En we zien dat Simone De Beauvoir is toegevoegd.

Alle auteurs en hun boeken te selecteren, maar ordenen de lijst nu op Familienaam, Voornaam en Titel:

Nu merken we dat Simone De Beauvoir niet meer in de lijst voorkomt. Dat komt doordat er voor Simone de Beauvoir geen boeken in de boekentabel zijn opgenomen.

Een INNER JOIN gaat alleen die personen tonen waarvoor een match in de tabel Boeken wordt gevonden.

left join met coalesce Boeken Personen Simone De Beauvoir

SELECT <kolommen uit A of uit B>
FROM A 
LEFT JOIN B 
-- hier veronderstellen we dat de vreemde sleutel in B staat
ON A.Id = B.A_Id
-- alternatief met vreemde sleutel in A:
-- ON A.B_Id = B.Id
SELECT Personen.Voornaam, Personen.Familienaam,
       Boeken.Titel 
FROM Personen
LEFT JOIN Boeken ON Boeken.Personen_Id = Personen.Id
ORDER BY Personen.Familienaam, Personen.Voornaam, Boeken.Titel;
SELECT <select_list>
FROM A
RIGHT JOIN B
-- ook hier kan het zijn dat de linkertabel de primary key bevat
ON A.B_Id = B.Id
WHERE A.B_Id IS NULL
-- LET OP:
-- hier moet je altijd de foreign key kolom gebruiken
-- een primary key kan immers nooit NULL zijn
-- net als tevoren kan de code wat variëren
-- de foreign key staat hier in de rechtertabel
-- hij kan (mits aanpassingen) ook in de linkertabel
SELECT <kolommen uit A of uit B>
FROM A 
LEFT JOIN B 
ON A.Id = B.A_Id

UNION ALL -- plaats de resultaten onder elkaar -> maak de kolommen expliciet! geen *

SELECT <select_list>
FROM A
RIGHT JOIN B
ON A.B_Id = B.Id
WHERE A.B_Id IS NULL
-- dit is niet de enige mogelijkheid
SELECT <kolommen uit A of uit B>
FROM A
INNER JOIN B ON A.Id = B.A_Id
-- alternatief waarbij je de volgorde wisselt
-- dit mag, want = betekent gewoon "is gelijk aan"
-- 2+2 = 4 betekent hetzelfde als 4 = 2+2
-- INNER JOIN B ON B.A_Id = A.Id
-- alternatief, als A de foreign key bevat:
-- INNER JOIN B ON A.B_Id = B.Id
-- ook hier kan je de volgorde nog eens omwisselen
SELECT Personen.Voornaam, Personen.Familienaam,
       Boeken.Titel 
FROM Boeken
INNER JOIN Personen ON Boeken.Personen_Id = Personen.Id;
SELECT Personen.Voornaam, Personen.Familienaam,
       Boeken.Titel 
FROM Personen
INNER JOIN Boeken ON Boeken.Personen_Id = Personen.Id
ORDER BY Personen.Voornaam, Personen.Familienaam, Boeken.Titel;
INSERT INTO Personen (
   Voornaam, 
   Familienaam,
   AanspreekTitel,
   Straat, 
   Huisnummer,
   Stad, 
   Commentaar,
   Biografie
)
VALUES
(
   'Simone', 
   'De Beauvoir', 
   'Mevrouw',
   'Rue Charles De Gaulle', 
   '38', 
   'Paris', 
   'Feministe',
   'Compagnon van Jean-Paul Sartre'
);
SELECT * FROM Personen
ORDER BY Familienaam, Voornaam;
SELECT Personen.Voornaam, Personen.Familienaam,
       Boeken.Titel 
FROM Personen
INNER JOIN Boeken ON Boeken.Personen_Id = Personen.Id
ORDER BY Personen.Familienaam, Personen.Voornaam, Boeken.Titel;
Kennisclip diagramnotatie
Kennisclip demo
Kennisclip
Kennisclip diagramnotatie
Kennisclip demo

OUTER EXCLUDING JOIN

Deze query retourneert alle records in de linkertabel (tabel A) en alle records in de rechtertabel (tabel B) die niet overeenkomen. Net als de gewone full outer join is deze standaard niet beschikbaar in MySQL, maar je kan hem krijgen door een left excluding join en een right excluding join te combineren. De code daarvan zou je intussen zelf moeten kunnen schrijven op basis van die voor de gewone outer join.

LEFT EXCLUDING JOIN

Deze query retourneert alle records in de linkertabel (tabel A) die niet overeenkomen met records in de rechtse tabel (tabel B). Eventuele kolommen uit de tabel B die vermeld worden in de select list bevatten sowieso de waarde NULL. Deze join wordt als volgt geschreven:

SELECT <select_list>
FROM A
LEFT JOIN B
-- ook hier kan het zijn dat de linkertabel de primary key bevat
ON A.B_Id = B.Id
WHERE A.B_Id IS NULL
-- LET OP:
-- hier moet je altijd de foreign key kolom gebruiken
-- een primary key kan immers nooit NULL zijn

RIGHT JOIN

Syntax

SELECT <kolommen uit A of uit B>
FROM A
RIGHT JOIN B
-- opnieuw: schrijfwijze hangt af van waar foreign key staat
ON A.B_Id = B.Id

Als ik de tabellen in de select statement switch en Boeken links en Personen rechts zet, gaat Simone de Beauvoir getoond worden?

SELECT Personen.Voornaam, Personen.Familienaam,
       COALESCE (Boeken.Titel, 'Geen boek opgenomen in Boeken') 
FROM Boeken
LEFT JOIN Personen on Boeken.Personen_Id = Personen.Id
ORDER BY Personen.Familienaam, Personen.Voornaam, Boeken.Titel;

Geen Simone de Beauvoir te zien. Hoe kan ik Simone tonen (zonder de volgorde opnieuw te switchen naar het origineel):

Door een RIGHT JOIN:

SELECT Personen.Voornaam, Personen.Familienaam,
       COALESCE (Boeken.Titel, 'Geen boek opgenomen in Boeken') 
FROM Boeken
RIGHT JOIN Personen ON Boeken.Personen_Id = Personen.Id
ORDER BY Personen.Familienaam, Personen.Voornaam, Boeken.Titel;

Simone De Beauvoir wordt getoond en de coalesce functie doet haar werk:

Hier merk je wel dat de derde kolomhoofding niet elegant is. Je kan die zelf opgeven in het select statement door een alias mee te geven:

SELECT Personen.Voornaam, Personen.Familienaam,
       COALESCE (Boeken.Titel, 'Geen boek opgenomen in Boeken')
       AS 'Titel van het boek'
FROM Boeken
RIGHT JOIN Personen ON Boeken.Personen_Id = Personen.Id
ORDER BY Personen.Familienaam, Personen.Voornaam, Boeken.Titel;
venn diagram left join
left join Boeken Personen Simone De Beauvoir
Venn diagram inner join
INNER JOIN Boeken Personen Simone De Beauvoir
Venn diagram right excluding join
Venn diagram outer join
Venn diagram outer excluding join

Venn diagram left excluding join

Venn diagram right join
right join met coalesce Boeken Personen Simone De Beauvoir
right join met coalesce en kolomtitel Boeken Personen Simone De Beauvoir
Kennisclip
Kennisclip
The basics of NoSQL databases — and why we need themfreeCodeCamp.org
Alles wat u moet weten over blockchain technologie.
Logo
BlockchainWikipedia

DROP

Een view kan je eenvoudig verwijderen.

Het verwijderen van een view resulteert niet in het verwijderen van de data in de bron. De data in de view wordt opgebouwd door een query die data uit andere tabellen haalt.

Syntax

DROP VIEW IF EXISTS viewnaam;

Voorbeeld

Toegepast op het voorbeeld vermeld bij CREATE.

DROP VIEW IF EXISTS TakenLeden;

Samenvattende flowchart

Onderstaande flowchart kan je helpen de juiste JOIN toe te passen.

ALTER

Syntax

ALTER VIEW bestaandeviewnaam
AS
SELECT ??? -- alsof je de view opnieuw definieert

Voorbeeld

ALTER VIEW TakenLeden
AS
SELECT Leden.Voornaam, Taken.Omschrijving, Taken.Id AS 'Taken_Id'
FROM Taken
INNER JOIN Leden ON Leden.Id = Taken.Leden_Id;

Views

Een view is een zogeheten virtuele tabel die gebaseerd is op het resultaat van een query.

Een view zou je dus als een zogeheten opgeslagen query kunnen bekijken. De data van een view worden evenwel niet opgeslagen in de database, de gegevens worden telkens opnieuw uit tabellen opgehaald op basis van de query en dit wanneer betreffende view wordt geconsulteerd.

Een view, gebaseerd op een query, kun je op exact dezelfde manier benaderen als een gewone tabel en je kan hierbij tevens join statements hanteren. M.a.w. wanneer je een view hebt gecreëerd, kan je deze view benaderen als een gewone tabel en een nieuwe query schrijven die bv. data uit de view en een andere 'echte' tabel haalt.

Een view bevat rijen en kolommen zoals een 'echte' tabel.

Een view is dus het resultaat van een query en kan kolommen bevatten dewelke afkomstig zijn uit verschillende tabellen.

Views gebruiken de sleutelwoorden die we kennen uit de DDL. Ze worden gecreëerd als "virtuele tabellen", dus het is logisch dat ze syntax gebruiken die gelijkaardig is aan die voor het creëren van gewone tabellen.

https://www.mysqltutorial.org/mysql-views-tutorial.aspx

CREATE

Syntax

CREATE VIEW viewnaam
AS
SELECT ??? -- hier kan vanalles komen, maar er wordt een resultatenset getoond

Voorbeeld

We baseren ons voorbeeld op onderstaande tabellen Taken en Leden.

Taken

omschrijving

Id

Leden_Id

bestek voorzien

1

2

frisdrank meebrengen

2

1

aardappelsla maken

3

3

Leden

voornaam

Id

Yannick

1

Bavo

2

Max

3

Het uitganspunt van een view is bepaalde data die afkomstig is uit verschillende tabellen samen te brengen en te benaderen als één nieuwe virtuele tabel.

Onderstaande query haalt bepaalde data op uit de tabellen Taken en Leden.

SELECT Leden.voornaam, Taken.omschrijving
FROM Taken
INNER JOIN Leden
ON Leden.Id = Taken.Leden_Id;

Het resultaat van deze query is:

voornaam

omschrijving

Yannick

frisdrank meebrengen

Bavo

bestek voorzien

Max

aardappelsla maken

Als we het resultaat van bovenstaande query willen opslaan in een view, doen we dit op volgende manier:

CREATE VIEW TakenLeden
AS
SELECT Leden.voornaam, Taken.omschrijving
FROM Taken
INNER JOIN Leden ON Leden.Id = Taken.Leden_Id;

Eens je bovenstaande query CREATE VIEW hebt uitgevoerd wordt de view bewaard in de database.

In MySQL Workbench kan je een view vinden onder deze rubriek:

Nu kan je deze view bevragen zoals een gewone tabel.

SELECT *
FROM TakenLeden;

Wel belangrijk te weten dat een view de data niet fysisch bevat, maar telkens de query zal uitvoeren. Dat kan ons verder een heleboel schrijfwerk besparen bij complexe queries.

Labo oefeningen

Calibratie

Start vanaf onderstaand script, 0500__CalibrateDB.sql:

-- MySQL dump 10.13  Distrib 8.0.16, for Linux (x86_64)
--
-- Host: localhost    Database: ApDB
-- ------------------------------------------------------
-- Server version    8.0.16

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
 SET NAMES utf8mb4 ;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

--
-- Table structure for table `Auteurs`
--
DROP DATABASE if exists ApDB;
CREATE DATABASE ApDB;
USE ApDB;

DROP TABLE IF EXISTS `Auteurs`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
 SET character_set_client = utf8mb4 ;
CREATE TABLE `Auteurs` (
  `Voornaam` varchar(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `Familienaam` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Auteurs`
--

LOCK TABLES `Auteurs` WRITE;
/*!40000 ALTER TABLE `Auteurs` DISABLE KEYS */;
INSERT INTO `Auteurs` VALUES ('Samuel','Ijsseling',1),('Jacob','Van Sluis',2),('Emile','Benveniste',3),('Evert W.','Beth',4),('R├®my','Bernard',5),('Robert','Bly',6),('timothy','gowers',7),(NULL,'?',8),(NULL,'Ovidius',9),('Haruki','Murakami',10),('David','Mitchell',11),('Nick','Harkaway',12),('Thomas','Ligotti',13),('Neil','Gaiman',16),('Stephen','King',17),('Terry','Pratchett',18),('Peter','Straub',19);
/*!40000 ALTER TABLE `Auteurs` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `Boeken`
--

DROP TABLE IF EXISTS `Boeken`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
 SET character_set_client = utf8mb4 ;
CREATE TABLE `Boeken` (
  `Titel` varchar(200) DEFAULT NULL,
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Boeken`
--

LOCK TABLES `Boeken` WRITE;
/*!40000 ALTER TABLE `Boeken` DISABLE KEYS */;
INSERT INTO `Boeken` VALUES ('Norwegian Wood',1),('Kafka on the Shore',2),('American Gods',3),('The Ocean at the End of the Lane',4),('Pet Sematary',5),('Good Omens',6),('The Talisman',7);
/*!40000 ALTER TABLE `Boeken` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `BoekenNaarAuteurs`
--

DROP TABLE IF EXISTS `BoekenNaarAuteurs`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
 SET character_set_client = utf8mb4 ;
CREATE TABLE `BoekenNaarAuteurs` (
  `Boeken_Id` int(11) NOT NULL,
  `Auteurs_Id` int(11) NOT NULL,
  KEY `fk_BoekenNaarAuteurs_Boeken` (`Boeken_Id`),
  KEY `fk_BoekenNaarAuteurs_Auteurs` (`Auteurs_Id`),
  CONSTRAINT `fk_BoekenNaarAuteurs_Auteurs` FOREIGN KEY (`Auteurs_Id`) REFERENCES `Auteurs` (`Id`),
  CONSTRAINT `fk_BoekenNaarAuteurs_Boeken` FOREIGN KEY (`Boeken_Id`) REFERENCES `Boeken` (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `BoekenNaarAuteurs`
--

LOCK TABLES `BoekenNaarAuteurs` WRITE;
/*!40000 ALTER TABLE `BoekenNaarAuteurs` DISABLE KEYS */;
/*!40000 ALTER TABLE `BoekenNaarAuteurs` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `Games`
--

DROP TABLE IF EXISTS `Games`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
 SET character_set_client = utf8mb4 ;
CREATE TABLE `Games` (
  `Titel` varchar(100) NOT NULL,
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Games`
--

LOCK TABLES `Games` WRITE;
/*!40000 ALTER TABLE `Games` DISABLE KEYS */;
INSERT INTO `Games` VALUES ('Anthem',1),('Sekiro: Shadows Die Twice',2),('Devil May Cry 5',3),('Mega Man 11',4),('Oregon Trail',5);
/*!40000 ALTER TABLE `Games` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `Leden`
--

DROP TABLE IF EXISTS `Leden`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
 SET character_set_client = utf8mb4 ;
CREATE TABLE `Leden` (
  `Voornaam` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Leden`
--

LOCK TABLES `Leden` WRITE;
/*!40000 ALTER TABLE `Leden` DISABLE KEYS */;
INSERT INTO `Leden` VALUES ('Yannick',1),('Bavo',2),('Max',3),('Herve',4);
/*!40000 ALTER TABLE `Leden` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `Platformen`
--

DROP TABLE IF EXISTS `Platformen`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
 SET character_set_client = utf8mb4 ;
CREATE TABLE `Platformen` (
  `Naam` varchar(20) NOT NULL,
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Platformen`
--

LOCK TABLES `Platformen` WRITE;
/*!40000 ALTER TABLE `Platformen` DISABLE KEYS */;
INSERT INTO `Platformen` VALUES ('PS4',1),('Xbox One',2),('Windows',3),('Nintendo Switch',4),('Master System',5);
/*!40000 ALTER TABLE `Platformen` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `Postcodes`
--

DROP TABLE IF EXISTS `Postcodes`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
 SET character_set_client = utf8mb4 ;
CREATE TABLE `Postcodes` (
  `Code` char(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `Plaats` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `Localite` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `Provincie` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `Province` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Postcodes`
--

LOCK TABLES `Postcodes` WRITE;
/*!40000 ALTER TABLE `Postcodes` DISABLE KEYS */;
INSERT INTO `Postcodes` VALUES ('2800','Mechelen','Malines','Antwerpen','Anvers'),('3000','Leuven','Louvain','Vlaams Brabant','Brabant Flamand');
/*!40000 ALTER TABLE `Postcodes` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `Releases`
--

DROP TABLE IF EXISTS `Releases`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
 SET character_set_client = utf8mb4 ;
CREATE TABLE `Releases` (
  `Games_Id` int(11) NOT NULL,
  `Platformen_Id` int(11) NOT NULL,
  KEY `fk_Releases_Games` (`Games_Id`),
  KEY `fk_Releases_Platformen` (`Platformen_Id`),
  CONSTRAINT `fk_Releases_Games` FOREIGN KEY (`Games_Id`) REFERENCES `Games` (`Id`),
  CONSTRAINT `fk_Releases_Platformen` FOREIGN KEY (`Platformen_Id`) REFERENCES `Platformen` (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Releases`
--

LOCK TABLES `Releases` WRITE;
/*!40000 ALTER TABLE `Releases` DISABLE KEYS */;
INSERT INTO `Releases` VALUES (1,1),(1,2),(1,3),(2,1),(2,2),(2,3),(3,1),(3,2),(4,1),(4,2),(4,3),(4,4);
/*!40000 ALTER TABLE `Releases` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `Taken`
--

DROP TABLE IF EXISTS `Taken`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
 SET character_set_client = utf8mb4 ;
CREATE TABLE `Taken` (
  `Omschrijving` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `Leden_Id` int(11),
  PRIMARY KEY (`Id`),
  KEY `fk_Taken_Leden` (`Leden_Id`),
  CONSTRAINT `fk_Taken_Leden` FOREIGN KEY (`Leden_Id`) REFERENCES `Leden` (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Taken`
--

LOCK TABLES `Taken` WRITE;
/*!40000 ALTER TABLE `Taken` DISABLE KEYS */;
INSERT INTO `Taken` VALUES ('bestek voorzien',1,2),('frisdrank meebrengen',2,1),('aardappelsla maken',3,3),('papieren bordjes meebrengen',4,null);
/*!40000 ALTER TABLE `Taken` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `Tweets`
--

DROP TABLE IF EXISTS `Tweets`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
 SET character_set_client = utf8mb4 ;
CREATE TABLE `Tweets` (
  `Bericht` varchar(144) DEFAULT NULL,
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `Users_Id` int(11) DEFAULT NULL,
  PRIMARY KEY (`Id`),
  KEY `fk_Tweets_Users` (`Users_Id`),
  CONSTRAINT `fk_Tweets_Users` FOREIGN KEY (`Users_Id`) REFERENCES `Users` (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Tweets`
--

LOCK TABLES `Tweets` WRITE;
/*!40000 ALTER TABLE `Tweets` DISABLE KEYS */;
INSERT INTO `Tweets` VALUES ('Don\'t forget -- Nintendo Labo: VR Kit launches 12/04!',1,1),('Splat it out in the #Splatoon2 EU Community Cup 5 this Sunday!',2,1),('Crikey! Keep an eye out for cardboard crocs and other crafty wildlife on this jungle train ride! #Yoshi',3,1),('You had a lot to say about #MetroExodus. Check out our favorite 5-word reviews.',4,2),('It\'s a perfect day for some mayhem.',5,2),('Drift all over N. Sanity Beach and beyond in Crash Team Racing Nitro-Fueled.',6,2);
/*!40000 ALTER TABLE `Tweets` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `Users`
--

DROP TABLE IF EXISTS `Users`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
 SET character_set_client = utf8mb4 ;
CREATE TABLE `Users` (
  `Handle` varchar(144) DEFAULT NULL,
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Users`
--

LOCK TABLES `Users` WRITE;
/*!40000 ALTER TABLE `Users` DISABLE KEYS */;
INSERT INTO `Users` VALUES ('NintendoEurope',1),('Xbox',2);
/*!40000 ALTER TABLE `Users` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

Veel-op-veel-relaties

Oefening 1

Merk op dat er tabellen zijn met de namen Boeken en tabel Auteurs. Gebruik volgende informatie om de tabel BoekenNaarAuteurs in te vullen met 0501__Oefening.sql:

  • Haruki Murakami schreef Norwegian Wood en Kafka on the Shore

  • Neil Gaiman schreef American Gods en The Ocean at the End of the Lane

  • Terry Pratchett en Neil Gaiman schreven samen Good Omens

Tip: zoek eerst de primary key van de auteur en van het boek in Auteurs en in Boeken. Maak dan in BoekenNaarAuteurs een rij aan die de twee aan elkaar linkt.

Tussenstappen

Noem volgend script 0502__Oefening.sql en voer uit:

USE ApDB;

ALTER TABLE Releases
add Datum DATE;

Noem volgend script het 0503__Oefening.sql en voer uit. Je hoeft het niet helemaal te begrijpen.:

USE ApDB;

UPDATE Releases, Games
SET Datum = '2019-02-22'
WHERE Games.Titel = 'Anthem' AND Releases.Games_Id = Games.Id;

UPDATE Releases, Games
SET Datum = '2019-03-22'
WHERE Games.Titel = 'Sekiro: Shadows Die Twice' AND Releases.Games_Id = Games.Id;

UPDATE Releases, Games
SET Datum = '2019-03-08'
WHERE Games.Titel = 'Devil May Cry 5' AND Releases.Games_Id = Games.Id;

UPDATE Releases, Games
SET Datum = '2018-10-02'
WHERE Games.Titel = 'Mega Man 11' AND Releases.Games_Id = Games.Id;

Oefening 2

Schrijf zelf een tabel, Uitleningen, die leden koppelt aan boeken die ze hebben uitgeleend in de bibliotheek. Een uitlening heeft een verplichte startdatum en eventueel (dus niet verplicht) een einddatum. Deze datums stel je voor met het DATE-datatype. Noem het script dat deze tabel aanmaakt 0504__Oefening.sql.

Oefening 3

Schrijf een script, 0505__Oefening.sql, dat volgende informatie in de databank plaatst:

  • Max heeft Norwegian Wood geleend van 1 februari 2019 tot 15 februari 2019.

  • Bavo heeft Norwegian Wood geleend van 16 februari 2019 tot 2 maart 2019.

  • Bavo heeft Pet Sematary geleend van 16 februari 2019 tot 2 maart 2019.

  • Yannick heeft Pet Sematary geleend van 1 mei 2019 en heeft het boek nog niet teruggebracht.

Tabellen samenvoegen bij veel-op-veel relaties

Opfrissing eerste semester

We hebben een tabel die bijhoudt wanneer een bepaalde videogame is uitgebracht op een bepaald platform. Die ziet er zo uit:

Games.Id

Platformen.Id

Releasedatum

1

1

22 februari 2019

1

2

22 februari 2019

1

3

22 februari 2019

2

1

22 maart 2019

2

2

22 maart 2019

2

3

22 maart 2019

3

1

8 maart 2019

3

2

8 maart 2019

4

1

2 oktober 2018

4

2

2 oktober 2018

4

3

2 oktober 2018

4

4

2 oktober 2018

Als we deze tabel willen ontcijferen, d.w.z. als we de namen van de games en de platformen willen zien, combineren we deze tabel Releases eerst met de tabel Platformen en vervolgens met de tabel games. Dat ziet er zo uit:

SELECT Games.Titel, Platformen.Naam
FROM Releases
     INNER JOIN Platformen ON Releases.Platformen_Id = Platformen.Id
     INNER JOIN Games ON Releases.Games_Id = Games.Id

Oefening 4

Toon nu zelf het overzicht van wie welk boek heeft uitgeleend en in welke periode. Doe dit in script 0506__Oefening.sql. Gebruik coalesce om duidelijk aan te geven wanneer een boek nog niet is teruggebracht en zorg dat de kolomtitel leesbaar is met as.

Persoon

Boek

Startdatum

Einddatum

Max

Norwegian Wood

1 februari 2019

15 februari 2019

...

...

...

...

Yannick

Pet Sematary

1 mei 2019

nog niet teruggebracht

Andere soorten JOINs

Oefening 5

Toon alle taken, met het lid dat de taak uitvoert. Als de taak door niemand wordt uitgevoerd, staat er "taak niet toegewezen". Maak voor jezelf eerst de tekening met overlappende cirkels zodat je weet hoe je dit kan uitschrijven, zoals in de theorie. Gebruik COALESCE en AS om je output leesbaar te maken, zoals in de voorbeeldoutput hieronder:

Voornaam

Omschrijving

Yannick

frisdrank meebrengen

Bavo

bestek voorzien

Max

aardappelsla maken

Taak niet toegewezen

papieren bordjes meebrengen

Noem het script 0507__Oefening.sql.

Oefening 6

Toon alle titels van games met hun bijbehorend platform, als er een is. Toon ook games waarvoor het platform niet meer ondersteund wordt (d.w.z. waarvoor geen info in Releases staat). Gebruik hiervoor een samenstelling van twee JOINs. Gebruik opnieuw COALESCE en AS voor mooiere output. Noem het script 0508__Oefening.sql.

Voorbeeldoutput:

Titel

Naam

Anthem

PS4

Anthem

XBox one

Anthem

Windows

...

...

Mega Man 11

Switch

Oregon Trail

Platform niet meer ondersteund

Tip: Probeer dit niet in één keer. Koppel eerst releases aan platformen. Controleer je output. Plaats het deel van de query dat deze koppeling doet tussen haakjes. Dit deel kan je nu ook zien als een cirkel in de tekening voor de verschillende soorten joins. Denk dan na over de tekening om ook games te betrekken en schrijf daarna pas de volledige query.

Oefening 7

Er is een tabel Taken en een tabel Leden. Bij taken staat (door middel van een verwijzing) welk lid een bepaalde taak uitvoert. Toon nu alle taken die niet aan iemand zijn toegewezen. Noem je script 0509__Oefening.sql. (Doe dit met stof die tot hiertoe aan bod is gekomen, niet met constructies die we nog niet gezien hebben.)

Oefening 8

Toon alle platformen waarvoor games beschikbaar zijn. Een platform wordt dus niet getoond als er geen games beschikbaar voor zijn, maar verschijnt zodra er minstens één game voor is. Noem je script 0510__Oefening.sql. (Doe dit met stof die tot hiertoe aan bod is gekomen, niet met constructies die we nog niet gezien hebben.)

Oefening 9

Toon alle games waarvoor het platform onbekend is, samen met alle platformen waarvoor geen games beschikbaar zijn. Het resultaat ziet er als volgt uit:

Titel
Naam

Oregon Trail

Geen platformen gekend

Geen games gekend

Master System

Noem je script 0511__Oefening.sql. (Doe dit met stof die tot hiertoe aan bod is gekomen, niet met constructies die we nog niet gezien hebben.)

Subqueries

Situatie

Stel: Je wil alle personen selecteren die ouder zijn dan de gemiddelde persoon in je tabel Personen (met een kolom Leeftijd). Hoe zou je zoiets aanpakken? Je kan eerst AVG(Leeftijd) opvragen, noteren en dan een SELECT met een WHERE uitvoeren. Het nadeel hiervan is dat hier twee queries moeten plaatsvinden en dat jij als programmeur het resultaat van die eerste query moet verwerken in de tweede. In dit onderdeel bekijken we een oplossing voor dit probleem en gelijkaardige problemen: geneste queries (ook wel subqueries) genoemd.

Oplossing

Een subquery is een query die wordt uitgevoerd als onderdeel van een grotere query. Deze kleinere query zal geen zichtbare uitvoer produceren, maar zal wel informatie leveren die je anders met de hand had moeten intypen in de grotere query. Dat laatste is vaak niet eens haalbaar!

BESLUIT

Views bieden heel wat voordelen, o.a.

  • Vereenvoudiging van complexe queries Views zijn vooral nuttig ingeval je vaak eenzelfde complexe query moet uitvoeren.

  • Business logica blijft consistent Indien je een query hebt die een complexe logica bevat, kunnen views helpen deze logica consistent te maken en de complexiteit zogeheten te verbergen.

  • Afscherming van gevoelige data Als een of meer tabellen gevoelige data bevatten, dan kan je opteren om enkel de relevante en niet gevoelige data in een view op te slaan. Je kan bepaalde gebruikers dan ook toegang geven tot enkel de views, maar niet tot de oorspronkelijke tabellen.

Een onafhankelijke subquery

Onafhankelijke subqueries zijn subqueries die je op zich kan uitschrijven en uitvoeren. Dat is handig, want het maakt onafhankelijke subqueries makkelijk te testen. Dit zijn de enige soorten die wij in deze cursus zullen behandelen.

Eenvoudig voorbeeld

Veronderstel je hebt een tabel Werknemers van volgende vorm:

Id

Voornaam

Familienaam

Kantoornummer

1

Peter

Spaas

04.04

2

Vincent

Nys

03.128

3

Jef

Inghelbrecht

04.03

Stel dat je ook een tabel Kantoorruimtes hebt van deze vorm. Beamer geeft aan of er een beamer geïnstalleerd is in het kantoor:

Id

Kantoornummer

Beamer

1

04.04

0

2

03.128

1

3

04.03

1

Wat als je nu wil weten wie er in een kantoor zit waarin een beamer beschikbaar is? Dan is een geneste query een optie:

select Voornaam, Familienaam
from Werknemers
where Kantoornummer in (select Kantoornummer from Kantoorruimtes where Beamer);

Start van binnen (de haakjes) en kijk dan naar buiten: select Kantoornummer from Kantoorruimtes where Beamer toont je alle nummers van kantoren met een beamer. Het sleutelwoordje in ken je: het zegt of een waarde aanwezig is in een lijst met waarden. In dit geval is die lijst het resultaat van de geneste query. Op deze manier krijgen we te zien wie in een kantoor met beamer zit. We noemen select Kantoornummer from Kantoorruimtes where Beamer een geneste query of subquery omdat het op zich een SQL-query is, maar wel een die ingebed is in een grotere query. Meerbepaald gaat het om een onafhankelijke subquery: een subquery die je zo kan uitvoeren. Inderdaad, als je een tabel Kantoorruimtes hebt, kan je select Kantoornummer from Kantoorruimtes where Beamer op zich schrijven.

Wat je hier ziet is trouwens een algemene regel: subqueries zetten we tussen haakjes!

Toegegeven, deze query had je kunnen vervangen door een query met een JOIN, zonder geneste query. Maar dat zou niet werken als je de werknemers niet wou selecteren, maar wel wou deleten.

RENAME

Het is mogelijk om de naam van de view te wijzigen.

USE ApDB;
RENAME TABLE Takenleden
TO TakenLeden_Updated;

Indexeren

Wat is een index?

Je kent het principe van een index waarschijnlijk uit alledaagse situaties.

  • In studieboeken staat vaak achteraan een alfabetisch geordende woordenlijst met paginanummers. Hiermee raak je veel sneller aan de gewenste informatie dan wanneer je een boek woord per woord leest vanaf het begin.

  • In het Engels wordt het woord "index" soms gebruikt om een telefoongids aan te duiden. Hiermee kan je heel snel iemand terugvinden als je de juiste plaatsnaam kent en een stukje informatie over de gezochte persoon kent (vaak een achternaam of beroep).

Indexen kunnen ook gebruikt worden om heel snel een databasetabel te doorlopen. Net zoals je zelf tijd verliest door altijd vanaf de eerste pagina te beginnen lezen, verliest een database engine tijd door altijd elk databaserecord te controleren.

We maken database indexen aan om deze inefficiëntie te vermijden. Net als woordenlijsten of telefoonboeken zijn ze gespecialiseerd in slechts een stukje van de aanwezige informatie, maar maken ze het wel mogelijk sneller informatie terug te vinden. Door op een verstandige manier om te springen met indexen kunnen we onze database veel efficiënter gebruiken zonder te investeren in sterkere hardware.

In eerste instantie is een tabel geïndexeerd op basis van zijn primaire sleutel, maar we kunnen ook indexeren op andere kolommen of zelfs op een combinatie van kolommen.

Indexeren verhoogt de snelheid waarmee gegevens worden geselecteerd, maar vertraagt de werking bij het opslaan van gegevens. Wanneer gegevens worden toegevoegd, wordt niet alleen de tabel bijgewerkt, maar moet ook de index worden bijgewerkt. Vergelijk met de registratie van een nieuwe persoon in een gemeente. Als deze persoon zich aanmeldt op het stadhuis en er wordt een telefoonboek bijgehouden, moeten de gegevens van de personendienst en de gegevens van het telefoonboek worden aangepast. Dat is meer werk dan alleen het aanpassen van de persoonsgegevens.

Een index kun je op elk willekeurig moment creëren/wijzigen/verwijderen, zonder dat dit invloed heeft op de opgeslagen data in de bijhorende tabellen. Anders gesteld: het is niet omdat het telefoonboek niet meer wordt uitgegeven, dat je plots geen telefoonnummer meer hebt.

Labo oefeningen

Labo oefeningen

Start vanaf volgend calibratiescript, 0512__Calibratie.sql:

DROP DATABASE  IF EXISTS `ApDB`;
CREATE DATABASE  IF NOT EXISTS `ApDB` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `ApDB`;
-- MySQL dump 10.13  Distrib 8.0.19, for Linux (x86_64)
--
-- Host: localhost    Database: ApDB
-- ------------------------------------------------------
-- Server version    8.0.17

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

--
-- Table structure for table `Boeken`
--

DROP TABLE IF EXISTS `Boeken`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `Boeken` (
  `Titel` varchar(200) DEFAULT NULL,
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Boeken`
--

LOCK TABLES `Boeken` WRITE;
/*!40000 ALTER TABLE `Boeken` DISABLE KEYS */;
INSERT INTO `Boeken` VALUES ('Norwegian Wood',1),('Kafka on the Shore',2),('American Gods',3),('The Ocean at the End of the Lane',4),('Pet Sematary',5),('Good Omens',6),('The Talisman',7);
/*!40000 ALTER TABLE `Boeken` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `Personen`
--

DROP TABLE IF EXISTS `Personen`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `Personen` (
  `Voornaam` varchar(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `Familienaam` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Personen`
--

LOCK TABLES `Personen` WRITE;
/*!40000 ALTER TABLE `Personen` DISABLE KEYS */;
INSERT INTO `Personen` VALUES ('Samuel','Ijsseling',1),('Jacob','Van Sluis',2),('Emile','Benveniste',3),('Evert W.','Beth',4),('Rémy','Bernard',5),('Robert','Bly',6),('timothy','gowers',7),(NULL,'?',8),(NULL,'Ovidius',9),('Haruki','Murakami',10),('David','Mitchell',11),('Nick','Harkaway',12),('Thomas','Ligotti',13),('Neil','Gaiman',16),('Stephen','King',17),('Terry','Pratchett',18),('Peter','Straub',19);
/*!40000 ALTER TABLE `Personen` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `Publicaties`
--

DROP TABLE IF EXISTS `Publicaties`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `Publicaties` (
  `Boeken_Id` int(11) NOT NULL,
  `Personen_Id` int(11) NOT NULL,
  KEY `fk_Publicaties_Boeken` (`Boeken_Id`),
  KEY `fk_Publicaties_Personen` (`Personen_Id`),
  CONSTRAINT `fk_Publicaties_Boeken` FOREIGN KEY (`Boeken_Id`) REFERENCES `Boeken` (`Id`),
  CONSTRAINT `fk_Publicaties_Personen` FOREIGN KEY (`Personen_Id`) REFERENCES `Personen` (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Publicaties`
--

LOCK TABLES `Publicaties` WRITE;
/*!40000 ALTER TABLE `Publicaties` DISABLE KEYS */;
INSERT INTO `Publicaties` VALUES (1,10),(2,10),(3,16),(4,16),(5,17),(6,16),(6,18),(7,17),(7,19);
/*!40000 ALTER TABLE `Publicaties` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `Reviews`
--

DROP TABLE IF EXISTS `Reviews`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `Reviews` (
  `Boeken_Id` int(11) NOT NULL,
  `Rating` tinyint(4) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Reviews`
--

LOCK TABLES `Reviews` WRITE;
/*!40000 ALTER TABLE `Reviews` DISABLE KEYS */;
INSERT INTO `Reviews` VALUES (1,4),(1,5),(1,5),(2,5),(3,3),(3,4),(3,4),(3,5),(4,4),(5,3),(6,4),(7,3);
/*!40000 ALTER TABLE `Reviews` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

Oefening 1

Maak een view aan met naam AuteursBoeken waarmee je makkelijk een overzicht kan vragen van welke auteur welk(e) boek(en) heeft geschreven.

Je oplossing zou er exact zo moeten uitzien:

Noem het script om deze view aan te maken 0513__Oefening.sql.

Oefening 2

Pas, niet rechtstreeks in de tabel Boeken maar wel via de view AuteursBoeken, de titel Pet Sematary aan naar Pet Cemetery.

Noem het script hiervoor 0514__Oefening.sql.

Wat gebeurt er als je de naam "Stephen King" via deze view in "Steven King" wil veranderen? Waarom, denk je?

Oefening 3 (drie stappen)

We willen een complexere versie van de bestaande view AuteursBoeken. We zullen hierbij ook de gemiddelde rating van elk boek plaatsen in een uitgebreide versie van de view, namelijk AuteursBoekenRatings. We zullen dit in stappen doen. Lees eerst de stappen, bekijk dan de figuren, voer dan de stappen uit.

  1. Gebruik een ALTER VIEW om je bestaande view AuteursBoeken te voorzien van het Id uit de tabelBoeken. Toon Id hier wel als Boeken_Id. Noem het script dat de view aanpast 0515__Oefening.sql.

  2. Maak een view GemiddeldeRatings aan op basis van Reviews. Noem de kolom met het gemiddelde Rating. Noem het script dat de view maakt 0516__Oefening.sql.

  3. Maak de viewAuteursBoekenRatings aan door een nieuwe view te maken gebaseerd op AuteursBoeken en GemiddeldeRatings. Noem het script dat de view maakt 0517__Oefening.sql.

Oefening 4

Vraag via de metadatabank op welke van je views updatable zijn en welke niet. Schrijf voor de meest complexe van de updatable views een UPDATE per kolom om te testen of je die kolom kan updaten. Je hoeft het script niet te bewaren, maar je moet in een gelijkaardige situatie (bv. op het examen) wel kunnen zeggen welke kolommen updatable zijn.

Besluit

Uit de laatste oefening zie je dat er meerdere redenen zijn om een view op te bouwen uit andere views. Enerzijds om stap per stap het resultaat op te bouwen. Anderzijds omdat een view die niet-updatable views bevat zelf toch nog updatable kan zijn. Uiteraard zijn niet alle kolommen dan noodzakelijk updatable, maar het kan beter zijn dan het opbouwen van één grote niet-updatable view.

UPDATE

Views kunnen onder bepaalde omstandigheden worden aangepast, ttz. de inhoud van de view kan op dezelfde wijze worden benaderd als een gewone tabel.

Er zijn hierbij wel enkele uitzonderingen in die zin dat bij de creatie van de view de query o.a. geen van volgende statements mag bevatten.

  • MIN, MAX, SUM, AVG en COUNT

  • DISTINCT

  • GROUP BY

  • HAVING

  • UNION

  • LEFT JOIN of RIGHT JOIN (en dus ook de EXCLUDING versies en OUTER JOIN via de workaround)

  • Merk op: INNER JOIN mag wel!

Waarom zijn juist deze clausules niet toegelaten? De meeste ervan groeperen informatie op zo'n manier dat je de groepering niet altijd ongedaan kan maken. De niet-toegelaten JOINs combineren informatie met NULL-waarden. Het zou niet zinvol zijn deze NULL-waarden aan te passen, want de aanpassingen zouden verloren gaan bij het herberekenen van de view.

UPDATE VIEW

USE ApDB;
UPDATE TakenLeden
SET Omschrijving = 'frisdrank voorzien'
WHERE Voornaam = 'Yannick';

Inspecteer TakenLeden en Leden nadat je deze opdracht hebt uitgevoerd. Wat zie je?

In een updatable view kan bepaald worden waar dergelijke aanpassingen echt moeten plaatsvinden. Volgende figuur toont het idee:

Er zijn helaas grenzen aan wat mogelijk is. Experimenteer met enkele DML-operaties met TakenLeden. Probeer o.a. een INSERT en een DELETE van telkens één rij.

UPDATE VIEW INFORMATIE

Je kan nagaan of een view aanpasbaar is door in de metadatabank te kijken.

SELECT table_name, is_updatable
FROM information_schema.views
WHERE table_schema = 'ApDB';

Labo oefeningen

Start vanaf het gegeven calibratiescript, 0526__CalibrateDB.sql. Dit vult de database ApDB in met onderstaande structuren:

De database die je hier krijgt is niet elegant ontworpen, maar ze maakt de subqueries die we hier zoeken makkelijker uit te schrijven dan een beter opgesplitste database.

Scalaire subqueries

Oefening 1

Schrijf een script, 0527__Oefening.sql, dat je alle voornamen van studenten toont die korter zijn dan gemiddeld.

Het formaat is:

Reminder: in de cursus is een functie terug te vinden om de lengte van een stuk tekst te bepalen. Gebruik het vergrootglas op Gitbook.

Oefening 2

Schrijf een script, 0528__Oefening.sql, dat je al de recentste evaluaties toont. Dat wil zeggen: alle evaluaties die hebben plaatsgevonden op de laatste dag dat er nog evaluaties waren.

Oefening 3

Schrijf een script, 0529__Oefening.sql, dat je alle studenten (enkel de Id's) toont die een hoger persoonlijk puntengemiddelde hebben dan het algemene gemiddelde. Het persoonlijk gemiddelde is dus het gemiddelde over alle evaluaties van enkel die student en het algemene gemiddelde is het gemiddelde over alle evaluaties in het algemeen.

Tips:

  • evaluaties van één student neem je samen door te groeperen op het ID van de student aan wie de evaluatie toebehoort

  • gebruik daarna having om het gemiddelde van één student te vergelijken met het algemene gemiddelde

    • het algemene gemiddelde vraag je me een scalaire query

Subqueries die een kolom produceren

Oefening 4

Schrijf een script, 0530__Oefening.sql, dat je de voornamen en familienamen toont van alle studenten die aangesloten zijn bij de studentenvereniging. Doe dit zonder gebruik te maken van JOIN. (Tip: een student is aangesloten bij de vereniging als er een rol bestaat die naar die student verwijst...)

Het formaat:

Oefening 5

Schrijf een script, 0531__Oefening.sql, dat je de voornamen toont die voorkomen bij studenten en personeel en directie.

Oefening 6

Toon in een script 0532__Oefening.sql de namen van directieleden die meer verdienen dan om het even welke werknemer, zonder gebruik te maken van max.

Het formaat:

Oefening 7

Toon in een script 0533__Oefening.sql de namen van directieleden die minder verdienen dan minstens één werknemer, zonder gebruik te maken van max.

Het formaat:

Derived tables / afgeleide tabellen

Oefening 8

Toon in een script 0534__Oefening.sql het laagste puntengemiddelde van alle studenten. Je hoeft de naam van de student er niet bij te tonen.

Het formaat:

Subqueries met tijdelijke opslag

Subqueries om met een waarde te vergelijken

scalars

Subqueries komen vaak van pas om één waarde te berekenen die dan gebruikt wordt in een grotere query. Wanneer we een query gebruiken om precies één waarde te produceren, noemen we die waarde een scalar.

Volgende query produceert een scalar:

Hier is geen group by aanwezig, dus het gemiddelde wordt over alle personen beschouwd. We zijn ook alleen geïnteresseerd in de leeftijd. Bijgevolg hebben we uiteindelijk maar één rij en één kolom, dus één uiteindelijk waarde. Met andere woorden: een scalar.

Scalars zijn belangrijk omdat een belangrijke groep subqueries specifiek bedoeld is om één resultaat te produceren. We spreken dan over scalaire subqueries. In oude databasesystemen konden subqueries zelfs alleen gebruikt worden om te vergelijken met een scalar! In nieuwere systemen kan je een scalaire subquery gewoonweg gebruiken waar je een enkele waarde kan gebruiken.

vergelijkingen met behulp van scalaire subqueries

We kunnen een subquery gebruiken om de oudste personen in onze database te tonen, zonder dat we op voorhand weten over welke leeftijdsgroep we het dan hebben.

Als de oudste persoon in onze database 91 jaar oud is, krijgen we info over iedereen die 81 jaar of ouder is. We kunnen ook kijken naar iedereen die "ongeveer de gemiddelde leeftijd" heeft:

Er is hier niets speciaals aan max of avg. Je kan ook andere functies gebruiken die je al kent. Je kan ook andere vergelijkingen gebruiken: =, >, <, >=, <=, <>, !=, <=> en zelfs LIKE zijn mogelijk.

Nog een voorbeeldje:

"Studenten die minder hbben behaald dan het gemiddelde" gaat zo, bij een tabel Studenten met onder andere een kolom Cijfer:

Je kan in principe gebruik maken van subqueries in om het even welke WHERE, niet alleen in een WHERE die hoort bij een SELECT. Let op, in MySQL heb je hier een belangrijke beperking: je kan de tabel niet aanpassen die je gebruikt voor de geneste SELECT. Iets als dit, waarbij we de jongste personen willen wissen, gaat dus niet:

Dit kan je wel oplossen door middel van sessievariabelen.

sessievariabelen

Een sessievariabele is vergelijkbaar met een variabele uit een general purpose programmeertaal zoals C# of TypeScript. Het is met andere woorden een koppeling tussen een naam en een waarde. Je kan een sessievariabele als volgt een waarde geven:

Een sessievariabele begint altijd met @. Hiermee kan je bovenstaande beperking op subqueries dus ook omzeilen, want je kan het volgende doen:

Wanneer je verbinding met de MySQL-databank verbroken wordt, verdwijnen al je sessievariabelen.

Subqueries voor vergelijkingen met lijsten resultaten

Scalaire subqueries zijn niet de enige subqueries die we hebben. Als je subquery één kolom als resultaat produceert, kan je deze kolom gebruiken als een lijst waarden waarmee je wil vergelijken. De meest gebruikte manier om een waarde en een lijst te vergelijken is door na te gaan of de waarde gewoonweg voorkomt in die lijst. Dat is ook wat we eerder deden met het sleutelwoordje IN, dus het zal niet verbazen dat IN gevolgd mag worden door een subquery die een kolom produceert.

Temporary tables

Subqueries van bovenstaande vorm kunnen nog meer doen, maar op het eerste gezicht zijn er veel beperkingen waar we hier niet dieper op in gaan. Om hier rond te werken, kan je gebruik maken van temporary tables of tijdelijke tabellen. Deze lijken op sessievariabelen, maar ze dienen niet om een scalar op te slaan. Ze dienen om één of meerdere kolommen tijdelijk op te slaan. Ze verdwijnen ook wanneer je de connectie verbreekt.

Een tijdelijke tabel aanmaken doe je via CREATE TEMPORARY TABLE. De syntax is verder dezelfde als voor een gewone tabel. Een tijdelijke tabel is ook alleen maar zichtbaar binnen je eigen verbinding. Er is dus geen risico dat iemand anders tegelijk dezelfde naam voor een tijdelijke tabel gebruikt als jij.

Tijdelijke tabellen kunnen complexere queries behapbaar maken. Veronderstel bijvoorbeeld dat we voor een demografische analyse willen weten welke naam gemiddeld de "jongste" is. We willen dus per naam de gemiddelde leeftijd berekenen en daarvan willen we het minimum.

Dit gaat niet:

Dat komt omdat we met onze group by hebben aangegeven dat we onze functies willen toepassen over groepjes. avg kan je toepassen op een groepje leeftijden, maar dat levert dan één uitkomst. Daar kan je niet nog een keer min op toepassen. Je bedoelt dat min over heel de resulterende kolom moet worden toegepast, maar dat wordt zo niet geïnterpreteerd door MySQL.

Wat wel werkt, is dit:

Hier zijn kortere oplossingen voor, meerbepaald materialized subqueries, maar deze oplossing is makkelijker verstaanbaar en heeft minder beperkingen.

M.a.w. de zijn ook hier te gebruiken (met bepaalde technische beperkingen).

We baseren ons op de view die we onder de rubriek hebben aangemaakt. Deze gebruikt een INNER JOIN, maar dat verbiedt niet dat we updates doen.

DML-commando's
CREATE

Voornaam

korte voornaam 1

korte voornaam 2

korte voornaam 3

...

Studenten_Id

Vakken_Id

Cijfer

Id 1

Id 4

Cijfer 1

Id 2

Id 5

Cijfer 2

Id 3

Id 6

Cijfer 3

Id

Id 1

Id 2

Id 3

...

...

Voornaam

Familienaam

Voornaam 1

Familienaam 1

Voornaam 2

Familienaam 2

Voornaam 3

Familienaam 3

Voornaam

Voornaam 1

Voornaam 2

Voornaam 3

Voornaam

Familienaam

Voornaam 1

Familienaam 1

Voornaam 2

Familienaam 2

Voornaam 3

Familienaam 3

Voornaam

Familienaam

Voornaam 1

Familienaam 1

Voornaam 2

Familienaam 2

Voornaam 3

Familienaam 3

Gemiddelde

Een cijfer

(Hier staat maar één rij!)

select avg(Leeftijd) from Personen;
select Voornaam, Familienaam
from Personen
where Leeftijd >= (select max(Leeftijd) from Personen) - 10;
select Voornaam, Familienaam
from Personen
where Leeftijd between (select avg(Leeftijd) from Personen) - 5 AND (select avg(Leeftijd) from Personen) + 5;
select Naam
from Studenten
where Cijfer < (select avg(Cijfer) from Studenten)
delete from Personen
where Leeftijd = (select min(Leeftijd) from Personen);
set @mySessionVariable = (select min(Leeftijd) from Personen);
set @minimumLeeftijd = (select min(Leeftijd) from Personen);
delete from Personen
where Leeftijd = @minimumLeeftijd;
select Voornaam, Familienaam
from Personen
where Voornaam in (select distinct Familienaam from Personen);
select min(avg(Leeftijd))
from Personen
group by Voornaam;
-- tijdelijke tabel maken
drop temporary table if exists gemiddeldeLeeftijdPerNaam;
create temporary table gemiddeldeLeeftijdPerNaam (
Naam varchar(200) not null,
Leeftijd int not null
);
-- tijdelijke tabel invullen
insert into gemiddeldeLeeftijdPerNaam
(select Voornaam, avg(Leeftijd) from Personen group by Personen.Voornaam);
-- het resultaat aflezen
select min(Leeftijd) from gemiddeldeLeeftijdPerNaam;
392KB
0526__Calibratie.sql
0526__Calibratie.sql

DROP

Indexen belasten het systeem als deze niet nodig zijn, daarom is het raadzaam om indexen die niet (meer) nodig zijn te verwijderen. De reden van deze belasting kun je vinden in het feit dat indexen moeten worden bijgewerkt zodra de data in een tabel worden aangepast. Bovendien kun je indexen niet aanpassen of wijzigen, hiervoor moet je de index verwijderen en opnieuw aanmaken volgens de nieuwe ingevingen.

Drop index

Om een bestaande index te verwijderen van een tabel gebruik je het drop index statement.

DROP INDEX index_name ON table_name;

Drop primary key

De primary key is een index en door middel van dit commando kan je deze index verwijderen.

DROP INDEX `PRIMARY` ON table_name;

CREATE

Standaardgebruik

Een index toevoegen doe je in het algemene geval met volgende syntax:

CREATE INDEX Column1Column2Index on TableName(Column1,Column2);

Dit maakt een index op de combinatie van de kolommen Column1 en Column2. Als er maar één kolom is, laat je de tweede gewoon achterwege. Meer dan twee kolommen kan ook.

De volgorde is hier van belang. Het telefoonboek is bijvoorbeeld een soort index op gemeente, familienaam en voornaam. Je kan het alleen maar efficiënt gebruiken als je de eerste kolom kent en het wordt efficiënter naarmate je meer kolommen kent.

Prefix index

Wanneer je een secundaire index voor een kolom maakt, dus een index die niet over de primaire sleutel gaat, slaat MySQL de waarden van de kolommen op in een afzonderlijke gegevensstructuur. Dit zijn structuren waarin je snel kan opzoeken.

In het geval dat de kolommen de tekenreekskolommen zijn (zoals VARCHAR), zal de index veel schijfruimte in beslag nemen en mogelijk de INSERT-bewerkingen vertragen.

Om dit probleem aan te pakken, kan je met MySQL een index maken voor het leidende deel van de kolomwaarden van de tekenreekskolommen met behulp van de volgende syntax:

column_name(length)

Je kan een index op twee manieren creëren.

1. bij creatie van de tabel zelf

-- mogen meerdere colum_names zijn, gescheiden door komma
-- length is optioneel
CREATE TABLE Table_name(
column_list,
INDEX(column_name(length))
);

2. nadien toevoegen aan een bestaande tabel

-- zelfde opmerking als boven
-- je mag een index op meerdere kolommen samen maken
CREATE INDEX index_name
ON table_name(column_name(length));

In bovenstaande voorbeelden is de lengte het aantal tekens voor de datatypes zoals VARCHAR (en het aantal bytes voor binaire datatypes).

Voorbeeld

Taken

omschrijving

Id

bestek voorzien

1

frisdrank meebrengen

2

aardappelsla maken

3

...

...

pasta koken

4000

Als we onderstaande query uitvoeren, dan zal heel de tabel worden doorlopen omdat er geen index is bepaald op het veld waarop het zoekcriterium werd ingesteld.

USE ApDB;
SELECT *
FROM Taken
WHERE Omschrijving LIKE 'aardappel%';

Als we deze query met het EXPLAIN statement uitvoeren, krijgen we volgende resultaat.

USE ApDB;
EXPLAIN SELECT *
FROM Taken
WHERE Omschrijving LIKE 'aardappel%';

Als je de tabel taken veel bevraagt terwijl je filtert op de omschrijving, is het zinvol om voor deze kolom een index te creëren waardoor de uitvoering een heel stuk sneller zal verlopen.

De grootte van de kolom Omschrijving is tijdens het ontwerp ingesteld op 50 karakters, maar dat wil niet zeggen dat we in de index 50 karakters per mogelijkheid moeten bijhouden. Voor de index moet je de lengte van het voorvoegsel bepalen. Soms wordt er gezegd dat je dit zo efficiënt mogelijk dient te doen door de prefixlengte zo kort mogelijk te houden. Hier schuilt wel een gevaar in: wanneer de tabel met nieuwe data wordt uitgebreid, is de index misschien niet meer zo uniek. Dat leidt niet tot fouten maar mogelijk wel tot performantieverlies.

Hoe zoek je nu de ideale lengte van de prefix op? Een vuistregel: zorg dat de index meteen naar een uniek resultaat leidt, maar dat hij niet groter is dan nodig om dit te bereiken.

Stap 1: zoek het aantal verschillende waarden in de kolom op

select count(distinct Omschrijving)
from Taken;

Stap 2: bepaal de maximale lengte van een waarde in de kolom

select max(length(Omschrijving)) from Taken;

Stap 3: zoek de minimale lengte waarbij je geen informatie verliest. Dit kan nooit meer zijn dan het resultaat van stap 2. Als de langste kolomwaarde bijvoorbeeld 21 is, start je door te kijken of 20 karakters al dan niet zorgen voor informatieverlies. Via LEFT krijg je alleen de eerste (aantal) tekens uit een string.

select count(distinct left(Omschrijving,20))
from Taken;

Zodra er minder resultaten zijn, heb je de prefix te kort gekozen. Het antwoord is dus één meer dan de lengte waarbij het aantal resultaten begint te krimpen.

Indien 20 de perfecte lengte van de prefix is, dan gaan we de index opbouwen.

USE ApDB;
CREATE INDEX OmschrijvingIdx
ON Taken(Omschrijving(20));

Binnen de schema navigation kan je nu de index zien. Als je nu bovenstaande select-query opnieuw uitvoert zal deze efficiënter en sneller verlopen.

De lengte van het "ideale" prefix kan wijzigen naarmate je meer data toevoegt aan een tabel.

Zelfs als je de ideale prefixlengte gebruikt, houdt het gebruik van een prefix gevolgen in. MySQL kan bij gebruik van een prefix niet garanderen dat twee waarden op dezelfde plaats in de index identiek zijn. Sommige queries kunnen hierdoor trager lopen bij een prefixindex dan bij een index zonder prefix.

De rol van kolomvolgorde

Als je een index op meerdere kolommen aanmaakt, is de volgorde van deze kolommen belangrijk. Vergelijk met het telefoonboek. Daar heb je een index op plaatsnaam en familienaam. Dit staat je toe snel iemand terug te vinden. Als je geen informatie kent over hoe de plaatsnamen geordend zijn (stel je voor dat alle gemeentes willekeurig in het telefoonboek stonden in plaats van alfabetisch), moet je toch elke pagina doorzoeken om zeker te zijn dat je de persoon in kwestie niet over het hoofd ziet.

Wat betekent dit in de praktijk? Je kan een index op meerdere kolommen (bijvoorbeeld A, B en C) gebruiken om snel opzoekingen te doen als je werkt met een doorlopende reeks van de eerste kolommen (bv. A of A en B of A, B en C). Maar je kan niet sneller zoeken met deze index als je bijvoorbeeld enkel B en C betrekt in je zoekopdracht.

ERD

We gebruiken de MySQL ER-editor om het ER-diagram aan te maken en dit dan vervolgens naar een tabelstructuur om te zetten.

trage queries opvolgen

Stap 1: bijhouden

Met de juiste instellingen kan je MySQL queries laten onthouden die langer duren dan een zekere drempelwaarde. Als het gaat om queries die regelmatig terugkomen, is het de moeite waard indexen aan te maken die deze queries kunnen versnellen.

Je ziet in MySQL Workbench hoe lang het duurt om een query uit te voeren:

Je kan de trage queries ook opslaan door in het bestand my.cnf in je MySQL-installatie de variabele slow_query_log op On te zetten, long_query_time op het aantal seconden dat "te veel" is in te stellen (bv. 0.2 voor 200 milliseconden) en slow_query_log_file in te vullen met de locatie van het gewenste logbestand. Alle queries die je vanaf dan uitvoert zullen in het logbestand worden bijgehouden.

Stap 2a: uitpluizen zonder MySQL Workbench

Je voert de queries in kwestie opnieuw uit, maar je laat ze voorafgaan door het sleutelwoordje EXPLAIN. Je krijgt dan één rij per doorzochte tabel in een uitgebreide versie van volgend formaat:

table

key

rows

naam van een doorzochte tabel

eventueel gebruikte index

aantal rijen doorzocht

naam van een andere doorzochte tabel

eventueel gebruikte index

aantal rijen doorzocht

Als er voor een index NULL staat, betekent dat dat alle rijen van de eerste tot de laatste zijn beschouwd, wat typisch erg inefficiënt is (zeker als het aantal rijen groot is). Door een index toe te voegen aan een kolom met een gezochte waarde (bv. een kolom vermeld in een WHERE), kan je de NULL wegwerken en het aantal doorzochte rijen drastisch verlagen.

Stap 2b: uitpluizen met MySQL Workbench

Volgende werkwijze is alleen mogelijk als je script één statement bevat. Laat in dit geval USE <naam-van-je-databank>; achterwege, want dat is op zich al een statement.

MySQL Workbench heeft ook een grafische weergave van je uitvoeringsplan. Deze kan je bekijken na het uitvoeren van je statement via onderstaande knop:

Als je op een element van het diagram gaat staan met je muis, krijg je ook een technische uitleg over waarom dat onderdeel van je query waarschijnlijk niet efficiënt is. Let op! Het betreft hier nog steeds vuistregels. Een "full table scan" is bijvoorbeeld geen ramp als je tabel erg klein is. Dat wordt ook vermeld in de kleine lettertjes.

Stap 3: algoritme om te optimaliseren voor een query

Als je een bepaalde SELECT-query wil optimaliseren, kan je onderstaand stappenplan volgen:

  1. Als er een WHERE voorkomt in je SELECT, voorzie dan één index op alle kolommen samen die via AND verbonden zijn en die rechtstreeks vergeleken worden met een constante waarde.

  2. Voeg aan je index ook de eerste van de volgende mogelijkheden toe die je hieronder krijgt.

    a. Een kolom die niet rechtstreeks wordt vergeleken met een waarde, maar wel in een bereik moet liggen (via BETWEEN, >, <, LIKE waarbij het eerste karakter geen wildcard is)

    b. Alle kolommen die in een GROUP BY worden gebruikt, in de volgorde waarin ze in de GROUP BY worden vermeld

    c. Alle kolommen die in een ORDER BY worden gebruikt, in de volgorde waarin ze in de ORDER BY worden vermeld, maar enkel als er geen mix van ASC en DESC voorkomt in de ORDER BY

Als er staat dat een kolom rechtstreeks vergeleken wordt met een waarde, betekent dat bijvoorbeeld dat er staat WHERE MYCOLUMN = .... Wat niet zou gaan is WHERE RIGHT(MYCOLUMN,3) = ... omdat we daar de kolom eerst verder verwerken en dan pas vergelijken. Dan heeft een index op de kolom geen zin.

UNIQUE

De primary key is een unieke index bij creatie. Er kan echter slechts één primary key per tabel worden gedefinieerd en indien je toch op een andere kolom ook een unieke index wil leggen kan je dit door een index te bepalen die uniek is. Je kan meerdere unieke indexen binnen één tabel hebben, maar slechts één primary key. De data zullen dan niet fysiek opgeslagen zijn in volgorde van deze unieke index, maar er zal wel een hulpstructuur bijgehouden worden die voor elke unieke waarde zegt waar je ze kan terugvinden.

CREATE UNIQUE INDEX index_name
ON table_name(index_column_1,index_column_2,...);

Er zijn twee manieren om een unieke index te creëren, de eerste bij creatie van de tabel of in het tweede geval nadien.

Bij creatie van de tabel doe je dit als volgt.

CREATE TABLE table_name(
//...
UNIQUE KEY(index_column_1,index_column_2,...) 
);

Deze syntax is algemeen en zegt dat een combinatie van waarden uniek moet zijn. Als je index maar één kolom gebruikt, kan je dit doen met een UNIQUE constraint op die kolom, bijvoorbeeld:

CREATE TABLE table_name(
//...
ColName VARCHAR(100) UNIQUE
);

Voorbeeld

We creëren een tabel met een primary key en een unieke index.

CREATE TABLE IF NOT EXISTS People (
    id INT AUTO_INCREMENT PRIMARY KEY,
    first_name VARCHAR(50) NOT NULL,
    last_name VARCHAR(50) NOT NULL,
    mail VARCHAR(50) NOT NULL,
    UNIQUE KEY index_unique_mail (mail)
);

Met het commando SHOW INDEXES FROM table_namekan je zien dat de unieke index werd aangemaakt.

SHOW INDEXES FROM People;

Vervolgens voegen wat data toe aan de tabel People.

INSERT INTO People(first_name,last_name,mail)
VALUES
('John','Doe','john.doe@modernways.be'),
('Jane','Doe','jane.doe@modernways.be'),
('John','Roe','john.roe@modernways.be'),
('Jane','Roe','jane.roe@modernways.be')
;

Omdat we op mail een unieke index hebben gelegd is het niet meer mogelijk personen toe te voegen met opgave van een mailadres dat al bestaat. Concreet, wanneer we Jef Doe met als mailadres john.doe@modernways.be zouden willen toevoegen, zal dit resulteren in een fout, want het opgegeven mailadres komt reeds voor.

INSERT INTO People(first_name,last_name,mail)
VALUES ('Jef','Doe','john.doe@modernways.be');

mogelijkheden

Toepassingen van indexen

Indexen kunnen nuttig zijn in vaak voorkomende scenario's:

  • in queries met een WHERE-clause die de waarde van een geïndexeerd attribuut controleert kunnen veel rijen op voorhand worden uitgesloten (bijvoorbeeld WHERE Personen.Voornaam = 'Elias' sluit al veel mogelijkheden uit)

  • om snel minima en maxima te bepalen (zoals hoe een woordenlijst vaak alfabetisch is, zijn ook indexen op kolommen met getalwaarden vaak gesorteerd)

  • om JOIN-operaties op geïndexeerde attributen te versnellen (herinner je dat INNER JOIN ON in MySQL net hetzelfde doet als een CROSS JOIN WHERE)

  • ...

Belangrijke types index in MySQL

Niet alle indexen zijn hetzelfde. Zoals eerder aangegeven kunnen indexen gebaseerd zijn op één kolom, of op meerdere kolommen. Een index met strengere voorwaarde zal typisch een grotere snelheidswinst opleveren dan een index met zwakkere eisen, omdat je sneller weet of je klaar bent met zoeken. Volgende indexen zijn voorzien in MySQL (en stemmen in sommige gevallen overeen met constraints op je data zoals UNIQUE):

  • Primaire sleutels: deze heb je meestal sowieso al aangemaakt. Primaire sleutelkolommen worden vaak gebruikt voor zoekoperaties en WHERE-clauses, dus het is logisch om ze automatisch in een index op te nemen. MySQL houdt rijen intern bij in een gesorteerde volgorde op basis van de primaire sleutel en dankzij deze sortering kan er efficiënt gezocht worden.

  • Unieke indexen: deze kan je definiëren op (verzamelingen van) kolommen met unieke waarden. De data kunnen niet fysiek opgeslagen worden in een volgorde bepaald door deze index (omdat de primaire sleutel de volgorde al bepaalt), maar er kan een extra datastructuur met de unieke waarden worden bijgehouden.

  • Gewone indexen: deze kan je definiëren op kolommen met waarden die niet noodzakelijk uniek zijn. Ze mogen ook NULL bevatten.

  • Fulltext indexen: deze dienen voor kolommen die een (lang) blok tekst bevatten. De tekst wordt opgesplitst in individuele woorden, die dan geïndexeerd worden. Zo kan je efficiënt blokken tekst zoeken die één specifiek woord bevatten.

Wanneer heb ik nu een index nodig?

Je zal waarschijnlijk pas indexen nodig hebben als je met grotere databases gaat werken. Langs de andere kant: eens je op dat punt zit, worden enkele zorgvuldig gekozen indexen snel onmisbaar. Het is wanneer je database stilaan onder het gewicht van bepaalde queries begint te kreunen, dat je een echt geïnformeerde beslissing kan nemen. Je doet dit door twee dingen op te meten:

  • welke soorten queries vaak uitgevoerd worden

  • hoe "duur" dergelijke queries zijn

Als je weet dat bepaalde queries vaak voorkomen en/of veel zoekwerk doorheen kolommen vereisen, is het zinvol de betrokken kolommen te indexeren op een manier die goed past bij hun typische gebruik. In de praktijk zal je ook de eventuele snelheidswinsten die je indexen opleveren opmeten. Hiervoor gebruik je het EXPLAIN-statement, dat een overzicht geeft van hoe een bepaald statement is uitgevoerd.

Referentiële beperkingen

Referentiële beperkingen

Als twee entiteiten gelinkt zijn, kan een aanpassing van de ene soms een aanpassing van de andere noodzakelijk maken.

Een voorbeeld: een gebruiker van een webshop schrijft zich uit en vraagt (zoals dat ook kan onder GDPR) dat al zijn/haar persoonlijke informatie uit de webshop verwijderd wordt. Dit omvat mogelijk rijen in een tabel Bezorgadressen (want één gebruiker kan meerdere geregistreerde adressen hebben), Bestellingen,... Meerbepaald: alle rijen die via een foreign key verwijzen naar de te wissen gebruiker.

Probeer volgende code:

Het lukt om Claude te wissen, maar niet om Edelgard of Dimitri te wissen. Dit komt omdat er (in andere tabellen) nog data met deze records geassocieerd is. Er is hier een referentiële beperking, dit wil zeggen een beperking die opgelegd wordt omdat er een verwijzing tussen data is. Die verhindert dat Edelgard en Dimitri gewist worden. Voor Claude is er geen verwijzende data, dus hij kan gewist worden.

Gedrag bij een aanpassing instellen

Er zijn in MySQL verschillende veelgebruikte referentiële beperkingen. Deze worden allemaal genoteerd na de defnitie van een foreign key. Ze drukken dus uit wat er moet gebeuren wanneer de waarde waarnaar verwezen wordt wijzigt.

Ze worden allemaal met volgende syntax genoteerd:

of

ON UPDATE RESTRICT / ON DELETE RESTRICT

Dit is de defaultoptie in MySQL. Ze betekent dat deze instructies gewoonweg niet toegelaten zijn als er een verwijzing bestaat. Dit is de reden dat het in bovenstaand voorbeeld wel mogelijk is Claude te wissen, maar niet Edelgard of Dimitri. Om dezelfde reden kan men het Id van Edelgard en Dimitri niet aanpassen.

ON DELETE CASCADE

Dit is geschikt als het geen zin heeft om de gekoppelde data te bewaren. Met deze constraint zou (in het voorbeeld hierboven) een DELETE van "Edelgard" ook inhouden dat "Adrestia street 1" gewist wordt.

ON UPDATE CASCADE

Dit zorgt ervoor dat een verwijzing mee wijzigt met de brondata. Een aanpassing van het Id van "Edelgard" van 1 naar 4 zou dan automatisch inhouden dat de kolom Customers_Id van "Adrestia street 1" mee gewijzigd zou worden naar 4.

ON DELETE SET NULL

Dit kan zinvol zijn als een verwijzing niet noodzakelijk is. Zo zou het adres van een klant bewaard kunnen worden wanneer de klant zelf verwijderd wordt. Merk op dat dit in bovenstaand voorbeeld niet kan werken, omdat Customers_Id nooit NULL mag zijn.

ON UPDATE SET NULL

Dit gedraagt zich hetzelfde als ON DELETE SET NULL, maar dan bij een UPDATE. Dit is technisch mogelijk, maar zelden zinvol. Ook hier wordt verondersteld dat de foreign key NULL mag bevatten.

Het is mogelijk een verschillende actie in te stellen voor beide types wijzigingen, i.e. DELETE en UPDATE. Het is niet mogelijk meerdere acties in te stellen voor dezelfde wijziging.

De eerste timing drukt uit hoe lang het duurt om de query uit te voeren. De tweede drukt uit hoe lang het duurt om de rsultaten op te halen en wordt niet verbeterd door een index.

Als je op deze knop klikt, krijg je een diagram waarin wordt uitgelegd welke stappen nodig zijn om je query uit te voeren. Een volledig overzicht van de betekenis van de elementen in zo'n diagram vind je . Je hoeft niet elk onderdeel te kennen, maar bekijk de pagina kort. Belangrijk is dat je rode boxes met daarin "full table scan" zo veel mogelijk vermijdt, zeker als het gaat om tabellen met veel data in.

Bovenstaand stappenplan is afkomstig uit . Dit is een zeer nauwe verwant van MySQL. Als je de pagina bekijkt, zie je dat het laatste woord over indexen nog niet gezegd is. Voor deze cursus volstaat het algoritme, maar als je ooit in je carrière geconfronteerd wordt met performantieproblemen in een database, raadpleeg dan deze uitleg voor je nieuwe (virtuele) hardware aankoopt!

Wat met NULL-waarden in combinatie met unieke indexen? Zie . De waarden zijn dus niet noodzakelijk 100% uniek, dit in tegenstelling tot sommige andere databasesystemen.

Je vindt een officiële lijst terug van scenario's waarin MySQL zinvol gebruik kan maken van indexen. Let wel op: indexen hebben niet alleen voordelen. Anders zouden we op elk attribuut en op elke combinatie van attributen een index definiëren. Elke index neemt plaats in en moet onderhouden worden, wat betekent dat de schrijfperformantie van het systeem een beetje daalt.

hier
de documentatie van MariaDB
de officiële documentatie
hier
create table Customers (
  Id int auto_increment primary key,
  Name varchar(100) not null
);
create table DeliveryAddresses (
  Id int auto_increment primary key,
  Street varchar(100) not null,
  HouseNumber int not null,
  Customers_Id int not null,
  constraint fk_DeliveryAddresses_Customers
  foreign key (Customers_Id)
  references Customers(Id)
);
insert into Customers (Name)
values
('Edelgard'),
('Dimitri'),
('Claude');
insert into DeliveryAddresses (Street, HouseNumber, Customers_Id)
values
('Adrestia street', 1, 1),
('Faerghus avenue', 100, 2);
-- eerdere code
REFERENCES SomeTable(Column1) -- vaak 1 kolom, kan in principe wel meer zijn
[ON DELETE action]
-- eerdere code
REFERENCES SomeTable(Column1) -- vaak 1 kolom, kan in principe wel meer zijn
[ON UPDATE action]
Logo