Det här är ett avsnitt i en webbkurs om databaser som finns fritt tillgänglig på adressen http://www.databasteknik.se/webbkursen/. Senaste ändring: 24 juni 2006.

Av Thomas Padron-McCarthy. Copyright, alla rättigheter reserverade, osv. Skicka gärna kommentarer till webbkursen@databasteknik.se.

Integritetsvillkor

Ibland råkar man lägga in felaktiga data i databasen. Till exempel kanske man skriver in att Bengt har 100000 kronor i lön när han egentligen bara har 1000. Eller så står Bengts lön på två ställen, och på ett ställe står det 1000 och på ett annat 100000. Visst vore det bra om databashanteraren kunde hjälpa oss, genom att helt enkelt hindra oss från att lägga in felaktiga data i databasen?

Databashanteraren kan förstås inte lyckas helt och hållet med att hålla databasen fri från felaktiga uppgifter. Den kan inte hoppa ut ur datorn och springa iväg och kolla upp Bengts lön. (Åtminstone inte med dagens teknik. I framtiden kanske städernas gator är fulla av databashanterare som rusar fram och tillbaka och kontrollerar löneuppgifter. Övning: Rita en bild av en gata i framtiden, med databashanterare och svävande bilar!) Men databashanteraren kan hjälpa till lite i alla fall.

Den som skapar och ansvarar för databasen (databasadministratören) kan berätta för databashanteraren vilka regler som alla data i databasen måste uppfylla. Reglerna kallas integritetsvillkor. Om nu en persons lön kan stå på två ställen, så kanske vi har integritetsvillkoret att båda de uppgifterna måste vara lika. Eller så har vi helt enkelt en regel som säger att alla löner måste ligga mellan 0 och 40000. (ABB-direktörer och chefsöverläkare får inte vara med i databasen.)

Faktaruta om ordet "integritet":

Om man tittar på en science fiction-film, och någon säger till kaptenen på rymdskeppet att "the integrity of the hull has been broken", så vet man att det är allvarligt. Då har det gått hål i skrovet, och rymdskeppet håller inte tätt längre. Luften pyser ut och rymdmonstren smiter in. Snart går hela rymdskeppet i bitar. Det engelska ordet "integrity" betyder ungefär att saker hänger ihop på rätt sätt.

Det svenska ordet "integritet" används ofta i en lite annorlunda betydelse, som när man pratar om "personlig integritet". Mer om det nedan. Men tills vidare menar vi med "integritet" samma sak som det engelska "integrity": att data i databasen hänger ihop på rätt sätt.

Integritetsvillkor ("integrity constraints" på engelska) är alltså villkor som begränsar vilka data som kan lagras i databasen. Åtminstone en del av integritetsvillkoren är egentligen också begränsningar som gäller i den riktiga världen, och inte bara i databasen, för databasen beskriver en del av världen. Om man har ett integritetsvillkor som säger att alla löner måste ligga mellan 0 och 40000, så säger man ju också att ingen människa, av de som ska vara med i databasen, har en lön som ligger utanför det intervallet.

Faktaruta om ordet "constraint":

Det engelska ordet "constrain" betyder "hindra" eller "begränsa", och "constraint" betyder "begränsning". En "integrity constraint" är just en begränsning på vad man tillåter för data i databasen.

Här är två vanliga typer av integritetsvillkor i relationsdatabaser:

  1. Nyckelvillkor, nämligen att primärnyckeln (och eventuella andra kandidatnycklar) måste ha unika värden, dvs att det inte får finnas två rader i en tabell som har samma värde på primärnyckeln.
  2. Referensintegritetsvillkor, nämligen att om det står i tabellen Anställd att en viss anställd jobbar på avdelning nummer 17, så ska det också finnas en avdelning med nummer 17, i tabellen Avdelning.

Ett exempel: Anställda och avdelningar

Tabellen Anställd innehåller data om anställda, och tabellen Avdelning innehåller data om avdelningar. JobbarPå är ett referensattribut till Nummer i Avdelning. Som det ser ut nu jobbar Svea på avdelningen Data, Sten jobbar på avdelningen Ekonomi, och Bengt jobbar ingenstans.

