Skip to content

Normaliseren

Als je een database ontwerpt, dan wil je dat je zo min mogelijk data opslaat. Je wil geen dubbele data opslaan en je wil geen data opslaan die je niet nodig hebt. Door tabellen te koppelen kan je data opslaan in een tabel en deze data gebruiken in een andere tabel. Dit is een belangrijk onderdeel van het ontwerpen van een database.

Hoe vind je de juiste tabellen om te koppelen?

Vaak is het intuïtief duidelijk welke tabellen je moet koppelen. Neem de onderstaande tabel:

id naam adres postcode woonplaats
1 Jan Kerkstraat 1 1234 AB Amsterdam
2 Piet Kerkstraat 1 1234 AB Amsterdam
3 Klaas Kerkstraat 1 1234 AB Amsterdam

In deze tabel staan drie personen. De personen hebben allemaal hetzelfde adres. Je kan dit adres opslaan in een aparte tabel en deze tabel koppelen aan de tabel met personen. De tabel met adressen ziet er dan als volgt uit:

id adres postcode woonplaats
1 Kerkstraat 1 1234 AB Amsterdam

De tabel met personen ziet er dan als volgt uit:

id naam adres_id
1 Jan 1
2 Piet 1
3 Klaas 1

Je hoeft nu niet meer het adres van een persoon op te slaan in de tabel met personen. Je kan het adres van een persoon opzoeken in de tabel met adressen. Zo heb je minder data opgeslagen.

Wanneer de gegevens complexer worden, wordt het minder intuïtief welke tabellen je moet koppelen. Met behulp van normaliseren kan je bepalen welke tabellen je nodig hebt en hoe je ze moet koppelen.

Normaliseren

Normaliseren is een proces waarbij je een database ontwerpt door een tabel te splitsen in meerdere tabellen. Je kan een tabel normaliseren door te kijken naar de functionele afhankelijkheden tussen de attributen in de tabel. Een functionele afhankelijkheid is een relatie tussen twee attributen in een tabel. Als je de waarde van het ene attribuut weet, dan kan je de waarde van het andere attribuut bepalen.

Aangeleverde tabel

Als voorbeeld nemen we de volgende tabel:

Voorbeeld tabel: Users en Achievements
userId userName userAvatarImage userJoindate achievementType achievementText achiemevementImage scores
1 Storm storm.png 10/09/1997 noShoot Don’t shoort enemies for 1 minute. noshoot.png 1342314 behaald op 01-02-2023 , 3245523, behaald op 02-03-2012
1 dodgy Dodge 10 enemy bullets in one session. dodgy.png
1 richkid Collect 10E10 coins in one session. richkid.png
2 Juffrouw Jannie juffrouw_jannie.png 12/01/2001 urdead Die within 2 seconds. urdead.png 4351542 behaald op 23-10-1990, 34578 behaald op 23-10-2008
2 noShoot Don’t shoort enemies for 1 minute. noshoot.png
2 dodgy Dodge 10 enemy bullets in one session. dodgy.png
3 Edgar edgar.png 05/04/2023 dodgy Dodge 10 enemy bullets in one session. dodgy.png 234 behaald op 23-10-1991 ,536 behaald op 23-10-1992, 654 behaald op 23-10-1993
3 richkid Collect 10E10 coins in one session. richkid.png
3 urdead Die within 2 seconds. urdead.png
4 Jos stift_master.png 01/02/2019 noShoot Don’t shoort enemies for 1 minute. noshoot.png 1 behaald op 23-10-2003, 2 behaald op 23-10-2002, 3 behaald op 23-10-2001
4 richkid Collect 10E10 coins in one session. richkid.png
4 urdead Die within 2 seconds. urdead.png

In deze tabel is getracht om de gegevens (scores en achievements) van een aantal spelers op te slaan.

Een aantal dingen vallen op:

  • De tabel bevat veel lege cellen.
  • De tabel bevat herhalende gegevens. De achievementType noShoot komt meerdere keren voor.
  • De tabel bevat een lijst met scores. De scores zijn gescheiden door een komma. De tabel bevat dus meerdere waarden in één cel.
  • De tabel bevat een lijst met achievements. De eigenschappen van iedere achievement zijn opgeslagen voor elke keer dat een achievement behaald is.

Om deze data beter toegankelijk te maken voor een applicatie, kan je de tabel normaliseren.

1e normaalvorm

De regels voor de eerste normaalvorm zijn:

  • geen herhalende groepen
  • geen kolommen met meerdere waarden
  • iedere rij heeft een unieke identificatie

