Databaseteori 19. Databaser Fra længe før EDB alderen har man haft arkiver med viden: lande har haft folkeregistre med oplysninger om landet borgere, firmaer har haft oplysninger om kunder og salg, man har haft telefonbøger, adresselister og meget andet. I takt med EDB-udviklingen er det blevet lettere at arkivere viden og ikke mindst genfinde og bruge denne viden. De danske myndigheder har i dag et forbløffende stort antal registre om landets borgere, firmaer og organisationer. Først et stykke henne i EDB-alderen er man begyndt at tale om databaser. Ordet benyttes ofte uden nærmere præcisering af, hvad man mener med database. I nogle tilfælde menes blot et kartotek uden nævneværdig struktur, i andre tilfælde menes en samling tabeller, der er knyttet sammen i en avanceret struktur. 20. Kartotek eller database Man skal skelne mellem et kartotek og en database. Et kartotek er en enkelt tabel med oplysninger, som i et gammeldags kartotek med kort. Fordelen er, at det er simpelt i struktur og let at opbygge, Ulempen er, at det er en ufleksibel struktur, og som vi skal se, kan det være nødvendigt at indtaste nogle oplysninger flere gange. I en database eller rettere en relationel database spredes oplysningerne i flere tabeller, som kobles sammen. Det er en mere kompliceret struktur; men den er fleksibel, og hvis den er korrekt opbygget, vil en oplysning normalt kun stå én gang. Eksempler på kendte databasesystemer er Paradox, dbase, Access, Approach. De fleste af disse fås integreret med kontorpakker. Desuden findes nogle meget store systemer som Oracle og Sybase. Kartotekssystemer findes blandt andet i de mindre kontorpakker som Microsoft Works og Claris Works. Disse kartotekssystemer benævnes af firmaerne bag dem som databaser, selvom der ikke er tale om relationelle databaser. Men database lyder jo også bedre og mere avanceret end kartotekssystem. 21. Database En database består som sagt af tabeller, der kobles sammen. Vi kan se på et konkret eksempel hvor vi skal registrere fornavn, efternavn, postnummer og by for en række personer. Vi registrerer disse oplysninger i to tabeller 1
Personer Byer Fornavn Efternavn Postnr Postnr By Jens Hansen 4173 4173 Fjenneslev Peter Hansen 5000 6973 Ørnhøj Mona Nielsen 6973 5000 Odense C Anni Petersen 5000 7660 Bækmarksbro Hans Aagaard 7660 Man benytter følgende begreber i forbindelse med tabeller. Attribut er navnet, der er knyttet til en søjle i tabellen. Tabellen Personer har således attributterne Fornavn, Efternavn og Postnr, og tabellen byer har attributterne Postnr og By. En række i en tabel benævnes en post. En post består af felter. F.eks. er Mona Nielsen 5000 en post i tabellen byer; denne post har felt-værdierne Mona, Nielsen og 5000. I nogle tabeller gælder, at alle felter i en søjle skal have forskellige værdier. Hvis dét er krævet, siges feltet at være et nøglefelt, idet værdien i feltet så entydigt bestemmer posten. F.eks. er Postnr. et nøglefelt i tabellen Byer, idet et postnr kun forekommer én gang; medens tabellen Personer ikke har nogen nøglefelter: i alle felter kan samme værdi forekomme flere gange. Databasesystemer har særlig effektive søgeteknikker for nøglefelter. En tabel kan have flere nøglefelter; det nøglefelt, som man vælger at bruge som den egentlige nøgle, kaldes primærnøglen. I visse tilfælde kan to eller flere felter tilsammen danne en nøgle. Man kunne tro, at Fornavn og Efternavn var sammensatte nøglefelter i tabellen Personer. Det er korrekt i den angivne tabel; men det er ikke sikkert kravet kan opfyldes, det kan jo tænkes, at der på et tidspunkt bliver registeret en person med samme fornavn og efternavn som en person, der allerede er i tabellen. Normalt bør enhver tabel have et nøglefelt. Dvs. vores persontabel bør udvides med et felt, der entydigt bestemmer en person. Det kan f.eks. være personens CPR-nummer. Når en tabel har et nøglefelt, vil posterne være sorteret efter dette nøglefelt; det kan dreje sig om forskellige former for sortering, som vi ikke skal komme nærmere ind på her. (Posterne er ikke sorteret i eksemplet nedenfor). Personer 2
CPR-nr Fornavn Efternavn Postnr 040659450 Jens Hansen 4173 1810434895 Peter Hansen 5000 0907624676 Mona Nielsen 6973 2309744694 Anni Petersen 5000 1303384533 Hans Aagaard 7660 Måske kunne man tænke sig at slå de to tabeller sammen til én. CPRnr Fornavn Efternavn Postnr By 0406594505 Jens Hansen 4173 Fjenneslev 1810434895 Peter Hansen 5000 Odense C 0907624676 Mona Nielsen 6973 Ørnhøj 2309744694 Anni Petersen 5000 Odense 1303384533 Hans Aagaard 7660 Bækmarksbro Ulempen herved ses tydeligt: Det er to steder angivet at det er Odense, der har postnummer 5000. Det kaldes redundans og skal så vidt muligt undgås; altså: den samme oplysning bør ikke stå flere steder i en database. Dels giver det en øget indtastning; men hvad der er værre: det øger risikoen for fejl, idet den samme oplysning måske er forkert nogle steder. Faktisk er det i eksemplet ovenfor uklart, om det er Odense C eller Odense, der har postnummeret 5000. Som nybegynder i database design vil man ofte lave én eller meget få tabeller. Man skal tværtimod tilstræbe det modsatte. Med erfaring lærer man at opbygge databaser på den rigtige måde. Som en hjælp til at sikre korrekt opbygning har man fastlagt nogle normalformer, som bør overholdes ved design af databaser. Disse normalformer er defineret forskelligt i databaselitteraturen; derfor kan du komme ud for andre definitioner, end dem vi anfører her. Vi vil se på de 3 første normalformer, som forklares ved hjælp af eksempler. 3
1. normalform Hvis vi skal lave en database over, hvilke byer en række personer har haft bopæl i, kunne man tænke sig denne opbygning PersonId Fornavn Efternavn Bopæl 1 Jens Hansen Struer 2 Peter Hansen Århus, Brejning, Hårby, Horsens 5 Mona Nielsen Skælskør, Århus,Horsens 8 Anni Petersen Ringsted, København 9 Hans Aagaard Århus, Kolding (for at øge overskueligheden er CPR-nummeret udskriftet med et andet nøglefelt, PersonId) Der er flere problemer med denne opbygning. De viser sig ved feltet bopæl. Dels er der et lagringsproblem: Det er umuligt på forhånd at vide hvor meget plads, der skal afsættes på f.eks. harddisk til at gemme bopæle. Dette problem kunne nok løses; men er bedst at undgå. Dels vil søgning i databasen være vanskelig: skal man finde alle personer, der har boet i Århus, er det ikke blot nødvendigt at gennemsøge alle bopælsfelter; databasesystemet skal også gennemsøge hvert felt for at finde Århus. Endelig har vi det omtalte redundansproblem: Århus står flere steder i tabellen, med risiko for stavefejl nogle steder. En korrekt opbygning er at dele tabellen i tre: én med personnavnene, én med alle bopæle og én med sammenhængen mellem PersonId og bopæl. Personer PersonId Fornavn Efternavn 1 Jens Hansen 2 Peter Hansen 5 Mona Nielsen 8 Anni Petersen 9 Hans Aagaard 4
Byer ById Bynavn 1 Ålborg 2 Århus 3 Brejning 4 Horsens 5 Hårby 6 Kolding 7 København 8 Ringsted 9 Skelskør 10 Struer Bopæl PersonId Bopæl 1 10 2 2 2 3 2 5 2 4 3 9 3 2 3 4 4 8 4 7 5 2 5 6 5
Umiddelbart ser denne struktur kompliceret ud. Men den løser alle de problemer, der blev nævnt i starten. F.eks. vil en søgning efter alle personer, der har boet i Århus foregå således: 1. Find ById for Århus i tabellen Byer (ét opslag), dvs. ById=2. 2. Gennemsøg tabellen Bopæl og find alle poster, hvor ById=2, det giver f.eks. PersonId=2. 3. Lav nu ét opslag i tabellen Navne for at finde navnet på personen. 4. Find næste post i tabellen Bopæl, hvor ById=2, det er posten med PersonId=5. 5. Slå op i navne. osv. Skal man blot finde ud af, hvor mange personer i databasen, der har boet i Århus; er det meget let: find antal forekomster af ById=2 i tabellen bopæl. Grundene til, at disse søgninger er bedre, er dels, at man nu kun skal søge i tabeller efter poster, hvor et felt er lig med søgeværdien; det er langt mere effektivt end at søge efter poster med felter, hvor søgeværdien er en del af feltværdien, og dels, at nogle søgninger sker på nøglefelter, hvor søgningen er hurtig. Den vigtigste fordel er dog sikkerheden mod fejl: hvis Århus er stavet forkert, vil man aldrig finde personer, der har haft bopæl i Århus. En sådan fejl vil straks blive opdaget. I den første flade struktur med én tabel, vil en stavefejl i Århus, kun vanskeligt blive opdaget; det er jo blot en enkelt person, man ikke finder. Første normalform: Hvis en tabel til samme primærnøgle indeholder et felt med flere værdier, skal disse gentagne feltværdier lægges over i en ny tabel med en kopi af primærnøglen. Kommentar til eksemplet: I den oprindelige tabel var det i strid med 1. normalform, at feltet Bopæl indeholdt flere værdier. I posten med PersonId=8 (Anni Petersen) indeholdt feltet to værdier: Ringsted og København. Vi sørgede for, at 1. normalform blev overholdt, ved at udskille PersonId og By i særlig tabel. Dvs. Ringsted og København er lagt over i denne tabel med en kopi af primærnøglen, PersonId. Yderligere har vi lagt bynavne i en tabel for sig selv for at undgå redundans i bynavnet. Det sidste var ikke nødvendigt for at overholde 1. normalform. Om man laver en særlig bytabel, kan også i nogen grad afhænge af hvor mange sammenfald, der er. Hvis det er sjældent, at en by forekommer flere gange, vil man næppe skille bynavnene ud i en særlig tabel. Det angivne eksempel er naturligvis for lille til at afgøre, hvorvidt en særskilt tabel med bynavne er relevant. 6
2. normalform Vi ser på et nyt eksempel fra en møbelfabrik, der har nogle modeller af skabe, bl.a. E2 og E3. De laves begge i fineret spånplade, massiv fyr eller massiv birk. For disse modeller registreres i en tabel antal colli (antal pakker), vægt og pris: Model Træsort Antal colli Vægt Pris E2 fin.spån. 3 47 2050 E2 fyr 3 38 2785 E2 birk 3 42 3225 E3 fin.spån. 4 77 3280 E3 fyr 4 61 4100 E3 birk 4 68 4570 Denne tabel har en sammensat nøgle, nemlig felterne model og træsort: Begge felter skal være kendt for at en post er udpeget. Feltet Antal colli afhænger kun af den første nøgle nemlig model. Medens felterne vægt og pris afhænger af hele den sammensatte nøgle. Anden normalform: Hvis en tabel har en sammensat nøgle skal alle felter, der ikke indgår i nøglen, afhænge af den samlede nøgle. Kravet i anden normalform er ikke opfyldt for feltet Antal colli, derfor skal dette felt udskilles i særskilt tabel med felterne Model og Antal colli: Model Antal colli E2 3 E3 4 7
Model Træsort Vægt Pris E2 fin.spån. 47 2050 E2 fyr 38 2785 E2 birk 42 3225 E3 fin.spån. 77 3280 E3 fyr 61 4100 E3 birk 68 4570 I begge tabeller afhænger alle ikke-nøglefelter nu af den samlede nøgle. 3. normalform Vi ser på et register i en kommune: Beboer Stilling Adresse Antal personer i husstanden Antal rum 1810434895 Revisor Algade 10 2 5 1303384533 Lærer Nørrealle 30 4 6 Nøglefeltet er beboer (en persons CPR-nummer). Felterne Stilling, Adresse og Antal personer i husstanden afhænger af nøglen, medens feltet Antal rum ikke afhænger af nøglen; men af adressen. Tredje normalform: I en tabel må et felt, der ikke er nøglefelt, ikke afhænge af et andet ikkenøglefelt. Det er som anført netop tilfældet i tabellen ovenfor. Derfor skal disse to ikke-nøglefelter med afhængighed skilles ud i en særlig tabel: Beboer Stilling Adresse Antal personer i husstanden 1810434895 Revisor Algade 10 2 1303384533 Lærer Nørrealle 30 4 8
Adresse Antal rum Algade 10 5 Nørrealle 30 6 Sammenfatning af normalformerne Man kan sammenfatte disse tre normalformer i følgende: I en tabel skal et felt, der ikke er nøglefelt, afhænge af tabellens samlede nøgle og ikke af andet end nøglen. Der findes en humoristisk formulering heraf, som er en analogi til den fra TV-serier kendte edsaflæggelse i amerikanske retssager: I swear to say the truth, the whole truth and nothing but the truth. So help me God. Analogien lyder: I Swear to use the key, the whole key and nothing but the key. So help me Codd. (Codd er ophavsmand til normalformerne). Man kan opfatte det som det krav, et ikke-nøglefelt skal opfylde. 22. Elevdatabasen set i relation til databaseteorien I dette afsnit vil elevdatabasen blive gennemgået i relation til databaseteorien, dvs. vi skal se på, om konstruktionen af den er i overensstemmelse med de tre normalformer. Desuden vil opbygningen blive kritiseret mht. en realistisk brug af den elever.db *ElevID Efternavn Fornavn Gade Postnummer By CPR-nummer Klasse hold.db *HoldID Fag LærerID elevhold.db *HoldElevID HoldID ElevID Karakter Forsømmelse laerer.db *LærerID Efternavn Fornavn fag.db *Fag 9
Ser vi først på tabellen elever.db, er det iøjnefaldende, at der er redundans, idet tredje normalform ikke er opfyldt. By afhænger af postnummer, men ikke af ElevID, så der burde have været en ekstra tabel: by.db Postnummer By og så burde By være udeladt i elever.db. Desuden kan man diskutere nødvendigheden i at have både ElevID og CPR-nummer, idet begge er entydige identifikatorer for eleven i modsætning til elevens navn. Men ellers er tabellen i orden. Tabellen hold.db er der ikke meget at sige til. Idet læreren er anført med LærerID, er der sørget for at hele lærerens navn ikke gentages. Hold.db er konstrueret for, at oplysningerne i den ikke skulle gentages i elevhold.db, idet der så for hver eneste elev på et hold skulle anføres Fag og LærerID i elevhold.db. Elevhold.db er konstrueret for at sammenknytte tabellerne elever.db og hold.db. Herved fås den simpleste angivelse af, hvilke hold en elev er på. Hvis disse hold skulle anføres i elever.db, ville vi komme i konflikt med 1. normalform, idet en elev jo er på flere hold. Tilsvarende ville der være flere elever på et hold, hvis man havde valgt, at eleverne var med i hold.db, hvilket også er i konflikt med første normalform. Laerer.db er også i orden. Fag.db er måske lidt mærkelig og kunne undværes, men den kunne dog tænkes udvidet: *FagID Fagnavn Bevilling Her kunne FagID være fagforkortelsen: fy, ma, da, og disse kunne med fordel benyttes i hold.db i stedet for fagets navn. Bevilling og lignende er felter, der har med andre områder af skolens administration at gøre. Bemærk, at det hyppigt blot er første normalform, man skal sørge for at overholde. Ved kritik af databasens opbygning eller det udviklede program bør man primært tænke på brugervenligheden. Brugeren skal bruge programmet både i det daglige samt ved den årlige indtastning af elever og hold. Det sidste er et stort arbejde og er derfor værd at forsøge at lette. 10
Mange steder kunne man have benyttet listebokse til at klikke sig frem til oplysninger i stedet for at skulle skrive dem. Mht. stamklassehold så burde eleverne automatisk knyttes til disse, så snart elevens klasse var angivet. Det ville tilsyneladende kræve, at der kom et ekstra felt i tabellen hold.db: Klasse, men holdnavnene er indrettet, så de for stamklassehold starter med klassenavnet og et mellemrum, så det vil kunne lade sig gøre at skrive en procedure, som kunne udføre dette. I det efterfølgende kapitel gennemgås lidt af, hvordan man ved mere programmering direkte kan arbejde med tabeller og få opfyldt ovenstående. 11