Anställd
Nummer Namn JobbarPå
1 Svea 1
2 Sten 3
3 Bengt  
             Avdelning
Nummer Namn
1 Data
2 Städning
3 Ekonomi

Även om man inte kan så mycket om databaser så verkar det väl rimligt att:

De fyra första villkoren kallas nyckelvillkor, och det sista kallas referensintegritetsvillkor.

Nyckelvillkor i SQL

Så här anger vi ett nyckelvillkor när vi skapar en tabell med SQL:
create table Avdelning
(Nummer integer not null,
Namn varchar(10),
primary key (Nummer));
(Man måste ange not null för kolumnen Nummer, för att den ska kunna deklareras som primärnyckel, eftersom en nyckel aldrig får innehålla null-värden.)

Nu har vi alltså skapat tabellen Avdelning, och talat om för databashanteraren att kolumnen Nummer är primärnyckel. Databashanteraren kommer nu att se till att varje avdelning har ett unikt nummer.

Vi stoppar in data i databasen:

insert into Avdelning values (1, 'Data');
insert into Avdelning values (2, 'Städning');
insert into Avdelning values (3, 'Ekonomi');
insert into Avdelning (Namn) values ('Lager'); /* Ger fel */
insert into Avdelning values (3, 'Lager');     /* Ger fel */
update Avdelning set Nummer = 2
    where Namn = 'Data';                       /* Ger fel */
De tre sista kommandona kommer att misslyckas, eftersom databashanteraren hindrar oss:

Referensintegritet i SQL

Så här anger vi ett referensintegritetsvillkor när vi skapar en tabell med SQL:
create table Anställd
(Nummer integer not null,
Namn varchar(10),
JobbarPå integer,
primary key (Nummer),
foreign key (JobbarPå) references Avdelning(Nummer));
Om tabellen Anställd redan finns, så kan man skriva så här för att lägga till ett referensintegritetsvillkor:
alter table Anställd
add foreign key (JobbarPå) references Avdelning(Nummer);
Eller, om man vill ge referensintegritetsvillkoret ett eget namn ("Anställd_till_avdelning"), så att man kan ta bort det sen om man skulle behöva:
alter table Anställd add constraint Anställd_till_avdelning
add foreign key (JobbarPå) references Avdelning(Nummer);
Nu har vi alltså skapat tabellen Anställd, och talat om för databashanteraren att kolumnen JobbarPå är ett referensattribut som refererar till tabellen Avdelning. Databashanteraren kommer nu att se till att varje anställd jobbar på en avdelning som faktiskt finns i avdelningstabellen. (Undantag: Vi sa aldrig not null för kolumnen JobbarPå, så man kan också lämna tomt i rutan, om en anställd inte jobbar på någon avdelning.)

Ett referensattribut refererar alltid till primärnyckeln i en annan tabell (eller, ibland, samma tabell). Den har alltså samma domän som nyckeln i en annan tabell. Därför kallas ett referensattribut ibland för främmande nyckel (engelska: foreign key).

Vi stoppar in data i databasen:

insert into Anställd values (1, 'Svea', 1);
insert into Anställd values (2, 'Sten', 3);
insert into Anställd (Nummer, Namn) values (3, 'Bengt');
insert into Anställd values (4, 'Sergio', 5); /* Ger fel */
update Anställd set Avdelning = 7
    where Namn = 'Svea';                      /* Ger fel */
delete from Avdelning where Namn = 'Data';    /* Ger fel */
update Avdelning set Number = 9
    where Namn = 'Data';                      /* Ger fel */
De fyra sista kommandona kommer att misslyckas, eftersom databashanteraren hindrar oss: Som du ser finns det flera olika sorters ändringar i databasen som kan göra att referensintegriteten bryts: insättningar, borttagningar och uppdateringar av rader. Vilken tur att databashanteraren kontrollerar allihop!

Vadå "misslyckas"?

Vi sa ovan att kommandona "misslyckas" när databashanteraren upptäcker att ett referensintegritetsvillkor är brutet. Men vad innebär det att kommandot "misslyckas"?