De eerste normaalvorm is een tabel waarin geen herhalende groepen voorkomen. Een herhalende groep is een groep attributen die meerdere waarden bevat. In de tabel hierboven komen twee herhalende groepen voor: de scores en de achievements.

De scores kan je normaliseren door een aparte tabel te maken met de scores. De behaalde datum kan je opslaan in een aparte kolom. De tabel met scores ziet er dan als volgt uit:

userId score date
1 1342314 01-02-2023
1 3245523 02-03-2012
2 4351542 23-10-1990
2 34578 23-10-2008
3 234 23-10-1991
3 536 23-10-1992
3 654 23-10-1993
4 1 23-10-2003
4 2 23-10-2002
4 3 23-10-2001

De primary key van deze tabel bestaat uit de userId en de date.

De tabel met gebruikers ziet er dan als volgt uit:

userId achievementType achievementText achievementImage
1 noShoot Don’t shoort enemies for 1 minute. noshoot.png
1 dodgy Dodge 10 enemy bullets in one session. dodgy.png
1 richkid Collect 10E10 coins in one session. richkid.png
2 urdead Die within 2 seconds. urdead.png
2 noShoot Don’t shoort enemies for 1 minute. noshoot.png
2 dodgy Dodge 10 enemy bullets in one session. dodgy.png
3 dodgy Dodge 10 enemy bullets in one session. dodgy.png
3 richkid Collect 10E10 coins in one session. richkid.png
3 urdead Die within 2 seconds. urdead.png
4 noShoot Don’t shoort enemies for 1 minute. noshoot.png
4 richkid Collect 10E10 coins in one session. richkid.png
4 urdead Die within 2 seconds. urdead.png

Omdat er geen enkele kolom voldoet aan de regels voor de primary key (uniciteit) zal de primary key bestaan uit de userId en achievementType.

Via de foreign key userId kan je de tabel met gebruikers koppelen aan de tabel met scores:

---
title: 1e normaalvorm
---
erDiagram
    USER ||--o{ SCORE : "userId"

2e normaalvorm

De regels voor de tweede normaalvorm zijn:

  • voldoen aan de 1e normaalvorm
  • geen partiële afhankelijkheden: ieder attribuut is afhankelijk van de gehele primary key

De tweede normaalvorm is alleen toe te passen met tabellen waarbij de primary key uit meerdere attributen bestaat (composite key) In de tabel met gebruikers is de primary key opgebouwd uit de userId en de achievementType. De achievementText en achievementImage zijn afhankelijk van de achievementType. De tabel voldoet dus niet aan de tweede normaalvorm.

De tabel met gebruikers kan je normaliseren door een aparte tabel te maken met de achievements. De tabel met achievements ziet er dan als volgt uit:

achievementType achievementText achievementImage
noShoot Don’t shoort enemies for 1 minute. noshoot.png
dodgy Dodge 10 enemy bullets in one session. dodgy.png
richkid Collect 10E10 coins in one session. richkid.png
urdead Die within 2 seconds. urdead.png

De tabel voor de behaalde achievements ziet er dan als volgt uit:

userId achievementType
1 noShoot
1 dodgy
1 richkid
2 urdead
2 noShoot
2 dodgy
3 dodgy
3 richkid
3 urdead
4 noShoot
4 richkid
4 urdead

De primary key van deze tabel bestaat uit de userId en de achievementType.

De tabellen zijn via hun foreign keys te koppelen:

---
title: 2e normaalvorm
---
erDiagram    
    ACHIEVEMENT }o--|| ACHIEVEMENTTYPE : "isOfType"
    USER ||--o{ ACHIEVEMENT : "achieved"
    USER ||--o{ SCORE : "scored"

3e normaalvorm

De regels voor de derde normaalvorm zijn:

  • voldoen aan de 2e normaalvorm
  • geen kolom mag afhankelijk zijn van een ander niet-key attribuut

In de achievement tabel is de achievementImage afhankelijk van de achievementType: de bestandsnaam van het plaatje komt direct overeen met de achievementType. De tabel voldoet dus niet aan de derde normaalvorm. Door de kolom achievementImage te verwijderen voldoet de tabel wel aan de derde normaalvorm. Bestandsnamen kunnen dan in code of met behulp van een SQL query worden gegenereerd.

De tabel met achievements ziet er dan als volgt uit:

achievementType achievementText
noShoot Don’t shoort enemies for 1 minute.
dodgy Dodge 10 enemy bullets in one session.
richkid Collect 10E10 coins in one session.
urdead Die within 2 seconds.