Database ontwerp 101
Uit Yapf
Dit artikel legt grofweg uit hoe je een goed database ontwerp kunt maken. Er zitten een aantal onvolkomenheden in (benamingen, methodes) maar daar heb je bij de gemiddelde database verder geen last van.
Database ontwerp 101.
Dit artikel legt de basis uit van hoe je een goed database ontwerp kunt maken. Er zitten een aantal onvolkomenheden in (benamingen, methodes) maar daar heb je bij de gemiddelde database verder geen last van.
Dit is een hele lap tekst, maar lees het wel helemaal door. Het ontwerpen van een database is in principe vrij eenvoudig, maar je moet je aan een aantal regels houden. Het is belangrijk om te weten wat die regels zijn, maar vooral ook _waarom_ die regels er zijn, anders ga je absoluut de fout in en achteraf een normalisatiefout verbeteren kan een enorme klus worden omdat je dan meestal al een hele serie queries en scripts hebt die er gebruik van maken.
De database uit dit artikel is ontworpen met behulp dan Dezign for Databases, van het Nederlandse bedrijf Datanamic, welke met flinke korting te verkrijgen is voor leden van de PHPFreakz vereniging
Voorbereiding.
Een goed database ontwerp begint op papier, met een opsomming van de gegevens die je in de database wilt hebben en wat je later allemaal met de database wil gaan kunnen. Dit gaat allemaal in gewoon Nederlands, zonder een woord SQL. In dit stadium moet je proberen om niet in tabellen en kolommen te denken maar alleen in gewoon nuchter "wat moet ik allemaal weten?" Denk hier niet te licht over, want als je er later achter komt dan je iets bent vergeten dan kun je meestal overnieuw beginnen. Dingen toevoegen aan een database is meestal een berg werk.
Als je voor een klant werkt, ga dan met die klant praten en probeer er achter te komen welke gegevens hij gebruikt en hoe die gegevens met elkaar te maken hebben. Vraag hem om je uit te leggen hoe hij zijn werk doet. Maak niet de klassieke fout om de klant te vragen welke gegevens hij wil opslaan want dat weet hij niet. Ga gewoon naast hem zitten en kijk wat zijn werkzaamheden precies zijn. Elke keer dat hij een formuliertje pakt maak jij een kopie en schrijf je op wat hij ermee doet. Breng zijn werk in kaart, alleen daarmee kun je bepalen wat de database moet gaan opslaan.
Blijf ook niet bij de baas hangen, praat met iedereen die met de database te maken krijgt. De baas is meestal degene die het minst weet van wat zijn werknemers doen.
Stap 1. Bepaal de entiteiten
De soorten informatie die in de database wordt opgeslagen heten 'entiteiten'. Deze zijn er in vier soorten: mensen, gebeurtenissen, lokaties en voorwerpen. Alles wat je in een database zou kunnen willen zetten past in deze categorieen. Pas het er niet in, dan is het geen entiteit maar een eigenschap van een entiteit.
Voorbeeld
Stel je maakt een website voor een winkel (niet perse een webwinkel), waar heb je dan allemaal mee te maken?
Wat er in een winkel gebeurd is redelijk simpel: "Een klant komt binnen in het filiaal en koopt één of meerdere producten."
Welke entiteiten zitten er in dit ene zinnetje: De winkel is een lokatie verkoop is een gebeurtenis, producten is een voorwerp en klanten zijn mensen.
Maar is dit alles? Vraag altijd om details! Kan de klant zelf een koop tot stand brengen? Nope, daar is de verkoper voor. Verkopers zijn mensen, dus verkopers wordt ook een entiteit.
Stap 2. Relaties en hun cardinaliteit
De volgende stap is bepalen wat de relaties tussen de verschillende entiteiten zijn en wat de cardinaliteit van elke relatie is.
Relaties
De relatie is de verhouding tussen de entiteiten, gewoon zoals in de echte wereld; wat doet de ene entiteit met de ander, hoe hebben ze met elkaar te maken?
Let op': Een relatie gaat altijd twee kanten op; als A met B te maken heeft dan heeft B per definitie ook met A te maken. Beschrijf de relatie altijd voor beide richtingen!
In het winkelvoorbeeld geldt dat:
- Klanten -> verkopen, klanten plegen verkopen
- Verkopen -> klanten, verkopen worden gepleegd door klanten
- Klanten -> producten, klanten kopen producten
- Producten -> klanten, producten worden gekocht door klanten
- Klanten -> winkels, klanten doen hun inkopen in winkels
- Winkels -> klanten, winkels ontvangen klanten
- Winkels -> producten, winkels bevatten producten
- Producten -> winkels, producten liggen in winkels
- Winkels -> verkopen, winkels zijn de plekken waar verkopen gebeuren
- Verkopen -> winkels, verkopen gebeuren in winkels
- Producten -> verkopen, producten worden verkocht
- Verkopen -> producten, een verkoop omvat producten
Cardinaliteit
De cardinaliteit geeft aan hoeveel van de ene kant van de relatie horen bij hoeveel van de andere kant. In eerste instantie geef je voor elke relatie aan hoeveel van de ene kant er horen bij precies 1 van de andere kant; Hoeveel klanten horen bij 1 verkoop? Hoeveel verkopen horen er bij 1 klant? Hoeveel verkopen vinden er plaats in 1 winkel?
In het webwinkel voorbeeld is het relatiediagram alsvolgt: (onthoud dat product hier staat voor een soort product, niet voor losse stuks!)
- Klanten -> verkopen, 1 klant kan meerdere keren iets te kopen.
- Verkopen -> klanten, 1 verkoop wordt altijd gedaan door 1 klant tegelijk.
- Klanten -> producten, 1 klant kan meerdere producten kopen.
- Producten -> klanten, 1 product kan door meerdere klanten gekocht worden.
- Klanten -> winkels, 1 klant kan in meerdere winkels kopen.
- Winkels -> klanten, 1 winkel kan meerdere klanten ontvangen.
- Winkels -> producten, in 1 winkel liggen meerdere producten
- Producten -> winkels, 1 product (soort) kan in meerdere winkels liggen
- Winkels -> verkopen, in 1 winkel kunnen meerdere verkopen plaatsvinden.
- Verkopen -> winkels, 1 verkoop kan maar in 1 winkel tegelijk plaatsvinden.
- Producten -> verkopen, 1 product kan in meerder verkopen voorkomen.
- Verkopen -> producten, 1 verkoop kan meerdere producten omvatten.
Nu gaan we deze gegevens samenvoegen om de cardinaliteit van de hele relatie te vinden. Hiervoor gaan we de cardinaliteiten optellen per relatie. Om dit makkelijk te doen veranderen we de notatie een beetje door de 'terug' relatie andersom te noteren:
- Klanten -> verkopen, 1 klant kan meerdere keren in je winkel komen om iets te kopen.
- Verkopen -> klanten, 1 verkoop wordt altijd gedaan door 1 klant tegelijk.
Die laatste draaien we om zodat hij dezelfde entiteitvolgorde heeft als de eerst. Let op de pijl die nu andersom staat!
- Klanten <- verkopen, 1 verkoop wordt altijd gedaan door 1 klant tegelijk.
Cardinaliteit is er in vier soorten: een-op-een, een-op-meer, meer-op-een, meer-op-meer. In database ontwerp wordt dit aangeven met respectievelijk: 1:1, 1:N, M:N en M:1. Om de juiste term te vinden laat je alle '1' gewoon staan. Als er aan de linker kant 'meer' staat wordt dat 'm' en als er aan de rechter kant 'meer' staat wordt dat 'm'.
- Klanten -> verkopen, 1 klant kan meerdere keren iets te kopen. 1:N
- Klanten <- verkopen, 1 verkoop wordt altijd gedaan door 1 klant tegelijk. 1:1
De echte cardinaliteit kun je nu berekenen door domweg de grootste waarde voor links en rechts te nemen, waarbij 'n' of 'm' dus groter zijn dan '1'. In dit geval staat er links in beide gevallen een '1', dus dat blijft '1'. Aan de rechter kant staat een 'N' en een '1', de 'N' is de grootste dus dat wordt 'N'. De totale cardinaliteit is dus '1:N'. Een klant heeft meerdere 'kopen', maar elke 'koop' heeft maar één klant.
Als je dit voor de andere relaties ook doet krijg je:
- Klanten -> verkopen -> 1:N
- Klanten -> producten -> M:N
- Klanten -> winkels -> 1:N
- Verkopen -> producten -> M:N
- Winkels -> Verkopen -> 1:N
- Winkels -> Producten -> M:N
We hebben dus drie keer een 1-op-meer relatie, en drie keer een meer-op-meer relatie.
Let op: meer wordt ook wel veel genoemd. Een meer-op-meer relatie is hetzelfde als een veel-op-veel relatie en dat is weer hetzelfde als een M:N relatie.
Stap 3. Bepaal Attributen
De gegevens die je over elke entiteit wilt kunnen opslaan heten "attributen".
Van de producten die je verkoopt weet je bv wat de bestelcode is, wat de prijs is, wat de naam van de fabrikant is en wat het typenummer is.
Van de klanten weet je het klantnummer, de naam, adres, telefoonnummer.
Van de winkels weet je de locatiecode, de naam, adres.
Van de verkopen weet je wanneer ze gebeurd zijn, in welke winkel, welke producten er zijn verkocht, aan welke klant er is verkocht en voor hoeveel geld er is verkocht.
Van de verkoper weet je het werknemernummer, naam, adres.
Wat er precies in al die attributen staat is nu nog niet interessant. Het gaat alleen om wat je wilt opslaan.
De oplettende lezer ziet nu in de verkopentabel iets vreemds gebeuren; aparte attrbuten voor elk verkocht product. Dit wordt later opgelost.
De Primary Key
De primary key (ook wel PK genoemd) is een attribuut of verzameling attributen die het mogelijk maken om een record uniek aan te kunnen wijzen. Als een attribuut als PK wordt gedefinieerd dan moeten alle attributen die in die PK zitten altijd in elk record een waarde hebben (je mag het dus niet leeg laten) en de combinatie van waardes binnen de attributen moet samen uniek zijn in de tabel. Elke entiteit in een database model heeft precies één primary key, maar die primary key kan wel bestaan uit meerdere attributen.
In het voorbeeld bestaan een aantal duidelijke kandidaten voor de PK. Klanten hebben elk een unieke klantcode, producten hebben een unieke productcode en de verkopen hebben een verkoopnummer. Elk van deze gegevens is uniek en elk record zal zo'n waarde bevatten, dus deze attributen kunnen PK worden. Voor de primary-key wordt vaak een numerieke kolom genomen zodat je een record eenvoudig kunt opzoeken aan de hand van een getal. Getallen nemen in de database ook weinig ruimte in, met 4 bytes per getal kun je al tot meer dan 4 miljard tellen, maar je mag in principe elke soort waarde als PK gebruiken.
Koppeleintiteiten
In koppel-entiteiten (zoals die die meer-op-meer relaties oplossen) wordt meestal verwezen naar de PK attributen van de entiteiten die ze koppelen. De PK van een koppel-entiteit is meestal weer een verzameling van deze verwijzende attributen zodat b.v. een klant niet twee keer aan dezelfde verkoop gekoppeld kan worden. In dit geval worden de velde ook wel PF's genoemd, Primary-Foreign-keys, omdat ze Primary-key zijn voor de entiteit waar ze in staan, maar Foereign-key zijn omdat ze verwijzen naar een waarde die uit de Primary-key van een andere entiteit komt.
In het ERD worden de PK attrubuten aangegeven door de tekst 'PK' voor of achter de naam van het attribuut te zetten. In het voorbeeld heeft eigenlijk alleen de 'winkel' entiteit niet echt een duidelijke kandidaat voor de PK, dus daar verzinnen we een nieuw attribuut voor; winkelnummer.
De Foreign key
De Foreign Key in een entiteit is de verwijzing naar de primary key van een andere entiteit In het ERD krijgt dat attribuut 'FK' achter de naam. De foreign-key van een entiteit kan zelf ook weer deel uitmaken van de primary key van die entiteit en in dat geval staat er 'PF' achter het attribuut. Dit is meestal het geval bij de koppel-entiteiten, omdat je twee instanties van de twee entiteiten meestal maar een keer aan elkaar koppelt. (bij 1 verkoop wordt 1 procucttype maar 1x verkocht. Er kunnen wel meerdere stuks van worden verkocht, maar het type wordt maar 1x verkocht)
Derived data
Derived data betekent 'afgeleide data'. Gegevens die voortkomen uit andere gegevens die je al hebt opgeslagen. In dit geval is de 'totaalprijs' in 'verkopen' een klassiek geval van derived data. Je weet precies wat er is verkocht en wat elk product kost, dus kun je altijd uitrekenen hoeveel er in totaal is betaald. In principe is het dus niet nodig om het totaal op te slaan.
Waarom doe ik het hier dan wel? Omdat het hier gaat om een verkoop en de prijs van het product kan varieren met de tijd. Een product kan vandaag 10 euro kosten en volgende maand 8 euro, maar voor de administratie moet je weten wat het kostte op het moment van de verkoop en de eenvoudigste manier om dat te doen is door het hier op te slaan. Er zijn veel elegantere manieren, maar dat gaat te ver voor dit artikel.
Voorbeeld
Met keys ziet het model er nu zo uit:
Stap Bepaal de soort relaties
Onderlinge afhankelijkheid / verplichte relaties
Onderlinge afhankelijkheid bestaat als het een niet kan bestaan zonder het ander. In dit geval kan er bijvoorbeeld geen verkoop plaatsvinden als er geen klant is om de verkoop te doen en er kan geen verkoop plaatsvinden als er geen producten zijn om te verkopen.
De relaties verkoop -> klanten en verkoop -> producten zijn dus verplicht, maar omgekeerd is dat niet het geval: een klant kan prima bestaan zonder ooit iets te kopen en een product hoeft niet perse verkocht te zijn om te kunnen bestaan.
Recursive relationships
Recursive relaties. Soms verwijst een entiteit terug naar zichzelf. Denk bijvoorbeeld aan een hierarchie van werknemers. Een vakkenvuller heeft een meerdere en dat is de de chef, maar die chef is zelf ook een werknemer en heeft zelf ook een meerdere, namelijk de directeur. Het attribuut meerdere van de werknemersentiteit verwijst dus teerug naar de werknemersentiteit.
Identifying & non-identifying relationships
Een Identifying relationship is een relatie waarin de PK van de ene tabel onderdeel wordt van de PK van de gerelateerde tabel. Dit is bijvoorbeeld vaak het geval bij koppelentiteiten.
Verplichte / Mandatory relationships
Een verplichte relatie is een relatie waarin de FK altijd ingevuld moet zijn. Dit is bijvoorbeeld het geval bij het klantnummer van een verkoop; een verkoop mag nooit worden gemaakt zonder een klantnummer erin.
Bij een niet-verplichte relatie hoeft de FK niet ingevuld te zijn. Let op dat niet elke database dit aan kan.
Stap Teken het Entity Relation Diagram (ERD)
Notatie
Het ERD geeft een grafisch overzicht van de database. Zoals overal zijn er ook voor het ERD meerdere stijlen en soorten dus vooraf even een overzicht van de stijl die in dit artikel wordt gebruikt.
Deze notatie heet de kraaienpoot notatie, hierbij worden entiteiten als rechthoeken weergegeven en de relaties tussen de entiteiten worden weergegeven als lijnen van entiteit naar entiteit. De soort relatie wordt aangegeven door aan de uiteinden tekens te plaatsen. De kant waar er verplicht record(s) aanwezig moeten zijn krijgt een streep haaks op de lijn. Niet-verplicht wordt aangegeven met een cirkel ipv een streep. Meer wordt aangegeven door een kraaienpoot; de relatie-lijn splitst zich in meerdere (meestal drie) lijnen. Een 1:1 relatie wordt dus:
Een 1:N relatie wordt:
Een M:N relatie wordt:
Een recursieve relatie is een mooie lus die bij de entiteit zelf terugkomt:
Het model van het voorbeeld komt er dan zo uit te zien:
Stap Relaties opschonen
Er zijn een aantal relatiesoorten die je niet in een datamodel wilt hebben en die moet je dus oplossen. Dit zijn standaardproblemen met standaardoplossingen dus het stelt niets voor.
Redundant Relationships.
Soms krijg je in je model zogenaamde redundant relationships. Redundant betekent overbodig, dubbelop, niet noodzakelijk. Een redudant relationship is een relatie die op meerdere manieren tegelijk gemaakt wordt in het ERD.
In het geval van dit voorbeeld is er een directe relatie tussen klanten en producten, maar er zijn ook relaties van klanten naar verkopen en van verkopen naar producten, dus indirect is er via verkoop ook een relatie tussen klant en product. De relatie klant<->product wordt dus twee keer gemaakt en een daarvan is dus 'redundant'. In het geval van de winkel worden producten altijd via een verkoop aan de klant verkocht dus dus de relatie klant<->product mag weg.
Het oplossen van meer-op-meer relaties.
Meer-op-meer relaties (M:N) zijn niet rechtstreeks mogelijk in een database. Wat een M:N relatie zegt is dat een aantal records uit de ene tabel hoort bij een aantal records uit de andere tabel. Ergens moet je dus gaan opslaan om welke records het precies gaat en de oplossing is dan ook om de relatie op te splitsen in twee één-op-meer relaties.
Dit gebeurt door een nieuwe entiteit aan te maken die tussen de twee gerelateerde entiteiten in zit. In dit geval is er een meer-op-meer relatie tussen verkopen en producten. En dat wordt opgelost door een nieuwe entiteit te maken; verkoop-detail. Die entiteit krijgt een meer-op-één relatie met verkopen, en een één-op-meer relatie met producten. (samen dus weer meer-op-meer)
Voorbeeld
In het voorbeeld zit een meer-op-meer relatie die moeten worden opgelost: producten<->winkels. Deze relatie geeft aan welke producten er in welke winkels liggen en dat is dus gewoon de voorraad van de winkel.
En met alle attributen en keys erin wordt het:
Normalisatie
Normalisatie maakt je model flexibel en betrouwbaar. Het genereert wel wat overhead omdat je meestal meer tabellen krijgt, maar daar staat tegenover dat je met je datamodel alle kanten op kunt zonder dat je het hoeft aan te passen om uitbreidingen en veranderingen te kunnen verwerken.
Normalisatie, de eerste vorm
De eerste vorm van normalisatie zegt dat er in een tabel geen herhalende kolommen/attributen mogen zijn. In de entiteit verkopen zitten vier productenattributen, dat is zo'n herhalende kolom
Wat hier fout aan is is dat er nu maar vier producten kunnen worden verkocht. Wil je er vier verkopen, dan moet je ofwel een tweede verkoop starten, of je datamodel aanpassen door 'product5' toe te voegen. Dit is natuurlijk allebei volkomen ongewenst.
Wat je hier hebt is gewoon een M:N relatie met de beperking dat N niet hoger kan zijn dan 4. De oplossing is dan ook dezelfde als voor de M:N relatie: maak een koppelentiteit. In dit geval 'verkoop_detail
Normalisatie, de tweede vorm
De tweede vorm zegt dat alle attributen van een entiteit functioneel afhankelijk moeten zijn van de hele primary-key. Dat betekent dat je elk attribuut van een entiteit alleen kunt identificeren middels de gehele primary-key.
Normalisatie, de derde vorm
De derde vorm zegt dat alle attributen rechtstreeks afhankelijk moeten zijn van de primary-key, en niet van andere attributen.Dit lijkt op wat de 2e normaalvorm zegt, maar in de 2e normaalvorm wordt eigenlijk het omgekeerde gezegd. In de 2e normaalvorm wijs je attributen aan via de PK, in de 3e normaalvorm moet elk attribuut alleen afhankelijk zijn van de PK, en niets anders.
Normalisatie, nog meer vormen
Normalisatie kent nog veel meer vormen, maar die zijn voor de gewone gebruiker niet echt interessant. Deze vormen zijn zeer gespecialiseerd voor bepaalde toepassingen. Als je je houdt aan de ontwerp regels en de normalisatie, dan kom je op een ontwerp dat voor de meeste toepassingen prima voldoet.
Stap Bepaal de datatypes van de attributen
Nu is het tijd om uit te zoeken welke datatypes er gebruikt moeten worden voor de verschillende attributen. Database sterven van de datatypes. Een aantal is gestandaardiseerd maar veel databases hebben hun eigen datatypes met elk hun eigen voordelen. De betere databases zoals PostgreSQL maken het zelfs mogelijk om je eigen datatypes te definieeren voor het geval de standaard types niet kunnen wat je nodig hebt.
De standaard datatypes die elke database kent zijn: CHAR, VARCHAR, TEXT, FLOAT, DOUBLE en INT. Dit zijn ook de meest-gebruikte.
Voor meer details kun je beter kijken in de handleiding van je database, zoals PostgreSQL of MySQL
Tekst
- CHAR(lengte) - bevat tekst (letters, cijfers, leestekens..) CHAR heeft als eigenschap dat er altijd een vast aantal tekens wordt opgeslagen. Als je een CHAR(10) definieert dan kun je er maximaal 10 tekens in kwijt maar als je er maar twee tekens in zet dan slaat de database toch tien tekens op. De lege ruimte wordt opgevuld met spaties.
VARCHAR(lengte) - hetzelfde als CHAR, maar bij VARCHAR wordt er alleen zoveel ruimte gebrukt als noodzakelijk. TEXT - kan zeer grote lappen tekst bevatten. Afhankelijk van het merk database kan dit oplopen tot gigabytes.
Getallen
- INT - Bevat een positief of negatief geheel getal. Veel databases hebben varianten op de INT, zoals TINYINT, SMALLINT, MEDIUMINT, BIGINT, INT2, INT4, INT8. Deze verschillen van de INT alleen in de afmeting van het getal dat er in past. Een gewone INT is 4 bytes lang (INT4)) en daar passen dus getallen van -2147483647 tot +2147483646 in, of als je hem als UNSIGNED definieert, van 0 tot 4294967296 in. De INT8, of BIGINT kan nog een stap hoger, van 0 tot 18446744073709551616, maar neemt wel 8 bytes aan diskruimte in beslag, ookal zet je er '1' in.
- FLOAT, DOUBLE - Hetzelfde idee als INT, maar nu mogen er ook 'Floating point' getallen in, dus breuken en cijfers achter de komma. Let wel goed op dat dit in sommige databases geen zuivere koffie is. Bijvoorbeeld in MySQL kun je slecht met die dingen rekenen, (1/3)*3 wordt met MySQL's floats 0.9999999, niet 1.
- NUMERIC, DECIMAL - Dit lijkt op FLOAT, maar is precies. Het heeft een exact aantal cijfers voor en achter de komma.
Andere types
- BLOB - voor binaire data zoals bestanden. Gebruik dit spaarzaam, het is in de meeste toepassingen helemaal fout.
- INET - voor IP adressen. Kan ook netmasks aan.
Voorbeeld
Voor het voorbeeldmodel worden de datatypes alsvolgt:
Afbeelding:Erd met atributen met keys goed denorm.gif
Het uitgewerkte model
Als je de normalisatieregels toepast kom je eigenlijk alleen tegen dat de fabrikant in de producten tabel ook een aparte tabel hoort te zijn:
Naslag
Woordenlijst
- Attributes (ENG) Attributen (NL)- gedetailleerde gegevens over een entiteit, zoals prijs, lengte, naam.
- Cardinality (ENG) Cardinaliteit (NL) - de verhouding tussen twee entiteiten, in aantallen. b.v.: een persoon kan meerdere bestellingen doen.
- Entities (ENG) Entiteiten (NL) - een abstract gegeven dat je in de database opslaat. b.v. klanten, producten.
- Foreign-key (ENG) Vreemde sleutel / externe sleutel (NL) - een verwijzing naar de Primary-key van een andere tabel. Foreign-key kolommen mogen alleen waardes bevatten die voorkomen in de Primary-key kolom waar ze naar verwijzen.
- Key (ENG) Sleutel (NL) - een key is een gegeven dat wordt gebruikt om records aan te wijzen. De bekendste key is de primary-key (zie primary-key)
- Normalisation (ENG) Normalisatie (NL) - Een flexibel datamodel volgt een aantal regels. Het toepassen van deze regels heet normaliseren.
- Transactie (ENG), Transaction (NL) - een verzameling queries die hij elkaar horen.
- Primary-key (ENG) Primaire sleutel (NL) - Een of meer kolommen binnen een tabel die samen een unieke combinatie van waardes vormen waarmee elk record afzonderlijk kan worden aangewezen. Denk bv aan een klantnummer of het serienummer van een televisie.
Normalisatie in een notendop
Kort samengevat zijn dit de regels waar je je bij een ontwerp aan moet houden:
- Geen 'een-op-een' relaties. Als je een 'een-op-een' relatie hebt tussen twee tabellen dan heb je twee tabellen waar elk record uit de ene tabel altijd hoort bij precies 1 record uit de andere tabel. Feitelijk heb je dan dus twee tabellen die elk een deel van de data van de records bevatten. Dit mag niet voorkomen omdat je in je SQL de tabellen toch weer samenvoegt en zo een hele hoop resources verspilt.
- Geen herhalende kolommen in een tabel. Een verkoopbon kan een groot aantal verkochtte producten bevatten. Als je die als kolommen in een tabel zet dan is het aantal kolommen bepalend voor het maximum aantal producten dat per verkoop verkocht kan worden. Wil je meer producten verkopen dan moet je het aantal kolommen uitbreiden of meerdere bonnen maken. Zo'n beperking is niet acceptabel.
- Alle gegevens in een record moeten afhankelijk zijn van de gehele primary-key. Als je meerdere producten koopt moet je elk product afzonderlijk kunnen aanwijzen in de tabel. Als je meerdere kolommen voor bestelde producten hebt kun je dit niet.
- Geen tabellen met dezelfde (of sterk gelijkende) structuur binnen dezelfde database.Als twee tabellen sterk op elkaar lijken dan is het meestal beter om ze samen te voegen. Producten hebben allemaal eigenschappen als naam, prijs, fabrikant, en sommige hebben andere eigenschappen extra, zoals kleur, afmeting, lengte. Die verschillen zijn echter lang niet groot genoeg om een aparte tabel te verantwoorden. Het is nu eenmaal veel eenvoudiger om met 1 tabel te werken dan om voor elk product eerst op te gaan zoeken in welke tabel het zou moeten staan.