Det brukar betyda att operationen avbryts, och att ingen ändring görs i databasen. Beroende på vad man använder för databashanterare och vad det är för sorts integritetsvillkor så sker det antingen direkt när man försökte göra ändringen, eller när hela transaktionen försöker göra commit.

Men man kan också tala om för databashanteraren att den inte ska avbryta operationen, utan "laga" databasen på lämpligt sätt. Titta på referensintegritetsvillkoret från exemplet ovan:

foreign key (JobbarPå) references Avdelning(Nummer)
I stället kan man skriva på följande olika sätt:
  1. foreign key (JobbarPå) references Avdelning(Nummer) on delete set null

    Det här betyder att om jag tar bort en avdelning som det finns anställda som jobbar på, så sätts JobbarPå för alla dessa anställda till null.

  2. foreign key (JobbarPå) references Avdelning(Nummer) on delete cascade

    Om jag tar bort en avdelning som det finns anställda som jobbar på, så tas även dessa anställda bort.

  3. foreign key (JobbarPå) references Avdelning(Nummer) on delete set default

    Om jag tar bort en avdelning som det finns anställda som jobbar på, så sätts JobbarPå för alla dessa anställda till den kolumnens defaultvärde.

  4. foreign key (JobbarPå) references Avdelning(Nummer) on update cascade

    Om jag ändrar numret på en avdelning som det finns anställda som jobbar på, så sätts JobbarPå för alla dessa anställda avdelningens nya nummer.

Alla fyra varianterna gör alltså att databashanteraren upprätthåller referensintegriteten genom att ta bort, eller ändra i, de refererande raderna, alltså raderna i Anställd-tabellen! Detta trots att ändringarna som databashanteraren reagerar på är såna som görs i tabellen Avdelning. Men referensvillkoret hör ju till tabellen Anställd, så kanske är det egentligen ganska naturligt att ändringarna görs i just den tabellen.

Ajöss med konsulterna!

Som exempel på on delete cascade tar vi tabellen Konsult. Den innehåller data om konsulter som är inhyrda av de olika avdelningarna. Man kan inte ta bort en avdelning hur som helst om det finns anställda som jobbar där, men konsulter är det bara att sparka och ta bort ur databasen:
create table Konsult
(Nummer integer not null,
Namn varchar(10),
InhyrdAv integer,
primary key (Nummer),
foreign key (InhyrdAv) references Avdelning(Nummer)
on delete cascade);

insert into Konsult values (3, 'Sture', 2);
insert into Konsult values (4, 'Sally', 2);
insert into Konsult values (5, 'Sune', 3); 
delete from Avdelning where namn = 'Städning'; /* Kaskad! */
Det sista kommandot tar bort städavdelningen, men också de båda konsulterna Sture och Sally som jobbade där.

Mer komplicerade villkor

Nyckelvillkor och referensintegritetsvillkor är ganska enkla att formulera, och i de flesta relationsdatabashanterare är det ganska enkelt att specificera sådana integritetsvillkor. Men det finns andra integritetsvillkor, som kan vara svårare att specificera.

Ibland talar man om allmänna semantiska integritetsvillkor (engelska: "general semantic integrity constraints"). Det är vilka villkor som helst, som beror på hur det ser ut i den verklighet som databasen ska modellera. "Semantik" har ju med "betydelse" att göra, och de här villkoren bestäms av vad databasens data betyder. Eftersom den riktiga världen kan vara hur komplicerad som helst, så kan också de här integritetsvillkoren vara hur komplicerade som helst.

Exempel på allmänna semantiska integritetsvillkor:

Det första av de två villkoren är exempel på ett statiskt villkor (engelska; "state constraint", dvs "tillståndsvillkor"). Det kan man kontrollera genom att titta på databasens innehåll.

Det andra villkoret, om att löner bara kan höjas, är ett dynamiskt villkor (engelska: "transition constraint", dvs "ändringsvillkor"). Det spelar bara in vid ändringar i databasen, och för att kontrollera det måste man jämföra innehållet i databasen före och efter ändringen.

