Professional Documents
Culture Documents
1. Inleiding
Bij databases begint het bij een vraag om informatie. Hoe en waar ga je die opslaan? Allereerst zal je de
wensen van de klant moeten vast leggen alvorens een oplossing te komen. Daarna kun je het technische plan
maken en tot uitvoering brengen. Dit kan soms heel erg complex worden. Vandaar dat een goede
voorbereiding essentieel is. Denk daarbij aan het maken van een analyse en een goed ontwerp.
In dit dictaat worden alle relevante zaken die nodig zijn omtrent databases en SQL kort behandeld. We
beginnen bij het EER-model en gaan vanuit daar verder met het databaseontwerp (DBO), logische
expressies en constraints, DDL en DML, SQL en tot slot T-SQL. In dit laatste gedeelte kun je informatie vinden
over het programmeren in de database met T-SQL. Daar worden variabelen, if-statements, loops, functies en
procedures, triggers en cursors in behandeld.
Let op: Dit dictaat is in ontwikkeling. Mocht je er onduidelijkheden of zelfs fouten in aantreffen laat het de
huidige vakeigenaar dan weten. Hij of zij zal het dan rechtzetten. Bij voorbaat dank voor je hulp! Samen maken
we er een goed te lezen dictaat van.
-4-
augustus 2016
2. EER-model
Een klant brengt graag in al zijn enthousiasme het verhaal over van zijn probleem of de opdracht. Dit is vaak
een onsamenhangend verhaal waarin hij niet goed weet wat er precies gedaan moet worden. Om daar
structuur in te krijgen kan er na het gesprek een model gemaakt worden van het domein van de klant. In zo’n
model worden alle relevante zaken opgenomen die belangrijk kunnen zijn voor de technische invulling van de
opdracht. Die technische invulling komt later wel, eerst het model!
In dit document gebruiken we daar ER-modellen voor. In dit hoofdstuk staat we kort stil bij de onderwerpen
die daarbij een rol spelen.
Iets op (digitaal) papier aanpassen is gemakkelijker dan in code. Wellicht nu nog niet, nu alles nog
overzichtelijk is in deze fase van je studie. Er komt echter en dag waarop je zelf ook terug kijkt naar je code en
dan graag een visuele houvast had willen hebben, bijvoorbeeld een model.
Er zijn eigenlijk maar 3 (of 4) verschillende vormen nodig bij het tekenen van een ER-model: vierkanten, ruiten
en ellipsen (en lijnen). Dat is eenvoudig te overzien en te bevatten. Ook voor iemand met gewoon gezond
verstand, maar zonder een technische achtergrond of ervaring in de ICT.
De klant kan na afloop van de eerste meeting over de opdracht snel zijn verhaal terugzien in het model. Zo kan
hij snel verifiëren of het goed is overgekomen. Mocht er iets missen, dan kan dat snel doorgegeven worden
een eenvoudig aangepast worden voor een update. Zo kun je dus in een vroeg stadium feedback krijgen over
jouw beeld van de opdracht.
Het model zal ook als inspiratie dienen voor de technische ontwerpen die gemaakt dienen te worden voor de
software. Het DBO (zie volgende hoofdstuk) maar ook het klassendiagram zouden hier van afgeleid kunnen
worden. Echter, let daarbij op dat het niet overgenomen moet worden! Een model is geen DBO en al helemaal
geen klassendiagram. Het hoeft dus niet zo te zijn dat elke entiteit letterlijk terug te vinden is in het DBO. Een
entiteit zou een klasse kunnen worden, maar dat moet niet. Het zou heel goed kunnen dat het niet nodig is in
de software. Denk daar dus in de ontwerpfase goed over na!
2.2 De notatie
Zoals gezegd bestaat het ER-model uit 3 basisvormen. Entiteiten zijn vierkanten, attributen van entiteiten
worden weergegeven als een ellips en relaties tussen entiteiten zijn ruiten. De verbindingen teken je met een
lijn tussen de verschillende componenten.
Entiteiten zijn ‘dingen’ die belangrijk zijn in het verhaal. Ze worden altijd in enkelvoud aangegeven. Denk
bijvoorbeeld aan een klant en een factuur. Deze ‘dingen’ hebben meestal eigenschappen die op hun beurt ook
weer belangrijk zijn in het geheel. Zo heeft een klant een naam, adres en een rekeningnummer en een factuur
heeft een factuurnummer en een datum. Dit noemen we attributen van de entiteit. Er bestaan ook onderlinge
-5-
augustus 2016
relaties tussen verschillende entiteiten. In dat geval zet je een ruit tussen de twee entiteiten en verbind elke
entiteit met de ruit om de onderlinge relatie aan te geven.
Er zijn drie verschillende soorten relaties, namelijk 1-op-1, 1-op-veel en veel-op-veel. In onderstaande tabel
vind je per relatie een voorbeeld van entiteiten die zo’n relatie zouden kunnen hebben samen en een
bijbehorende uitleg.
In figuur 1 zie je een voorbeeld van een ER-model over projecten waar medewerkers aan werken voor een
bepaalde klanten. Hieronder bespreken we stuk voor stuk alle aspecten van dit model.
-6-
augustus 2016
2.3.1 De entiteiten
Het voorbeeld ER-model uit figuur 1 kent drie entiteiten. Deze drie zijn klant, project en medewerker. De naam
van een entiteit is altijd in enkelvoud zoals je misschien al is opgevallen.
2.3.2 De relaties
Elke project wordt voor een specifieke klant gedaan. Er kunnen meerdere projecten voor een klant worden
gedaan. Op elk project kunnen meerdere medewerkers werken. Een medewerker kan aan meerdere projecten
werken. Zoals je ook kunt zien in het model is de functie die een medewerker vervult bij een bepaald project
een attribuut van de relatie. De functie kan namelijk door meerdere personen vervuld worden en een
medewerker kan op elk project een andere functie hebben.
2.3.3 De attributen
Elke entiteit heeft een attribuut in dit voorbeeld. Dat is echter niet verplicht voor een entiteit. Zo kan het
voorkomen dat een entiteit geen attributen heeft in het model. De attributen die zijn onderstreept zijn
identificerend. Dat betekent dat met dat attribuut een specifieke entiteit kan worden aangewezen. Zo kun je
bijvoorbeeld aan de hand van het KVK nummer een specifieke klant aanwijzen. Er is er maar 1 met dat
nummer. Met het adres van een klant is ook iets geks aan de hand. Dat attribuut is dubbel omcirkeld. Dat
betekent dat dit attribuut bestaat uit meerdere gegevens. Zo kan een adres bestaan uit een straat,
huisnummer en een woonplaats. Echter, een postcode met huisnummer kan ook voldoende zijn.
Tot slot zijn er blijkbaar twee specifieke medewerkers, namelijk de consultant en de accountmanager. Dit zijn
subtypes van de medewerker. Deze uitbreiding op het ER-model heet dan ook subtypering. Deze twee
uitbreidingen, kardinaliteit en subtypering wordt hieronder kort beschreven.
2.4.1 Kardinaliteit
Elke relatie heeft een kardinaliteit. We hebben tot nu toe drie type relaties gezien: 1-op-1, 1-op-veel en veel-
op-veel. Echter deze kunnen beide kanten op ook optioneel of verplicht zijn. De lijn van een relatie kan enkel
of dubbel worden getekend. Wanneer deze enkel is, is de relatie optioneel. De entiteit kan maar moet niet
perse deelnemen in de relatie. Als er een dubbele lijn wordt gebruikt is de relatie verplicht. De entiteit moet
-7-
augustus 2016
dan minstens 1 keer deelnemen aan de relatie. In het voorbeeld van figuur 2 betekent het dat elke consultant
ten minste 1 specialisme moet hebben. Niet elke specialisme is toegerekend aan een consultant.
Zie het volgende voorbeeld.
Figuur 3: Een medewerker en zijn afdeling.
In figuur 3 zie je dat een medewerker
verplicht op 1 afdeling werkt. Uiteraard
kan het zo zijn dat een medewerker op
meer dan 1 afdeling tegelijk werkt. Echter,
dit model zegt dat in dit geval dat niet zo
is. Elke medewerker werkt op verplicht 1
afdeling.
2.4.2 Subtypering
De subtypering gebruik je overal waar je wat complexere entiteiten wilt modelleren zonder daar heel veel
dubbel voor te moeten noteren. In het EER-model uit figuur 2 zie je eigenlijk 3 soorten medewerkers. Er zijn
consultants, accountmanagers en “gewone” medewerkers. Elk van deze drie soorten hebben de eigenschapen
van een medewerker. Dat betekent dat elke medewerker, consultant en accountmanager een “werkt onder”-
relatie heeft. Iedereen kan dus een baas hebben, wat natuurlijk ook klopt in het echt. Wanneer de entiteit nog
attributen had gehad zouden deze ook door alle drie de entiteiten en sub-entiteiten worden gedeeld.
Deze subtypering noemen we ook wel een is-een-relatie. Dat komt omdat je het altijd kunt uitspreken als “een
consultant is een medewerker” en ook “een accountmanager is een medewerker”. Wanneer dat in jouw
model niet zo werkt moet je nog eens goed naar je oplossing kijken.
Het voordeel van de subtypering is dat we dus niet alle attributen en relaties die een medewerker heeft ook bij
de consultant en accountmanager moeten zetten. Die krijgen ze ook, want het zijn medewerkers. Bijkomend
voordeel is dat we bij de subtypes kunnen afwijken van de standaard medewerker-entiteit. Zo heeft elke
accountmanager een bonus per account die hij binnen weet te halen. Een “gewone” medewerker en een
consultant heeft die bonus niet, maar de accountmanager wel. Consultants hebben specialismen waar
accountmanagers en “gewone” medewerkers dat niet hebben.
Tot slot kun je nog aangeven of de subtypering verplicht is of niet. Zo kan het zijn dat elke medewerker een
consultant of een accountmanager moet zijn en niet “gewoon” een medewerker is. Dan is de subtypering
verplicht. Dat geef je net als bij de kardinaliteit aan met een dubbele lijn.
De subtypering kun je vergelijken met het principe van overerving binnen het object-georiënteerd
programmeren.
-8-
augustus 2016
3. Databaseontwerp
Nadat het model is opgesteld en de klant zijn goedkeuring heeft gegeven kan er worden nagedacht over de
technische invulling van de opdracht. Ook daar moet goed over worden nagedacht. In dit dictaat gaan we er
natuurlijk vanuit dat er een database nodig zal zijn, maar dat hoeft natuurlijk lang niet altijd het geval te zijn.
Sommige zaken kunnen prima in een file worden opgeslagen of hebben geen persistentie nodig.
Als eerste moet er een keus worden gemaakt voor het type en merk database. In veel gevallen zal er voor een
relationele database gekozen worden, maar NoSQL databases zijn sterk in opkomst met de steeds groter
wordende informatiestroom die we Big Data noemen. In ons geval gaan we het hebben over relationele
databases. Dat heeft alles met informatie te maken die onderlinge een sterke relatie met elkaar hebben. Denk
daarbij bijvoorbeeld aan grote betaal- en banksystemen.
Het merk is ook belangrijk, want elke fabrikant heeft zo net weer zijn eigen manier van doen. De concepten
zijn overal hetzelfde, maar de invulling ervan niet altijd. In ons geval hebben we voor Microsoft SQL Server
gekozen, maar andere relationele databases zijn bijvoorbeeld die van Oracle, MySQL, PostgreSQL en DB2.
Alle punten zijn belangrijk, maar zeker het laatste punt. Een model is niets meer dan een snelle schets van de
meest belangrijke aspecten van de opdracht voor een klant. Een ontwerp is een gedetailleerde tekening van
hetgeen gemaakt moet gaan worden in code. Dit kan dus direct omgezet worden in SQL commando’s om de
database aan te maken.
3.2 Transformatie
Om van een EER-model tot een DBO te komen zijn bepaalde stappen nodig. Hieronder worden de
verschillende stappen genoemd.
We kijken eerst naar de algemene notatie en daarna naar de omzetting van model naar ontwerp.
3.3 Notatie
We hanteren in dit document een vaste notatie voor het DBO. Op de volgende pagina zie je een voorbeeldje
van een tabel. De naam van de tabel staat bovenaan, in de eerste rij van de tabel. In dit geval gaat het om
een tabel “Persoon”. Daarna volgens de attributen van de tabel. Bij deze tabel zijn dat er vijf. Bovenaan
staat altijd de
-9-
augustus 2016
primaire sleutel (Engels: primaire key, PK), daarna de vreemde Figuur 4: Een voorbeeld van een tabel in een DBO.
sleutels (Engels: Foreign Key, FK) en dan de overige attributen.
Gegevens die uniek moeten blijven binnen een kolom worden
met de letter U gemarkeerd van UNIQUE. Gegevens die
leeggelaten mogen worden noemen we nullable en worden met
de letter N gemarkeerd.
In de linker-kolom van het DBO staan de constraints, in het midden de attributen en rechts de datatypes. De
datatypes zijn types die gebruikt kunnen worden voor de gekozen database. In ons geval MSSQL, dus de types
in het voorbeeld hierboven zijn specifiek voor MSSQL.
Je ziet in dit voorbeeld een veel-op-veel-relatie tussen order en product. Een order heeft één of meerdere
producten en een product staat optioneel op veel orders. Een veel-op-veel-relatie werk je altijd op dezelfde
manier uit, namelijk met behulp van een koppeltabel (zie figuur 6). Het attribuut aantal kan dan ook mooi in
die nieuwe tabel worden opgenomen. OrderRegel lijkt een mooie naam voor deze tabel. Wat je vaak ziet is een
combinatie van de twee tabellen, in dit geval bijvoorbeeld Order_Product.
Zoals je ziet aan de koppeltabel zijn er twee vreemde sleutels (FK) en is er één gecombineerde primaire sleutel
(PK). Dat wil zeggen dat de combinatie van OrderID en ProductID altijd uniek is.
-10-
augustus 2016
Overige relaties werk je op een soortgelijke manier uit. Bij een 1:N relatie zet je de vreemde sleutel aan de
“veel-kant”. Die tabel gaat dus verwijzen naar één andere regel in de andere tabel. Een 1:1 relatie werk je uit
als een 1:N relatie, maar dan met een UNIQUE constraint op de vreemde sleutel. Zo voorkom je dat er
meerdere verwijzingen naar dezelfde regel kunnen ontstaan.
Een recursieve relatie werk je op precies dezelfde manier uit als bij een normale, niet-recursieve relatie. In het
geval van de studenten met peer reviews hebben we te maken met een veel-op-veel relatie. In dat geval
krijgen we, net als in het vorige voorbeeld, een koppeltabel. Deze bevat dan ook twee vreemde sleutels, alleen
verwijzen deze allebei naar dezelfde primaire sleutel van dezelfde tabel.. In figuur 8 zie je het resultaat van
bovenstaand EER-model.
-11-
augustus 2016
Grofweg zijn er vier mogelijkheden voor het uitwerken van een subtypering:
Bij optie 1 zou je een grote tabel krijgen die je dan waarschijnlijk Medewerker zou noemen. De attributen voor
accountmanager zijn leeg wanneer het om een consultant gaat en vice versa. Je hebt nog 1 extra attribuut
nodig om het onderscheid tussen de twee subtypes te kunnen maken om te zien wat voor een soort
medewerker het betreft.
Optie 2 zie je veel in de praktijk. Hij lijkt op optie 1, maar dan wordt het type niet opgeslagen in de
Medewerker-tabel, maar in een aparte Medewerkerstype-tabel. Je legt dan met een vreemde sleutel een
verwijzing naar het desbetreffende type.
Bij optie 3 krijg je twee tabellen, een voor consultant en een voor accountmanager. Het nadeel hierbij zijn de
overeenkomstige attributen (zoals de “Werkt onder”-relatie), die moet je namelijk dubbel bijhouden. Optie 4
-12-
augustus 2016
lost dit op door wel een aparte medewerker-tabel te behouden. De onderliggende subtypes houden dan een
verwijzing bij naar de medewerker waar zij bij horen.
Tip: Teken de 4 verschillende vormen eens uit op papier om te zien hoe dit er uit ziet!
-13-
augustus 2016
4. Logische expressies
Wat is een logische expressie en waarom zou je deze nodig hebben? In de informatica ben je veelal met het
evalueren van condities bezig. Dat betekent gewoon dat je gegevens vergelijkt en afhankelijk van hun waarde
een beslissing neemt om het een of het ander te gaan doen: “Als x groter is dan 3 doe ik dit, anders iets
anders”.
Bij SQL kom je ook logische expressies tegen. Denk daarbij aan wat ingewikkeldere query’s en constraints
(volgende hoofdstukken). Om die op te lossen kan het helpen om ze eens goed te analyseren en als het waren
te ontleden. Zo houd je overzicht en maakt je minders snel fouten bij het opstellen van de query of constraint.
Deze query gaat fout omdat het AND en de OR achterelkaar worden bekeken door het DBMS. Je krijgt nu een
overzicht van alle gebruikers die “Rick” heten of die “Rik” heten en ouder dan 18 zijn. Dat is niet helemaal wat
er gevraagd was. Het DBMS interpreteert de query eigenlijk als volgt.
In dit soort gevallen doe je er dus goed aan om zelf haakjes te gebruiken in je query. Dan komt deze er als volgt
uit te zien.
Bij grotere en meer ingewikkelde query’s en constraints is het vaak goed om haakjes te gebruiken. Dit
bevorderd de leesbaarheid en zorgt er voor dat je minder snel fouten maakt. Bovenstaand voorbeeld kunnen
we ook nog herschrijven tot een echt wiskundige logische expressie. Daarmee kunnen we eenvoudig de
correctheid nagaan.
leeftijd > 18 A
naam = 'Rik' B
naam = 'Rick' C
We kunnen nu zeggen dat we alle gebruikers willen hebben die voldoen aan A en aan óf B óf C. De logische
expressie wordt dan A AND (B OR C). Dit kun je met alles zo doen om ingewikkeld lijkende vergelijkingen te
herschrijven. Er is echter nog een soort stelling die we niet alleen maar met AND en OR kunt noteren.
Bijvoorbeeld de regel dat voor alle gebruikers jonger dan 12 jaar het wachtwoord langer dan 10 moet zijn.
Anders gezegd zou je kunnen stellen dat het wachtwoord langer dan 10 moet zijn wanneer een gebruiker
jonger is dan 12 jaar. Dus, uit het een volgt het ander.
leeftijd < 12 A
LENGTH(wachtwoord) > 10 B
De logische expressie is dan A ⇒ B. Dit spreek je uit als A impliceert B. Als je jonger bent dan 12 jaar, dan moet
de lengte van je wachtwoord groter zijn dan 10.
A⇒B
(NOT A) OR B
Het is maar wat je eenvoudiger noemt, maar zo kun je de implicatie herschrijven naar iets dat je wel kunt
programmeren in SQL. De NOT draait de stelling van A om. A stond voor leeftijd < 12. NOT A is dan leeftijd <=
12, waarbij het gelijkheidsteken belangrijk is!
We kunnen ook bewijzen dat de bovenstaande twee logische expressies gelijk aan elkaar zijn. Dat kun je
(onder andere) aantonen met behulp van waarheidstabellen.
4.4 Waarheidstabellen
Een waarheidstabel is een tabel waarin je kunt aflezen welke logische waardes (TRUE en FALSE) alle
parameters (A, B, C, enzovoorts) kunnen aannemen in een bepaalde expressie. Voor het gemak gebruiken we
1 voor TRUE en 0 voor FALSE. We nemen A AND (B OR C) weer als voorbeeld. We beginnen altijd door een
tabel van 1 parameter te maken, namelijk A, en daar alle logische waardes voor te noteren (TRUE en FALSE).
A
0
1
Dit is hopelijk nog goed te volgen. Deze tabel vertelt ons niet meer dan dat A twee verschillende waardes kan
aannemen, namelijk TRUE en FALSE. In de logische expressie staan ook de parameters B en C. Voor elke
nieuwe parameter die je toevoegt aan de waarheidstabel moet je de complete tabel kopiëren en eronder
plakken, als verdubbeling. Voeg nu de nieuwe parameter nu zodanig toe dat alle waarheidscombinaties met
voorgaande parameters in de tabel staan. Dat klinkt wat ingewikkeld, maar, een voorbeeld doet wonderen!
Wanneer we B toevoegen komt de waarheidstabel er als volgt uit te zien.
-15-
augustus 2016
A B
0 0
1 0
0 1
1 1
Zoals je kunt zien staan alle combinaties tussen A en B in de tabel. Zowel A als B zijn FALSE, of juist TRUE, een
van de twee is TRUE en de ander FALSE of andersom. Op dezelfde manier kun je C toevoegen, maar nu krijg je
eerst vier keer FALSE onder elkaar en daarna vier keer TRUE om unieke combinaties met A en B te maken.
A B C
0 0 0
1 0 0
0 1 0
1 1 0
0 0 1
1 0 1
0 1 1
1 1 1
Bovenstaande tabel is je beginpunt. Deze kun je direct maken zodra je de logische expressie ziet. Tel het aantal
parameters en maak de tabel op. Nu kun je een nieuwe kolom toevoegen, bijvoorbeeld B OR C, en deze
invullen aan de hand van de waardes van B en C.
A B C B OR C
0 0 0 0
1 0 0 0
0 1 0 1
1 1 0 1
0 0 1 1
1 0 1 1
0 1 1 1
1 1 1 1
Er komt dus alleen een 1 te staan in de rechter-kolom wanneer er in bij B of C een 1 staat. Nu kunnen we ook
de rest van de logische expressie toevoegen, namelijk A AND (B OR C).
A B C B OR C A AND (B OR C)
0 0 0 0 0
1 0 0 0 0
0 1 0 1 0
1 1 0 1 1
0 0 1 1 0
1 0 1 1 1
0 1 1 1 0
1 1 1 1 1
Deze tabellen kun je eenvoudig online laten genereren. Het is echter goed om te snappen hoe dit werkt. Het
versterkt namelijk je begrip en inzicht waardoor je eenvoudiger complexe query’s, constraints en andere
programmatuur zult kunnen programmeren.
Tip: Probeer de waarheidstabel voor (A AND B) OR C zelf te maken en merk de verschillen met bovenstaande.
-16-
augustus 2016
5. Constraints
Een constraint is vrij vertaald een beperking. Een beperking op de gegevens die je wilt opslaan in de database.
Deze constraints kun jij zelf aanmaken. Er zijn er een aantal die stuk voor stuk weer voor verschillende
toepassingen gebruikt kunnen worden en op andere manier geïmplementeerd moeten worden. In dit
hoofdstuk behandelen we de soorten constraints en bekijken we van een deel daarvan hoe je deze kunt
implementeren.
5.1 Uitleg
Zoals gezegd is een constraint een beperking die je als programmeur kunt aanmaken in de database. Meer
concreet, het is een regel of eis waar de informatie in een cel, rij, kolom, tabel of gehele database aan moet
voldoen. Bijvoorbeeld dat elke leeftijd in de leeftijd-kolom van de student-tabel groter moet zijn dan 17. Zodra
een nieuw gegeven niet voldoet aan alle constraints die van toepassing zijn zal deze niet worden opgeslagen.
De reden waarom deze regels nodig zijn is vanwege de consistentie van de informatie die wordt opgeslagen in
de database. Stel je voor dat inderdaad iedere student in jouw tabel minstens 18 moet zijn, maar dit heb je
niet met een constraint beschermd. Je kunt er dan voor kiezen om dit alsnog in je C#-programma te
controleren voordat je nieuwe gegevens in de student-tabel opslaat of wijzigt. Echter, iemand anders uit jouw
team maakt de mobile app voor jullie software. Hij vergeet deze constraint en controleert de leeftijd van de
studenten niet. Zodoende kunnen er alsnog foutieve gegevens worden opgeslagen. De vuistregel is “wat in de
database kan, doe je ook in de database”.
Vreemde en primaire sleutels zijn ook constraints. Stel je eens voor dat je deze niet zou gebruiken. Je kunt
zonder vreemde sleutels dan verwijzen naar gegevens die helemaal niet bestaan. Zonder primaire sleutels kun
je dubbele gegevens opslaan of deze zelfs leeg laten. Het DBMS controleert voor jou dat dit allemaal niet
gebeurd.
De eerste twee typen constraints kunnen we prima in SQL programmeren. Voor de onderste drie hebben we
een krachtigere programmeertaal in de database nodig, zoals bijvoorbeeld T-SQL (zie later hoofdstuk).
Om je een beter beeld te geven van de verschillende soorten constraints gaan we ze allemaal langs met een
voorbeeld aan de hand van onderstaande tabellen.
Medewerker-tabel:
ID Naam Functie Salaris
7519 Bram Brentjens Project leider 4000
7520 Anna Althuis Requirements engineer 3400
7521 Karel Konings Kwaliteitsmanager 3000
7522 Piet Patter Secretaris 1800
7523 John Janssen Rapporteur 2000
-17-
augustus 2016
Requirement-tabel:
ID Beschrijving MoSCoW Medewerker_ID
2467 De gebruiker kan op… M 7520
2468 Wanneer op “verder”… M 7520
2469 Alleen een machinist… S 7519
2470 Zodra het systeem aan… W 7520
De Student_ID kolom uit de Requirement-tabel is een verwijzing (vreemde sleutel) naar de ID-kolom uit de
Medewerker-tabel.
Een voorbeeld van een attribuut constraint is de naam van een medewerker die verplicht ingevuld moet zijn.
Deze mag dus niet leeg worden gelaten. In het geval van de voorbeelden zijn alle velden ingevuld en wordt de
constraint dus niet overtreden.
Een ander voorbeeld is het salaris dat minstens €1900 moet zijn. Bij Piet Patter is dat niet het geval. Zijn salaris
zal eerst naar €1900 verhoogd moeten worden alvorens de constraint kan worden geïmplementeerd.
Bij bovenstaande constraint is een implicatie nodig in de logische expressie. Stel je de volgende twee expressie
voor:
Functie = ‘kwaliteitsmanager’ A
Salaris > 3100 B
Dan krijg je A ⇒ B als logische expressie. Als de functie van iemand ‘kwaliteitsmanager’ is, moet gelden dat
salaris > 3100. Dit kun je zoals we eerder hebben gezien herschrijven naar de functie mag niet
‘kwaliteitsmanager’ zijn óf er moet gelden dat salaris > 3100. Ga dit zelf na als je dat nog niet hebt gedaan!
Dit soort zaken kun je niet met SQL programmeren (met uitzondering van de primaire en vreemde sleutels).
SQL kan namelijk alleen maar de gegevens van een bepaald veld of meerdere velden binnen een regel
evalueren. Zodra er informatie over meerdere regels of meerdere tabellen nodig is zal je een krachtigere taal
nodig hebben, zoals T-SQL.
-18-
augustus 2016
Ook kun je bepaalde functionaliteit programmeren die wordt gestart op het moment dat de database op
wordt gestart of juist af wordt gesloten. Dit soort complexe zaken zijn bij dit vak nog niet aan de orde, maar
deze kunnen logischerwijs ook niet in SQL worden geprogrammeerd.
De meest bekende constraints zijn de primaire en vreemde sleutel. Soms ook beter bekend als primairy key en
foreign key met als respectievelijke afkortingen PK en FK. Een primairy key is uniek en nooit leeggelaten. Aan
dat veld kun je elke regel in een tabel dus terugvinden. De FK is vaak gekoppeld aan een PK. Dit is een
verwijzing naar een ander veld in een andere tabel. Een regel verwijderen uit een tabel waar een andere regel
naar verwijst met een FK-constraint is dus ook niet mogelijk! We leren hier later nog wat trucjes voor.
Dan zijn er nog drie andere veel gebruikte constraints: nullable, unique en check. Je kunt een veld nullable
maken waarmee je aangeeft dat het veld eventueel leeggelaten mag worden. Denk daarbij aan een
telefoonnummer wat je bij registratie optioneel wel of niet kunt invullen. Een veld unique maken is precies
wat je verwacht, het is uniek. Denk daarbij aan je BSN of E-mailadres die beide altijd uniek moeten zijn binnen
een tabel. Met de check-constraint kun je wat ingewikkeldere zaken aangeven. Bijvoorbeeld dat het salaris
groter moet zijn dan een bepaalde waarde, dat het E-mailadres een @-symbool bevat of dat het huisnummer
is ingevuld wanneer de postcode is gegeven. De implementatie van deze constraints zien we in het hoofdstuk
over de Data Definition Language (DDL).
Hier rechts zie je een voorbeeld van een DBO van een Persoon-tabel
met constraints. Zoals je ziet is het ID van een persoon het
identificerende gegeven, de primaire sleutel (PK). Alle adressen
worden blijkbaar opgeslagen in een aparte tabel, want elk persoon heeft een verwijzing AdresID naar de PK
-19-
augustus 2016
van de Adres-tabel. Het E-mailadres van elk persoon moet uniek zijn. Je kunt je dus niet twee keer aanmelden
met hetzelfde E-mailadres in dit systeem. De geboortedatum mag leeggelaten worden want die is nullable (N)
en het gewicht niet.
Let op dat een veld ook meerdere constraints kan hebben. Zo kan het zijn dat de primaire sleutel een extra
check heeft. Een PK van een tabel kan ook een FK naar een andere tabel is. En een FK kan ook uniek zijn om 1-
op-1 relaties te programmeren. Let op dat de PK altijd al unique en not nullable is.
-20-
augustus 2016
6. DDL en DML
DDL staat voor Data Definition Language en DML voor Data Manipulation Language. Nu je weet hoe je een
ontwerp moet maken en constraints daarin kunt aangeven wordt het tijd om dat om te gaan zetten naar SQL.
Dat doe je met DDL. We gaan definiëren hoe de data wordt opgeslagen (CREATE). Daarna gaan we die
informatie manipuleren door er gegevens aan toe te voegen (INSERT), te wijzigen (UPDATE), verwijderen
(DELETE) en in te zien (SELECT).
Het is allemaal geen hogere wiskunde en het is voornamelijk een hoop zelf uitzoeken, uitproberen en gewoon
doen. We beginnen met het aanmaken van de tabellen en constraints.
Het begint dus met CREATE TABLE en dan de naam de persoon gevuld door alle kolommen tussen haakjes,
gescheiden door komma’s. Voor elke kolom geef je de naam van die kolom aan, het type en eventueel de
constraints indien mogelijk. In tegenstelling tot het ontwerp is in SQL elke kolom standaard nullable en moet je
expliciet aangeven wanneer deze niet leeg gelaten mag worden door er NOT NULL bij te zetten. Dat is het
tegenovergestelde van wat we bij het databaseontwerp hebben gezien.
Het is ook mogelijk om na de laatste kolom nog extra constraints toe te voegen. Dat kan handig zijn omdat je
de constraints dan ook een naam kunt geven. De naam van een constraint is handig wanneer je deze moet
verwijderen of wanneer de constraint juist voor problemen zorgt. Het DBMS zal dan de naam van de
constraint gebruiken om je duidelijk te maken dat er iets mee aan de hand is. Bijvoorbeeld zoals in het
volgende voorbeeld.
-21-
augustus 2016
In het in SQL uitgewerkte voorbeeld van de Medewerker-tabel kun je twee check-constraints zien. De lengte
van de naam van een Medewerker moet minstens 2 lang zijn en het minimale salaris moet €1900 zijn. De
lengte van de naam wordt gecontroleerd met een in-kolom constraint. Het minimale salaris wordt
gecontroleerd met een in-tabel constraint. Deze constraint heeft als naam ck_MinSalaris.
In onderstaand voorbeeld zie je hoe je een kolom kunt toevoegen aan een tabel.
In het volgende voorbeeld kun je zien hoe je een constraint toevoegt aan een tabel.
-22-
augustus 2016
Zorg er dus altijd voor dat je eerst tabellen verwijderd waar geen andere tabellen naar verwijzen. Daarna de
andere. Je kunt ook eerst zelf de constraint verwijderen volgens het volgende voorbeeld. Let er dan wel op dat
je de FK dan toevoegt met een zelf ingegeven naam.
Let er op dat het verwijderen van gegevens uit een tabel zonder de WHERE-clausule resulteert in een lege
tabel zonder gegevens. Dit kun je niet meer terugdraaien.
6.5 Transacties
Het is geen onderdeel van DDL of DML, maar bij deze wordt het kort benoemd. Als je met de database bezig
bent kun je bepaalde acties clusteren en als geheel doorvoeren naar het DBMS en daarmee permanent maken
of terugdraaien sinds het begin van de transactie. Zie het volgende korte voorbeeld.
<DML-statements>
Op het moment dat de transactie wordt doorgevoerd met een COMMIT staan alle gegevens in de database.
Mocht er tussentijds iets niet helemaal gaan zoals je had voorspeld, dan kun je deze nog terugdraaien met het
volgende commando.
<DML-statements>
-23-
augustus 2016
Alle DML-statements zullen nu teruggedraaid worden. Let er op dat DDL-statements impliciet ook een
COMMIT uitvoeren. Wanneer je tussen je DML dus een DDL-commando uitvoert wordt als het voorgaande dus
doorgevoerd. Bij een ROLLBACK zal alles voor het DDL-commando dus blijven staan in de database. Dat kun je
niet meer terugdraaien!
-24-
augustus 2016
7. Geavanceerde SQL
Het selecteren van informatie uit een database doe je middels SQL, wat op zichzelf weer een onderdeel is van
de Data Manipulation Language zoals we hebben kunnen lezen. Als het goed is heb je al enige ervaring met
SQL. We beginnen dit hoofdstuk met een korte herhaling en duiken daarna dieper in de materie. Denk daarbij
aan meer ingewikkelde JOIN-constructies zoals de OUTER en recursieve JOIN.
Je kunt bij de SELECT verschillende gegevens aangeven die je zou willen selecteren. Gebruik een asterisk-
symbool (*) om alle beschikbare kolommen uit de tabellen die je in de FROM hebt gespecificeerd te
selecteren. Anders kun je separate kolommen aangeven uit de tabellen, gescheiden door komma’s.
In de FROM zet je alle tabellen die je nodig hebt om alles te selecteren. Let er op dat je ze koppelt door middel
van een JOIN met de PK en FK. Wanneer je dat eenmaal hebt gedaan kun je in de SELECT alle kolommen
gebruiken uit alle tabellen die in de FROM staan. Let er op dat je aangeeft uit welke tabel je de gegevens wilt
hebben voor die kolommen die dezelfde naam hebben in verschillende tabellen. Je kunt tabellen ook een alias
geven door na de naam van de tabel een nieuwe naam op te geven. Vaak is dit de eerste letter van de tabel.
Voor een Student-tabel zou je dus S nemen. Zie het volgende voorbeeld.
Hierboven zie je een voorbeeld van de meest voorkomende JOIN. Dit is de INNER JOIN. We kijken verderop
waarom deze zo wordt genoemd en wat voor een varianten je nog meer hebt. We koppelen de cijfers aan de
studenten. Daarbij moeten we wel de juist cijfers aan de juiste studenten koppelen. Vandaar dat we de twee
tabellen moeten koppelen met de JOIN.
Met de WHERE-clausule kunnen we een soort filter aangeven. In bovenstaand voorbeeld geven we aan alleen
maar de resultaten van die studenten te willen zien die jonger dan 18 jaar oud zijn. De ORDER BY zorgt er voor
dat alle resultaten worden gesorteerd op oplopend (ASC) studentnummer. Zet er DESC in plaats van ASC
achter om de sortering te laten aflopen.
SELECT
Prijs AS PrijsExBTW,
Prijs * 1.21 AS PrijsInBTW
-25-
augustus 2016
FROM Product;
In het voorbeeld zie je dat je dus zelf kolommen kunt aanmaken door allerlei gegevens te combineren en er
optioneel mee te rekenen. In dit geval nemen we de prijs van een product inclusief en exclusief BTW. In de
database wordt alleen maar de exclusief-prijs opgeslagen. De prijs inclusief BTW moeten we dus nog
uitrekenen. Dat kan zoals aangegeven in de SELECT-clausule.
Je kunt ook ingebouwde SQL-functies gebruiken zoals de COUNT, SUM, MIN, MAX en AVG. Hiermee kun je
respectievelijk het aantal, de som, het minimum, het maximum en het gemiddelde van een bepaalde set
opvragen. Zo krijg je door SELECT COUNT(*) FROM Gebruiker uit te voeren het aantal gebruikers
terug. SELECT SUM(Prijs) FROM Bestelling geeft de optelling van alle prijzen van alle bestellingen.
Om de gemiddelde prijs van een product op te vragen kun je SELECT AVG(Prijs) FROM Product
uitvoeren.
Let er op dat je moet groeperen zodra je naast een functie ook andere kolommen wilt gebruiken. De vuistregel
is dat je alle kolommen die je naast de functie(s) in de query gebruikt ook in de GROUP BY moet zetten. Bekijk
onderstaand voorbeeld.
Wanneer je bovenstaande query zou uitvoeren kan je, afhankelijk wat voor database je gebruikt, een
foutmelding verwachten. Je vraagt namelijk het totaal aantal studenten op (dat is 1 waarde) en alle leeftijden
van de studenten (dat zijn waarschijnlijk meerdere waardes). Zodra je op leeftijd groepeert zal het DBMS het
aantal studenten per groep gaan tellen. Bovenstaande query geeft dus een overzicht van het aantal studenten
per leeftijd. Let daar goed op!
Student-tabel:
ID Naam Geboortedatum SLB_ID
Docent-tabel:
-26-
augustus 2016
Bij elke student is bijgehouden wie hij of zij als SLB heeft. Het SLB_ID in de student-tabel verwijst naar het ID
van een docent. Zoals je kunt zien heeft Xander Xavier geen studenten waar hij SLB van is.
Naam SLB
-27-
augustus 2016
Het wordt echter complexer wanneer je de namen van alle docenten moet opvragen met, indien aanwezig, de
namen van al zijn of haar SLB-studenten. Dat is het witte gedeelte van de linker docent-cirkel, inclusief de
overlap met student. In dat geval zal de INNER JOIN die docent niet aan een student kunnen koppelen. Die zijn
er namelijk niet. In dat geval zal de betreffende docent niet in het resultaat worden meegenomen. In het
voorbeeld zou Xander Xavier bijvoorbeeld niet in het resultaat voorkomen omdat hij geen studenten naar hem
refereren. Hij is geen SLB. Om dit netjes op te lossen heb je een andere JOIN nodig. Dat is de OUTER JOIN.
Zoals je kunt zien in bovenstaande query wordt hier een LEFT OUTER JOIN toegepast. Onthoud het als “de
linker kant” is primair in deze query. Dus de student-tabel die links van de LEFT JOIN staat is de tabel waar je
alle gegevens van wilt.
Er is ook zo iets als een RIGHT OUTER JOIN, wat eigenlijk hetzelfde is als de linkse variant. Wanneer je de twee
tabellen in de FROM-clausule omdraait kun je van de LEFT een RIGHT maken en heb je dezelfde query. Dit ziet
er dan als volgt uit.
Een OUTER JOIN is dus link- of rechtsom altijd hetzelfde, mits je de tabellen maar aan de juiste kant zet. Het
resultaat ziet er dan als volgt uit.
-28-
augustus 2016
-29-
augustus 2016
Een medewerkers-tabel:
ID Naam Functie Salaris MangerdByID
In bovenstaand voorbeeld kun je zien dat elke medewerker een manager boven zich kan hebben. Iedereen is
een medewerker en iedereen, behalve de CEO Frits Fazant, heeft een manager die zelf ook weer een
medewerker is. De medewerker-tabel heeft dus een recursieve-relatie. Via het ManagedByID kom je uit bij de
manager van de desbetreffende medewerker.
We willen de namen van alle medewerkers opvragen inclusief de naam van diens manager. Voor deze query
zullen we de recursieve relatie moeten gebruiken. Via het ManagedByID moeten we bij de manager uitkomen.
We hebben dus twee keer de medewerker-tabel nodig. Een keer voor de naam van de medewerker zelf en nog
een keer voor de naam van de manager. We komen dan uit op de volgende query.
Omdat je twee keer dezelfde tabel gebruik in de FROM-clausule, is het van belang om deze een alias te geven.
Met die alias kun je in de SELECT-clausule aangeven uit welke van de twee tabellen je de gegevens wilt
selecteren. Wanneer je dit niet doet zal SQL je een foutmelding geven. Welke naam wil je dan selecteren, die
van de medewerker of van de manager?
Stel we willen weten welke medewerkers er allemaal meer verdienen dan het gemiddelde van alle
medewerkers onder dezelfde manager. De toevoeging op het eind van de vorige zin over het gemiddelde
salaris van alle medewerkers die onder dezelfde manager vallen is moeilijk. Dat lijkt een beetje op een groep,
maar is juist de correlatie. De oplossing zit ‘m in het feit dat je dit probleem in twee delen moet oplossen en
later moet samenvoegen.
Als eerste wordt een overzicht gevraagd van alle medewerkers die meer verdienen dan een bepaald
gemiddelde. Laten we even €3000 kiezen als gemiddeld salaris. Dan ziet de query er als volgt uit.
-30-
augustus 2016
In het tweede gedeelte van de query wil je het gemiddelde salaris van alle medewerkers weten die onder een
en dezelfde manager vallen. Stel, we pakken voor nu even de manager met ID 2725. Dat is medewerker Erna
Edings. De query die we zoeken ziet er als volgt uit.
SELECT AVG(med.Salaris)
FROM Medewerker med
WHERE med.ManagedByID = 2725;
In de query hierboven selecteren we het gemiddelde salaris van alle medewerkers die als manager de
medewerker met ID 2725 hebben.
Nu moeten we de twee query’s combineren. Het gemiddelde salaris van alle medewerkers onder dezelfde
manager is niet €3000. Dat gemiddelde hebben we uitgezocht met de tweede query. Die kunnen we dus als
sub-query gebruiken. Dat levert ons het volgende tussenresultaat op.
Echter, het manger ID dat we zoeken is ook niet 2725. Dat moet hetzelfde ID zijn als van de manager uit de
hoofd-query. Dus die moeten we aan elkaar gelijk stellen. Dat maakt de query gecorreleerd. De hoofd-query
houdt verband met de sub-query. We komen dan tot onderstaand resultaat.
-31-
augustus 2016
8. T-SQL
Zoals al een paar keer eerder in dit dictaat aan bod is gekomen zijn er niet voor alle problemen een passende
oplossing in SQL. Daar heb je bepaalde technieken voor nodig die meer mogelijkheden bieden dan SQL. Een
voorbeeld daarvan is Microsoft’s T-SQL dat staat voor Transact-SQL. In dit hoofdstuk vind je de basis over dit
onderwerp om er zelf verder mee aan de slag te kunnen.
8.1 Uitleg
T-SQL is een Microsoft uitbreiding op SQL. Andere bekende uitbreidingen met vergelijkbare functionaliteit zijn
stored procedures van MySQL en PL/SQL van Oracle. De basis die je al gewend ben uit C# heeft waarschijnlijk
wel een soortgelijke oplossing in T-SQL. Denk daarbij aan variabelen, if-statements, loops en methodes. Ook
kun je er bijvoorbeeld exceptions en file I/O mee programmeren.
SQL is een declaratieve taal. Dat wil zeggen dat je als programmeur met SQL kunt zeggen welke gegevens je
wilt hebben. Het is niet mogelijk om het DBMS te vertellen hoe hij dat moet doen. C# en ook T-SQL zijn
imperatieve programmeertalen. Daarmee kun je zeggen hoe bepaalde bewerkingen uitgevoerd dienen te
worden. Beide hebben hun voor- en nadelen.
SQL is heel goed in het kort en krachtig noteren van welke gegevens er opgehaald moeten worden of wat voor
een object er in een database aangemaakt moet worden. Je kunt er echter geen tussentijdse gegevens mee
onthouden om ze op een later tijdstip in je code of script te hergebruiken. Ook kun je de volgorde van een
bewerking niet aangeven. Dit is waar je een imperatieve programmeertaal als T-SQL voor nodig hebt.
T-SQL zorgt voor performance winst ten opzichte van C# omdat de code in de database is opgeslagen.
Daarmee kan T-SQL dus sneller door alle gegevens zoeken en gemakkelijker SQL uitvoeren. Je kunt grote
problemen opdelen in kleinere deelproblemen en dus modulair maken. Net zoals je dat in C# doet eigenlijk.
Dat vergroot de leesbaarheid van je code. Ook zijn bepaalde delen dan herbruikbaar voor andere
programmeurs en andere projecten. Daarmee boek je tijdwinst en hoe je de code niet telkens opnieuw te
programmeren. Ook kun je er met T-SQL voor zorgen dat bepaalde database-gerelateerde programmatuur is
afgeschermd van andere programmeurs die geen toegang tot de database hebben. Daarmee voorkom je dat
anderen de gegevens in de database kunnen ver…
8.2 Procedures en functies
Net als in C# en andere programmeertalen kun je in T-SQL variabelen maken, if-statements gebruiken, loops
toepassen, maar ook methodes aanmaken en gebruiken. Er zijn twee soorten methodes, namelijk procedures
en functies. Een procedure is een methode zonder return-value. Een functie geeft wel een waarde terug als
resultaat. Verder is er geen verschil tussen de twee.
-32-
augustus 2016
Deze procedure haalt alle medewerkers op met een bepaalde voor- en achternaam. De naam van de
procedure is HaalWerknemerOp en heeft twee parameters, Achternaam en Voornaam, beide aangegeven met
het @-symbool. Na AS komt de body van de procedure. Wanneer het sleutelwoord GO wordt gegeven zal het
voorgaande T-SQL object naar de DBMS worden gepersisteerd.
Het volgende voorbeeld maakt gebruik van een return-waarde. Dit is dus een functie.
Deze functie geeft een integer terug als resultaat. Op de eerste regel van de function body (na de BEGIN)
wordt een lokale variabele ret aangemaakt van het type int. De SUM van alle producten wordt opgehaald met
een SQL statement. Er wordt daarna nog een extra check gedaan voordat deze waarde wordt geretourneerd.
8.3 Triggers
Om complexe constraints te kunnen maken in de database heb je veelal triggers nodig. Een trigger is een
methode die wordt uitgevoerd op het moment dat een bepaald event in de database optreedt. Je kunt het
vergelijken met een event handler.
Je kunt bij een trigger aangeven dat deze bijvoorbeeld wordt uitgevoerd na een INSERT, UPDATE of DELETE
statement. Hij kan vervolgens de methode één keer uitvoeren of per regel uit het INSERT, UPDATE of DELETE
statement. Bekijk de volgende voorbeelden.
BEGIN
DECLARE @Count int;
SET @Count = (SELECT COUNT(*) FROM inserted
WHERE Prioriteit = 'Hoog');
PRINT CAST(@Count AS varchar(3)) + ' regel(s) met hoge prioriteit.';
END;
GO
Bovenstaande trigger laat het aantal orders met hoge prioriteit zien die zijn toegevoegd aan de orders-tabel.
De inserted-tabel, net als de deleted tabel, is een bijzondere in-memory tabel die SQL Server aanmaakt bij
triggers. Alle regels die bij het INSERT-statement worden toegevoegd aan de tabel waar de trigger op staat
komen tijdelijk in die tabel. Iets soortgelijks gebeurt bij het verwijderen met de deleted-tabel. Voor UPDATE-
statements worden de regels die aangepast gaan worden eerst in de deleted-tabel gezet en daarna in de
inserted-tabel.
De trigger uit bovenstaand voorbeeld is een zogenaamde instead of trigger. Die wordt uitgevoerd voorafgaand
aan de uitvoering van het INSERT, UPDATE en/of DELETE statement. De trigger vervangt het INSERT, UPDATE
en/of DELETE statement dat eigenlijk uitgevoerd zou worden. Je moet deze dus nog expliciet toevoegen aan de
trigger wanneer je het statement alsnog wilt uitvoeren. Dat gebeurt ook in het voorbeeld hierboven.
Deze instead-of trigger wordt dus uitgevoerd voordat de INSERT statements daadwerkelijk in de database
staan. Zo kun je controleren of er bijvoorbeeld al een persoon met dezelfde naam bestaat. Dit kan uiteraard
veel beter met een UNIQUE constraint, maar het laat goed de opbouw en het nut van de instead-of trigger
zien, want je kunt hier veel complexere controles mee uitvoeren natuurlijk.
8.4 Cursors
Elke uitkomst van een SQL-query komt in een apart stuk geheugen te staan van het DBMS. Een cursor is een
soort verwijzing naar dat stukje geheugen. Het kan namelijk soms nuttig zijn om regel voor regel door het
resultaat van een query te lopen. Cursors gebruik je veelal in combinatie met een loop. Denk daarbij aan een
foreach-loop waar je per regel iets uit een filestream haalt. Elke regel die je binnenhaalt bestaat weer uit losse
onderdelen. Denk aan de velden (kolommen) in een tabel.
-34-
augustus 2016
Deze methode loopt door alle mederwerkers en print stuk voor stuk alle namen. Uiteraard kun je vele
ingewikkeldere zaken uitvoeren per loop-iteratie, maar het toont kort en krachtig het gebruik en de toepassing
van een cursor.
-35-