Normalformer
Funksjonelle avhengigheter, lukning, dekomponering, og veien fra 1NF til BCNF — med en interaktiv kalkulator og et helt gjennomarbeidet eksempel.
Hvis du vet X, vet du Y
En funksjonell avhengighet (FD) er det formelle språket for hvordan attributter «bestemmer» hverandre. Vi skriver X → Y, og leser det «X bestemmer Y» — eller mer presist: hvis to rader er enige om alle attributter i X, så må de også være enige om alle attributter i Y.
Gitt et relasjonsskjema R, og delmengder X, Y ⊆ R. FD-en X → Y holder på R hvis enhver lovlig instans r(R) tilfredsstiller: for hvert par av tupler t₁, t₂ i r, hvis t₁[X] = t₂[X], så er t₁[Y] = t₂[Y].
Hva FD-er er og ikke er
- FD-en er en regel om alle gyldige instanser, ikke et empirisk faktum om en konkret tabell.
- Det at en konkret instans tilfeldigvis tilfredsstiller en FD, betyr ikke at FD-en holder på skjemaet.
- FD-er kommer fra domenekunnskap: «hver bil har én eier», «hvert rom har én kapasitet i en gitt bygning».
| A | B | C | D |
|---|---|---|---|
| a₁ | b₁ | c₁ | d₁ |
| a₁ | b₂ | c₁ | d₂ |
| a₂ | b₂ | c₂ | d₂ |
| a₂ | b₃ | c₂ | d₃ |
| a₃ | b₃ | c₂ | d₄ |
I instansen ovenfor: A → C er tilfredsstilt (begge a₁-rader har c₁; begge a₂-rader har c₂). Derimot er C → A ikke tilfredsstilt: rad 4 og 5 har samme C (c₂) men ulik A (a₂ vs a₃).
Trivielle FD-er
En FD X → Y er triviell hvis Y ⊆ X — den holder for alle instanser uansett. {A, B} → A er triviell. Vi bryr oss bare om ikke-trivielle FD-er.
Klasserom(bygning, romnr, kapasitet)?romnr → kapasitet alene er ikke gyldig — to bygninger kan ha rom med samme nummer, men ulik kapasitet.Tre regler som genererer alle
Gitt en mengde FD-er F, finnes det andre FD-er som må holde som logisk konsekvens. Mengden av alle slike kalles lukningen F⁺. Tre aksiomer er nok til å utlede alle:
Y ⊆ X, så X → Y. Triviell.X → Y, så X, Z → Y, Z for ethvert Z.X → Y og Y → Z, så X → Z.Disse aksiomene er sunne (de utleder bare gyldige FD-er) og komplette (alt som er logisk gyldig kan utledes). Tre nyttige avledede regler:
- Union: hvis
X → YogX → Z, såX → Y, Z. - Dekomposisjon: hvis
X → Y, Z, såX → YogX → Z. - Pseudotransitivitet: hvis
X → YogW, Y → Z, såW, X → Z.
Hva kan vi utlede?
X⁺Settet av alle attributter som er funksjonelt bestemt av X under en gitt FD-mengde F. Skrives X⁺ (eller X⁺F).
Algoritmen
- Initialiser
resultat ← X. - For hver FD
α → βiF: hvisα ⊆ resultat, oppdaterresultat ← resultat ∪ β. - Gjenta steg 2 til
resultatikke endrer seg.
Hva bruker vi X⁺ til?
- Supernøkkel-test:
Xer supernøkkel hvis og bare hvisX⁺ = R. - FD-test:
X → Yfølger fra F hvis og bare hvisY ⊆ X⁺. - Lossless join-test: for binær dekomposisjon
R₁, R₂, sjekk om(R₁ ∩ R₂)⁺ ⊇ R₁ellerR₂.
Interaktiv lukningskalkulator
Et lite leksjonseksempel: R(A, B, C, D, E) med F = { A → B, B → C, CD → E, E → A }. Velg attributter for X, og se lukningen utvikle seg trinn for trinn.
Hva er {A}⁺? Hva med {D}⁺? Hva med {A, D}⁺? Når du finner et X der X⁺ = {A,B,C,D,E}, har du funnet en supernøkkel.
{C, D} en supernøkkel?Finn alle kandidatnøkler
Supernøkkel for R: et sett K der K⁺ = R. Kandidatnøkkel: en supernøkkel som er minimal (ingen ekte delmengde er supernøkkel). Primærnøkkel: en valgt kandidatnøkkel.
Algoritme: alle kandidatnøkler
- Identifiser hvilke attributter aldri er på høyre side i en FD (eller bare i en FD som også har dem på venstre side). De må være med i hver kandidatnøkkel.
- Identifiser hvilke attributter aldri er på venstre side. De er ikke nødvendige.
- For de mellomliggende attributtene: prøv minimale supersett av (1) og se om lukningen dekker R.
I R(A,B,C,D,E) med F fra forrige seksjon: D dukker aldri opp på høyre side, så D må være med i hver kandidatnøkkel. {A,B,C,E} er gjensidig avledbare via FD-syklusen A→B→C→A (gjennom CD→E→A). Så kandidatnøklene er {A,D}, {B,D}, {C,D}, {E,D}.
R(A,B,C,D) med F = {AB → C, C → D}, hvilken er kandidatnøkkelen?Splitt en relasjon — uten å miste informasjon
Når vi splitter R i to skjemaer R₁ og R₂, vil vi at den naturlige join-en av r₁ og r₂ reproduserer den opprinnelige r nøyaktig. Hvis det er tilfellet, er dekomposisjonen lossless.
En lossy dekomposisjon kan gi flere rader etter join — men noen av dem er falske. Ekstra «tupler» som ikke svarer til virkeligheten er nettopp tap av informasjon: vi har mistet evnen til å skille hvilke rader som hører sammen.
Lossless join-testen
For binær dekomposisjon av R i R₁ og R₂, hvor minst én av disse FD-ene gjelder i F⁺:
R₁ ∩ R₂ → R₁, ellerR₁ ∩ R₂ → R₂.
Med andre ord: skjæringen må være supernøkkel for minst én av delene.
Eksempel: lossy
R(ID, navn, gate, by, lønn). Vi splitter på navn:
- R₁(ID, navn)
- R₂(navn, gate, by, lønn)
Skjæringen er {navn}. Men «navn» er ikke unikt — to ulike personer kan hete «Kim Hansen». {navn}⁺ = {navn} (ingen FD med navn på venstre side, alene). Ikke supernøkkel for noen av delene → lossy. Etter join får vi krysskobling mellom de to Kim-radene, og kan ikke lenger fortelle hvilken adresse som hører til hvilken Kim.
Eksempel: lossless
R(ID, navn, dept, bygning, budsjett). Vi splitter på dept:
- R₁(ID, navn, dept)
- R₂(dept, bygning, budsjett)
Skjæringen er {dept}. Vi vet at dept → bygning, budsjett, så {dept}⁺ ⊇ R₂. Supernøkkel for R₂ → lossless.
Dependency preservation
Dekomposisjonen er dependency preserving hvis foreningen av FD-ene som kan testes lokalt i hver del, fortsatt er logisk ekvivalent med F. Hvis ikke, må noen FD-er sjekkes med en join etter hver oppdatering — kostbart.
Lossless og dependency preserving er uavhengige egenskaper. Ideelt sett vil vi ha begge. Vi kan alltid få lossless. Vi kan ikke alltid få begge samtidig med BCNF — men 3NF garanterer det.
Fra 1NF til BCNF
α → β: enten er α supernøkkel, eller hvert attributt i β−α er del av en kandidatnøkkel. Garantert lossless + dependency preserving dekomposisjon.BCNF ⊂ 3NF ⊂ 2NF ⊂ 1NF. Et BCNF-skjema er automatisk i 3NF, og videre i 2NF og 1NF.
3NF i ord
For hver ikke-triviell FD α → β som holder, må minst én av:
αer supernøkkel for R, eller- hvert attributt i
β − αer med i en eller annen kandidatnøkkel for R.
BCNF i ord
For hver ikke-triviell FD α → β som holder, må:
αer supernøkkel for R.
Det er det. BCNF har bare ett alternativ — det andre alternativet i 3NF er det som åpner for dependency preservation, men også for visse redundanser.
Boyce–Codd normalform
Algoritme: dekomposer R til BCNF
- Beregn alle FD-er som holder (lukningen
F⁺). - Finn en ikke-triviell FD
α → βhvorαikke er supernøkkel. Hvis ingen finnes, er R i BCNF — ferdig. - Splitt R i
R₁ = α ∪ βogR₂ = R − (β − α). - Gjenta rekursivt på R₁ og R₂.
Eksempel
InDep(id, navn, lønn, dept, bygning, budsjett) med F = {id → navn, lønn, dept; dept → bygning, budsjett}.
- FD-en
dept → bygning, budsjettbryter BCNF:depter ikke supernøkkel. - Splitt:
α = dept,β = {bygning, budsjett}. - R₁ = (dept, bygning, budsjett) → kalt Avdeling.
- R₂ = R − (β − α) = (id, navn, lønn, dept) → kalt Larer.
Verifiser: id → navn, lønn, dept har id som supernøkkel. dept → bygning, budsjett har dept som supernøkkel i R₁. Begge tabeller er nå i BCNF.
Skjæringen R₁ ∩ R₂ = {dept}. {dept}⁺ inkluderer {bygning, budsjett}, altså hele R₁ — supernøkkel. Lossless. ✓
Helt fra tabell til BCNF
Gitt skjemaet R(prosjektkode, prosjektnavn, ansattnr, navn, timer) der vi registrerer hvor mange timer hver ansatt har jobbet på hvert prosjekt. Krav fra domenet:
- Hvert prosjekt har et unikt navn:
prosjektkode → prosjektnavn. - Hver ansatt har et navn (ikke unikt mellom personer):
ansattnr → navn. - For hver (ansatt, prosjekt) registreres timer:
ansattnr, prosjektkode → timer.
Steg 1 · Finn kandidatnøkkel
Hvilke attributter er aldri på høyre side? ansattnr og prosjektkode. De må være med. {ansattnr, prosjektkode}⁺ = {ansattnr, prosjektkode, navn, prosjektnavn, timer} = R. Minimal supernøkkel → kandidatnøkkel.
Steg 2 · Sjekk BCNF
FD-er å sjekke:
prosjektkode → prosjektnavn— er{prosjektkode}supernøkkel?{prosjektkode}⁺ = {prosjektkode, prosjektnavn}. Ikke hele R. Bryter BCNF.ansattnr → navn— er{ansattnr}supernøkkel?{ansattnr}⁺ = {ansattnr, navn}. Ikke hele R. Bryter BCNF.ansattnr, prosjektkode → timer— er{ansattnr, prosjektkode}supernøkkel? Ja (det er kandidatnøkkelen). OK.
Steg 3 · Dekomponer
Velg første brudd: prosjektkode → prosjektnavn. Splitt:
- R₁ = {prosjektkode, prosjektnavn} → Prosjekt
- R₂ = R − {prosjektnavn} = {prosjektkode, ansattnr, navn, timer}
R₁ er i BCNF (skjema med to attributter er alltid i BCNF — et nyttig faktum). R₂ må sjekkes videre.
I R₂: ansattnr → navn bryter fortsatt. Splitt:
- R₂ₐ = {ansattnr, navn} → Ansatt
- R₂ᵦ = R₂ − {navn} = {prosjektkode, ansattnr, timer} → Jobber
Begge er nå i BCNF.
Sluttskjema
CREATE TABLE Prosjekt (
prosjektkode CHAR(6) PRIMARY KEY,
prosjektnavn VARCHAR(100) UNIQUE NOT NULL
);
CREATE TABLE Ansatt (
ansattnr INT PRIMARY KEY,
navn VARCHAR(80) NOT NULL
);
CREATE TABLE Jobber (
ansattnr INT,
prosjektkode CHAR(6),
timer DECIMAL(5,1) NOT NULL,
PRIMARY KEY (ansattnr, prosjektkode),
FOREIGN KEY (ansattnr) REFERENCES Ansatt(ansattnr),
FOREIGN KEY (prosjektkode) REFERENCES Prosjekt(prosjektkode)
);
Tre tabeller, hver i BCNF. Ingen redundans (prosjektnavn lagres én gang per prosjekt; ansattnavn lagres én gang per ansatt). Lossless: skjæringene er supernøkler. Dependency preserving: alle tre FD-er kan sjekkes lokalt.
{prosjektkode}⁺ på det punktet? Holder lossless join-testen?Når BCNF koster for mye
Det klassiske eksempelet: dept_advisor(student, lærer, dept) der:
- en lærer er knyttet til nøyaktig ett institutt:
lærer → dept - en student kan ha flere veiledere, men maks én per institutt:
student, dept → lærer
Kandidatnøkler: {student, dept} og {student, lærer}. FD-en lærer → dept bryter BCNF (lærer er ikke supernøkkel). Splitt:
- R₁ = (lærer, dept)
- R₂ = (student, lærer)
Begge er i BCNF. Men: FD-en student, dept → lærer kan ikke lenger sjekkes lokalt — student er i R₂, dept er i R₁. Hver gang noen legger til en (student, lærer)-par, må vi joine for å verifisere constrainten.
Velg BCNF når
- SQL likevel ikke kan håndheve generelle FD-er effektivt (det meste av tiden)
- Du foretrekker å eliminere all FD-redundans
- Lærebokas anbefaling — standardvalget
Velg 3NF når
- Du har en tapt FD som er forretningskritisk og som må sjekkes per oppdatering
- Du har infrastruktur for materialiserte views med constraints
- Eksamensoppgaven eksplisitt ber om dependency preservation
«Selv om vi ikke alltid kan få en dependency-preserving BCNF-dekomposisjon, er det fortsatt foretrukket å gå for BCNF — fordi sjekking av andre FD-er enn primærnøkkelen er vanskelig i SQL likevel.» (Silberschatz §7.3.3)
Oppsummering av kapittel 4
Vil du teste hele kapittelet samlet? Gå tilbake til oversikten og ta sluttquizen — tolv spørsmål på tvers av 4A, 4B og 4C.