Faktaruta om orden "statiskt" och "dynamiskt":

I vardagsspråket brukar "statiskt" betyda något som står still och är tråkigt, medan "dynamiskt" är något som rör på sig med stor fart. Här använder vi orden i deras mer tekniska betydelser:

  • Om något är statiskt behöver man inte bry sig om att titta på hur det ändras med tiden. Det kan bero på att det inte ändras, eller på att man tittar på det vid en viss, bestämd tidpunkt. Det har, så att säga, ingen tidsdimension alls.
  • Att något är dynamiskt betyder att man måste ta hänsyn till hur det ändras med tiden. Det har en tidsdimension.

Hur anger man dessa mer komplicerade villkor?

I databashanterare som följer SQL2-standarden kan man ange assertions för att specificera en del allmänna semantiska integritetsvillkor. När man gjort det, kommer databashanteraren sen att kontrollera villkoret automatiskt, på samma sätt som med nyckelvillkoren och referensintegriteten som vi beskrev ovan.

Faktaruta om ordet "assertion":

Det engelska ordet "assertion" betyder ungefär samma sak som svenskans "påstående", fast lite starkare: så här är det minsann, och hör sen!

Vi tänker oss att vi använder samma exempeldatabas som i avsnittet om SQL. Villkoret att ingen får tjäna mer än sin närmaste chef kan skrivas så här:

create assertion checksalary
check (not exists (select *
                   from Anställd as proletär, Anställd as boss
                   where proletär.Chef = boss.Nummer
                   and proletär.Lön > boss.Lön))
Det är alltså ett villkor, uttryckt i SQL, som databashanteraren nu har i uppgift att kontrollera, och hela tiden se till att det är sant. Om någon transaktion försöker ändra i databasen så att någon får högre lön än sin chef, så kommer den transaktionen att avbrytas.

Dynamiska villkor, som begränsar vilka ändringar som får göras i databasen, kan normalt inte uttryckas med en assertion. Det beror på att man måste titta på tillståndet i databasen både före och efter ändringen för att kunna kontrollera villkoret.

Aktiva databaser

Ett integritetsvillkor hanteras ju av databashanteraren genom att villkoret kontrolleras, och om det inte är uppfyllt så sker någon typ av åtgärd.

Därifrån är inte steget långt till att införa en liknande mekanism, inte bara för integritetsvillkor, utan för vilka villkor som helst. Man skriver en regel som anger ett villkor och en åtgärd, och när det villkoret är uppfyllt, så ska databashanteraren utföra åtgärden. Då har vi det som kallas en aktiv databas.

Om vi använder en aktiv databas som låter oss ange ECA-regler, kan vi skriva en ECA-regel om villkoret att löner bara kan höjas:

create exception bad_salary "Kan inte sänka lönen!";
create trigger salarycheck for Anställd after update as begin
    if (new.Lön < old.Lön) then
        exception bad_salary;
end;
(Skrivsättet är något förenklat jämfört med den databashanterare som exemplet är hämtat från. Exakt hur man skriver varierar mellan olika databashanterare.)

Om någon transaktion försöker ändra kolumnen Lön i tabellen Anställd så att det nya värdet blir lägre än det gamla, så kommer regeln att utlösas, och den transaktionen kommer att avbrytas.

I en riktig aktiv databashanterare kan man göra mer än bara avbryta transaktionen. Ofta finns så kallade lagrade procedurer, som är små (eller stora) programsnuttar som man kan lagra i databasen, och som kan köras när villkorsdelen av en regel är uppfylld. Reglerna kan därför användas till mycket annat än att bara kontrollera integritetsvillkor. Till exempel kan en aktiv databashanterare automatiskt beställa mer varor när lagret i en butik börjar bli tomt.

Vem kollar villkoren? Procedurellt eller deklarativt?

Det finns två olika sätt att ange integritetsvillkoren i en databas: procedurellt och deklarativt.

Hittills i det här avsnittet har vi bara sett den deklarativa metoden: att man med olika former av regler talar om för databashanteraren vad som ska gälla, och sen är det databashanterarens uppgift att se till att inga transaktioner kan ändra data så att integritetsvillkoren bryts.

