Lær prinsippene for god databasedesign gjennom de tre normalformene.
Forestill deg at en skole lagrer all informasjon i én eneste tabell:
| elev_id | fornavn | etternavn | klasse | laerer | fag | karakter |
|---|---|---|---|---|---|---|
| 1 | Emma | Hansen | 10A | Kari Nordli | Matematikk | 5 |
| 1 | Emma | Hansen | 10A | Per Haugen | Norsk | 4 |
| 1 | Emma | Hansen | 10A | Lise Vik | Naturfag | 5 |
| 2 | Oliver | Johansen | 10A | Kari Nordli | Matematikk | 3 |
| 2 | Oliver | Johansen | 10A | Per Haugen | Norsk | 4 |
Unormaliserte tabeller (som den store enkelttabellen over) fører til tre typer problemer kalt anomalier:
Eksempel: Emma Hansen bytter fra 10A til 10B. Informasjonen «10A» finnes i tre rader. Hvis vi bare oppdaterer to av dem, står det 10B i to rader og 10A i én rad – inkonsistens.
Eksempel: Vi ansetter en ny lærer, Jon Hauge, som skal undervise i historie. Men vi kan ikke legge ham inn i tabellen uten å også ha en elev og en karakter, fordi alle kolonnene er i samme tabell.
Eksempel: Hvis vi sletter alle karakterrader for Liam (den eneste eleven i 10C med naturfagskarakter), mister vi ikke bare karakteren, men potensielt informasjonen om at Lise Vik underviser i naturfag.
Alle tre anomaliene skyldes det samme grunnproblemet: data som logisk hører til forskjellige ting er blandet sammen i én tabell. Normalisering løser dette ved å dele data inn i separate, spesialiserte tabeller.
En tabell er i første normalform (1NF) når:
1. Alle kolonner inneholder atomære verdier (udelelige enkelverdier)
2. Det finnes ingen gjentakende grupper eller kolonner
3. Hver rad er unik (tabellen har en primærnøkkel)
| elevid | fornavn | fag |
|---|---|---|
| 1 | Emma | Matematikk, Norsk, Naturfag |
| 2 | Oliver | Matematikk, Norsk |
fag inneholder flere verdier i én celle – det bryter med 1NF. Hvordan finner du alle elever som tar naturfag? Du kan ikke bruke WHERE fag = 'Naturfag' fordi verdien er «Matematikk, Norsk, Naturfag».| elevid | fornavn | fag |
|---|---|---|
| 1 | Emma | Matematikk |
| 1 | Emma | Norsk |
| 1 | Emma | Naturfag |
| 2 | Oliver | Matematikk |
| 2 | Oliver | Norsk |
Nå har hver celle bare én verdi, og vi kan enkelt søke:
``sql
SELECT fornavn FROM eleverfag WHERE fag = 'Naturfag';
``Et annet eksempel: Gjentakende kolonner
elevid fornavn fag1 fag2 fag3 1 Emma Matematikk Norsk Naturfag 2 Oliver Matematikk Norsk NULL
Dette bryter også med 1NF – gjentakende kolonner for fag. Hva hvis en elev tar fire fag? Da må vi legge til en ny kolonne. Løsningen er å dele opp i rader, som vist over.
En tabell er i andre normalform (2NF) når:
1. Den er i 1NF
2. Alle ikke-nøkkelkolonner er fullt funksjonelt avhengige av hele primærnøkkelen
2NF er relevant for tabeller med sammensatte primærnøkler (primærnøkler som består av to eller flere kolonner). Hvis en kolonne bare avhenger av én del av nøkkelen, bør den flyttes til en egen tabell.
Tenk deg en tabell for kursregistrering med sammensatt primærnøkkel (elevid + fagid):
| elevid | fagid | elevnavn | fagnavn | karakter |
|---|---|---|---|---|
| 1 | 1 | Emma | Matematikk | 5 |
| 1 | 2 | Emma | Norsk | 4 |
| 2 | 1 | Oliver | Matematikk | 3 |
elevnavn avhenger bare av elevid (ikke av hele nøkkelen). fagnavn avhenger bare av fagid. Bare karakter avhenger av hele nøkkelen (elevid + fagid).| elevid | elevnavn |
|---|---|
| 1 | Emma |
| 2 | Oliver |
fag:
| fagid | fagnavn |
|---|---|
| 1 | Matematikk |
| 2 | Norsk |
karakterer:
| elevid | fagid | karakter |
|---|---|---|
| 1 | 1 | 5 |
| 1 | 2 | 4 |
| 2 | 1 | 3 |
En tabell er i tredje normalform (3NF) når:
1. Den er i 2NF
2. Ingen ikke-nøkkelkolonne er avhengig av en annen ikke-nøkkelkolonne (ingen transitive avhengigheter)
Med andre ord: alle ikke-nøkkelkolonner avhenger direkte av primærnøkkelen, ikke indirekte via andre kolonner.
| elevid | fornavn | etternavn | postnummer | poststed |
|---|---|---|---|---|
| 1 | Emma | Hansen | 0150 | Oslo |
| 2 | Oliver | Johansen | 0150 | Oslo |
| 3 | Nora | Olsen | 5003 | Bergen |
poststed avhenger av postnummer, som igjen avhenger av elevid. Dette er en transitiv avhengighet: elevid → postnummer → poststed. Poststed avhenger ikke direkte av elevid.| elev_id | fornavn | etternavn | postnummer |
|---|---|---|---|
| 1 | Emma | Hansen | 0150 |
| 2 | Oliver | Johansen | 0150 |
| 3 | Nora | Olsen | 5003 |
poststeder:
| postnummer | poststed |
|---|---|
| 0150 | Oslo |
| 5003 | Bergen |
Nå lagres hvert poststed bare én gang, og alle ikke-nøkkelkolonner avhenger direkte av primærnøkkelen i sin respektive tabell.
2NF – Full funksjonell avhengighet: Alle ikke-nøkkelkolonner avhenger av hele primærnøkkelen, ikke bare en del av den. (Relevant ved sammensatte primærnøkler.)
3NF – Ingen transitive avhengigheter: Alle ikke-nøkkelkolonner avhenger direkte av primærnøkkelen, ikke av andre ikke-nøkkelkolonner.
Huskeregel: «Nøkkelen, hele nøkkelen, og ingenting annet enn nøkkelen.»
- 1NF: Dataene har en nøkkel (primærnøkkel, atomære verdier)
- 2NF: Avhenger av hele nøkkelen (ikke deler av sammensatt nøkkel)
- 3NF: Ingenting annet enn nøkkelen (ingen transitive avhengigheter)
| utlaanid | elevnavn | klasse | boktittel | forfatter | isbn | utdato | inndato |
|---|---|---|---|---|---|---|---|
| 1 | Emma Hansen | 10A | Sofies verden | Jostein Gaarder | 978-82-03-19 | 2024-09-01 | 2024-09-15 |
| 2 | Emma Hansen | 10A | Beatles | Lars S. Christensen | 978-82-02-25 | 2024-09-10 | NULL |
| 3 | Oliver Johansen | 10A | Sofies verden | Jostein Gaarder | 978-82-03-19 | 2024-09-05 | 2024-09-20 |
elevnavn er ikke atomært (fornavn + etternavn i én kolonne) → bryter 1NFSteg 1: 1NF – atomære verdier
Del elevnavn i fornavn og etternavn. Fjern gjentakende data.
Steg 2: 2NF og 3NF – fjern partielle og transitive avhengigheter
``sql
CREATE TABLE elever (
elevid INTEGER PRIMARY KEY AUTOINCREMENT,
fornavn TEXT NOT NULL,
etternavn TEXT NOT NULL,
klasse TEXT
);
CREATE TABLE boker (
bokid INTEGER PRIMARY KEY AUTOINCREMENT,
tittel TEXT NOT NULL,
forfatter TEXT,
isbn TEXT UNIQUE
);
CREATE TABLE utlaan (
utlaanid INTEGER PRIMARY KEY AUTOINCREMENT,
elevid INTEGER NOT NULL,
bokid INTEGER NOT NULL,
utlaansdato DATE NOT NULL,
innleveringsdato DATE,
FOREIGN KEY (elevid) REFERENCES elever(elevid),
FOREIGN KEY (bokid) REFERENCES boker(bokid)
);
-- Sett inn data
INSERT INTO elever (fornavn, etternavn, klasse) VALUES
('Emma', 'Hansen', '10A'),
('Oliver', 'Johansen', '10A');
INSERT INTO boker (tittel, forfatter, isbn) VALUES
('Sofies verden', 'Jostein Gaarder', '978-82-03-19'),
('Beatles', 'Lars S. Christensen', '978-82-02-25');
INSERT INTO utlaan (elevid, bokid, utlaansdato, innleveringsdato) VALUES
(1, 1, '2024-09-01', '2024-09-15'),
(1, 2, '2024-09-10', NULL),
(2, 1, '2024-09-05', '2024-09-20');
``
Nå er data normalisert: elevinformasjon lagres bare én gang, bokinformasjon lagres bare én gang, og utlånstabellen kobler dem sammen.
Normalisering er generelt en god praksis, men det finnes situasjoner der man bevisst velger å denormalisere – altså tillate noe redundans:
Hovedregelen er: Start alltid med normalisering. Denormaliser bare hvis du har dokumenterte ytelsesproblemer som ikke kan løses på andre måter. Det er mye lettere å denormalisere en normalisert database enn å normalisere en rotete en.
Når du skal designe en database fra bunnen av, følg denne prosessen:
1. Forstå kravene – Hva skal systemet gjøre? Hvilke data trengs?
2. Identifiser entiteter – Hva er «tingene» vi lagrer? (Elever, bøker, bestillinger...)
3. Definer attributter – Hvilke egenskaper har hver entitet?
4. Finn relasjoner – Hvordan henger entitetene sammen?
5. Tegn ER-diagram – Visualiser strukturen
6. Normaliser – Sjekk 1NF, 2NF, 3NF og juster tabellene
7. Skriv SQL – Opprett tabellene med CREATE TABLE
8. Test med data – Sett inn testdata og kjør spørringer
9. Iterer – Juster designet basert på reelle behov
God databasedesign er en ferdighet som utvikles med praksis. Jo flere databaser du designer, jo bedre intuisjon utvikler du for hva som fungerer og hva som ikke fungerer.
Pass deg for disse vanlige feilene:
1. Kommaseparerte lister i kolonner: fag = "Matte, Norsk, Naturfag" bryter med 1NF. Bruk en koblingstabell.
2. Gjentakende kolonner: telefon1, telefon2, telefon3 bryter med 1NF. Lag en egen tabell for telefonnumre.
3. Beregnede verdier lagret i tabellen: Ikke lagre alder hvis du har fodselsdato. Beregn det med SQL i stedet.
4. Miks av ulike entiteter: Ikke legg elevinfo og faginfo i samme tabell bare fordi de brukes sammen.
5. For mye normalisering: Det finnes tilfeller der det er greit å lagre noe «ekstra» data for å forenkle spørringer.
Hva er normalisering i databasesammenheng?
Denne tabellen bryter med første normalform (1NF). Hvorfor?
| elev_id | fornavn | hobbyer |
|---|---|---|
| 1 | Emma | Fotball, Lesing, Sjakk |
| 2 | Oliver | Gaming, Fotball |
Hvilken type anomali oppstår i dette scenariet?
Du har en tabell der elevnavn og klasse lagres sammen med karakterer. Emma Hansen er i klasse 10A og har tre karakterrader. Du oppdaterer klassen til 10B i to av radene, men glemmer den tredje.
Denne tabellen er unormalisert. Identifiser hvilke normalformer den bryter med, og vis hvordan du normaliserer den til 3NF.
| bestillingid | kundenavn | kundeepost | produkt | pris | antall | totalpris |
|---|---|---|---|---|---|---|
| 1 | Emma Hansen | emma@mail.no | USB-kabel | 79 | 2 | 158 |
| 2 | Emma Hansen | emma@mail.no | Mus | 299 | 1 | 299 |
| 3 | Oliver Johansen | oliver@mail.no | USB-kabel | 79 | 3 | 237 |
Hvilken normalform bryter denne tabellen med?
| ansattid | navn | avdeling | avdelingsleder |
|---|---|---|---|
| 1 | Kari Nordli | Matematikk | Per Haugen |
| 2 | Lise Vik | Naturfag | Tom Bakke |
| 3 | Jon Hauge | Matematikk | Per Haugen |
Design en fullstendig normalisert database (3NF) for et treningssenter. Systemet skal håndtere:
- Medlemmer (navn, telefon, e-post, medlemstype)
- Treningsklasser (navn, beskrivelse, varighet, instruktør)
- Påmeldinger til klasser (medlem, klasse, dato, tidspunkt)
- Instruktører (navn, spesialisering, telefon)
Tegn eller beskriv tabellene med kolonner, primærnøkler, fremmednøkler og relasjonstyper. Skriv deretter CREATE TABLE-kode for alle tabellene.
Hva er huskeregelen for de tre første normalformene?