Alternativet är att ange integritetsvillkoren procedurellt, dvs med vanlig programkod som kontrollerar att de är uppfyllda. Det kan vara i ett programmeringsspråk som Java eller C i ett applikationsprogram, eller kanske inuti en lagrad procedur i databasen. Programmeraren måste skriva programkod som kontrollerar varje ändring som ska göras, så att den uppfyller alla integritetsvillkor.

Helst vill man att databashanteraren ska sköta om kontrollen, automatiskt och utan att användare eller applikationsprogrammerare behöver bry sig. Det har flera fördelar:

Ibland måste man förstås göra kontrollen procedurellt, till exempel eftersom villkoren är för komplicerade för att uttrycka i enkla regler, eller för att man vill åtgärda brott mot reglerna på ett mer avancerat sätt än vad databashanteraren klara av, eller helt enkelt för att just den databashanterare man använder inte har stöd för alla integritetsvillkor man behöver.

En möjlig uppdelning i olika typer av integritetsvillkor

Om man vill kan man dela upp integritetsvillkor i tre olika klasser, nämligen inneboende, implicita och explicita:

Databasens interna integritet

Hittills har vi talat om integriteten för uppgifter i databasen, och att de inte får strida mot integritetsvillkoren. Men alla databashanterare har också olika typer av interna datastrukturer. Till exempel finns det index, som inte är synliga för den vanliga användaren, men som används av databashanteraren när den söker efter data i databasen. Indexen, och alla andra interna datastrukturer, måste förstås vara korrekta.

Varje index måste stämma överens med den riktiga tabell som det pekar in i. Om man lägger till eller tar bort rader i tabellen, och indexet av någon anledning inte ändras för att reflektera detta, så kan databashanteraren kanske bli så förvirrad att den kraschar.

Därför måste databashanteraren vara mycket noga med att upprätthålla integriteten på de interna datastrukturerna. Annars kanske man inte längre kan komma åt några data alls i databasen.

"Integritet" som i "personlig integritet"

I det här avsnittet har vi pratat om "integritet" i betydelsen "dataintegritet", som ungefär innebär "utan inre motsägelser" eller "stämmer med integritetsvillkoren för den här databasen". Men som vi redan nämnt så används det svenska ordet "integritet" ofta i en lite annorlunda betydelse, som när man pratar om "personlig integritet". Personlig integritet heter "privacy" på engelska, och det betyder ungefär att uppgifter om mig, till exempel min adress och min lön, och min religion och mitt brottsregister, inte ska vara tillgängliga för vem som helst hur som helst.

Kom ihåg:

Vi tar inte upp så mycket om personlig integritet här, men vi ska i alla fall nämna de svenska personnumren, som ibland ger upphov till både debatt och förvirring. Är personnummer hemliga? Är författaren dum när han nu talar om att hans personnummer är 631211-1658?

Nej, svenska personnummer är inte hemliga. De är inte alls hemliga. Om du vet namn och adress på en person, så att det går att avgöra vem det är, så kan du ringa till skattemyndigheten och få den personens personnummer. Du behöver inte tala om vem du är eller vad du ska ha personnumret till.

Eventuella problem och risker med personnummer handlar alltså inte om att personnummer är hemliga, utan om att de är unika. De fungerar som en nyckel eller ett unikt namn, inte som ett lösenord.

Om det finns en fara med personnummer så är det att det blir lätt att se att den där 631211-1658 som skriver databaskurser är samma person som den där 631211-1658 som har årskort på Klubb Läderhamster. Och det kanske jag inte vill att alla ska veta. Därför vill jag kanske inte att Klubb Läderhamster ska använda mitt personnummer som medlemsnummer, och trycka det på medlemskortet.

De viktigaste begreppen

De viktigaste begreppen från det här avsnittet finns också med i ordlistan:

integritet, dataintegritet, personlig integritet (privacy), integritetsvillkor, nyckelvillkor, referensintegritet, aktiv databas

Litteratur


Webbkursen om databaser av Thomas Padron-McCarthy.