Databasernas grunder

vår 2026

6. Databasdesign

Principerna för god design

Vid design av en databas behöver man fastställa dess struktur: vilka tabeller ska databasen innehålla och vilka kolumner ska varje tabell ska ha. Det finns många möjliga sätt att göra detta på, och med hjälp av några centrala principer kommer man långt.

Vid databasdesign vill man att den resulterande databasen ska vara enkel och smidig att använda med SQL. Databasens struktur bör utformas så att information enkelt kan hämtas och ändras med SQL-kommandon.

Principerna för databasdesign är användbara och hjälper oss att få fungerande lösningar. Det är dock viktigt att reflektera över vad som ligger bakom principerna och när det kan vara motiverat att göra på något annat sätt. Det är viktigare att designa en databas så att den lämpar sig för sitt användningsområde än att följa principerna bara för principernas skull.

Tabell vs. klass

Definitionen av en databastabell beskriver vilken typ av information som kan lagras i tabellen. Till exempel innehåller varje rad i tabellen Movies nedan filmens namn och utgivningsår:

CREATE TABLE Movies (
  id INTEGER PRIMARY KEY,
  name TEXT,
  release_year INTEGER
);

INSERT INTO Movies (name, release_year) VALUES ('Snövit', 1937);
INSERT INTO Movies (name, release_year) VALUES ('Fantasia' 1940);
INSERT INTO Movies (name, release_year) VALUES ('Pinocchio', 1940);

I många programmeringsspråk beskriver definitionen av en klass vilken typ av information objekten innehåller. Till exempel definierar följande Pythonkod klassen Movie, som innehåller filmens namn och utgivningsår. Därefter lägger koden till objekt i en lista.

@dataclass
class Movie:
    name: str
    release_year: int

movies = []
movies.append(Movie("Snövit", 1937))
movies.append(Movie("Fantasia", 1940))
movies.append(Movie("Pinocchio", 1940))

Definitionen av en databastabell liknar alltså en klass i programmering, och en enskild rad i tabellen motsvarar ett objekt som skapats från klassen.

En eller flera tabeller?

I programmering baseras alla objekt av samma typ på samma klass. På samma sätt finns alla rader av samma typ i en tabell i databasen. Således kan vi hantera raderna smidigt med SQL-kommandon.

Om databasen exempelvis innehåller filmer är ett bra tillvägagångssätt att lagra alla filmer i en och samma tabell, Movies:

id  name       release_year
--  ---------  ------------
1   Snövit     1937        
2   Fantasia   1940        
3   Pinocchio  1940        
4   Dumbo      1941        
5   Bambi      1942        

Från denna tabell kan vi till exempel hämta filmer från år 1940 på följande sätt:

SELECT name FROM Movies WHERE release_year = 1940;

Vad händer om vi istället skulle dela upp filmerna i flera tabeller, till exempel efter utgivningsår? Då skulle tabellen Movies1940 innehålla filmer från 1940. Vi skulle kunna hämta filmerna på följande sätt:

SELECT name FROM Movies1940;

Denna lösning fungerar så länge vi bara vill hämta filmer från ett visst utgivningsår. Databasen blir dock snabbt knepig att använda om vi vill göra andra typer av sökningar. Om vi exempelvis vill hämta alla filmer från åren 1940–1950 behöver vi flera olika frågor:

SELECT name FROM Movies1940;
SELECT name FROM Movies1941;
SELECT name FROM Movies1942;
...
SELECT name FROM Movies1950;

När filmerna är i en och samma tabell räcker en fråga:

SELECT name FROM Movies WHERE release_year BETWEEN 1940 AND 1950;

När filmerna finns i en och samma tabell kan vi alltså hantera dem mångsidigt med enskilda SQL-kommandon, vilket inte skulle vara möjligt om flmerna fanns i flera olika tabeller.

Referera

En-till-många-relation

Vi betraktar en databas som innehåller tabeller med kurser och lärare. Mellan tabellerna finns en en-till-många-relation: varje kurs har en lärare, medan en och samma lärare kan undervisa flera kurser. Vi kan skapa databasens tabeller på följande sätt:

CREATE TABLE Teachers (
  id INTEGER PRIMARY KEY,
  name TEXT
);

CREATE TABLE Courses (
  id INTEGER PRIMARY KEY,
  name TEXT,
  teacher_id INTEGER REFERENCES Teachers
);

I tabellen Courses refererar kolumnen teacher_id till tabellen Teachers, det vill säga den innehåller ett ID-nummer för en viss lärare. Referensen anges med REFERENCES-attributet, som anger att heltalet i kolumnen refererar till tabellen Teachers.

Vi kan till exempel lägga in följande data i tabellerna:

INSERT INTO Teachers (name) VALUES ('Kaila');
INSERT INTO Teachers (name) VALUES ('Kivinen');
INSERT INTO Teachers (name) VALUES ('Laaksonen');

INSERT INTO Courses (name, teacher_id) VALUES ('Datanätverk', 1);
INSERT INTO Courses (name, teacher_id) VALUES ('Seminarium', 1);
INSERT INTO Courses (name, teacher_id) VALUES ('PHP-programmering', 3);
INSERT INTO Courses (name, teacher_id) VALUES ('Neuronnät', 2);

Många-till-många-relation

Låt oss betrakta en situation där flera lärare kan undervisa en kurs tillsammans. Det är då fråga om en många-till-många-relation, eftersom en kurs kan ha flera lärare och en lärare kan undervisa flera kurser.

Nu kan en rad i tabellen Teachers vara kopplad till flera rader i tabellen Courses, och på motsvarande sätt kan en rad i tabellen Courses vara kopplad till flera rader i tabellen Teachers. Eftersom en rad i databasen inte kan innehålla en lista med referenser kan vi inte lägga till referenser direkt i någon av tabellerna, utan vi måste skapa en ny tabell för referenserna:

CREATE TABLE Teachers (
  id INTEGER PRIMARY KEY,
  name TEXT
);

CREATE TABLE Courses (
  id INTEGER PRIMARY KEY,
  name TEXT
);

CREATE TABLE CourseTeachers (
  course_id INTEGER REFERENCES Courses,
  teacher_id INTEGER REFERENCES Teachers
);

Skillnaden från tidigare är att tabellen Courses inte längre innehåller någon referens till tabellen Teachers. Istället finns nu en ny tabell, CourseTeachers, som refererar till båda tabellerna. Varje rad i denna tabell beskriver en relation av typen ”kursen id undervisas av lärare id”.

Vi kan till exempel på följande sätt beskriva att en kurs har två lärare:

INSERT INTO Teachers (name) VALUES ('Laaksonen');
INSERT INTO Teachers (name) VALUES ('Luukkainen');

INSERT INTO Courses (name) VALUES ('PHP-programmering');
INSERT INTO Courses (name) VALUES ('Neuronnät');

INSERT INTO CourseTeachers VALUES (1, 1);
INSERT INTO CourseTeachers VALUES (1, 2);
INSERT INTO CourseTeachers VALUES (2, 1);

Detta innebär att lärarna Laaksonen och Luukkainen undervisar kursen PHP-programmering. Därtill undervisar Laaksonen kursen Neuronnät.

Observera att denna lösning kunde användas även i det tidigare fallet där en kurs alltid har exakt en lärare. I så fall skulle dock databasen i princip innehålla en onödig tabell.

Atomär data

Princip: Varje kolumn i en databastabell ska innehålla en enda, alltså atomär (odelbar), uppgift, såsom ett tal eller en sträng. En kolumn får inte innehålla en lista med flera värden.

Denna princip underlättar hanteringen av databasen med SQL-kommandon. När varje uppgift finns i sin egen kolumn går det enkelt att hänvisa till datan.

En lista kan lagras i databasen genom att skapa en tabell där varje rad motsvarar ett enskilt element i listan, som i det tidigare exemplet med tabellen CourseTeachers. Varför skulle vi dock inte bara kunna lagra listan i en enda kolumn? Följande exempel förtydligar detta.

Exempel

Steg 1

Vi vill spara studerandes tentresultat i databasen. Tenten består av fyra uppgifter och man kan få 0–6 poäng per uppgift. Vi kunde försöka lagra tentpoängen på följande sätt:

student_id  points 
----------  -------
1           6,5,1,4
2           3,6,6,6
3           6,4,0,6

Idén är att kolumnen points innehåller en sträng med en lista av poäng separerade med kommatecken. Denna lösning bryter mot principen ovan, där varje kolumn ska innehålla en enda uppgift. Vad är problemet med denna lösning?

Problemet med lösningen är att det blir besvärligt att komma åt poängen med SQL-kommandon eftersom de ligger inuti en sträng. Om vi till exempel vill beräkna varje studerandes totala poäng behöver vi en fråga som ser ut ungefär på följande sätt:

SELECT student_id,
       SUBSTR(points, 1, 1) + SUBSTR(points, 3, 1) +
       SUBSTR(points, 5, 1) + SUBSTR(points, 7, 1) AS total_points
FROM Results;

Här används funktionen SUBSTR för att extrahera en delsträng från huvudsträngen. Frågan är dock krånglig och fungerar endast när det finns exakt fyra poäng och de är ensiffriga. Vi behöver ett bättre sätt för att lagra poängen.

Steg 2

I följande tabell finns fyra kolumner för poängen, vilket gör att vi kan hantera dem en i taget:

student_id  points1  points2  points3  points4
----------  -------  -------  -------  -------
1           6        5        1        4
2           3        6        6        6
3           6        4        0        6

Vi kan nu ställa frågan på ett smidigare sätt:

SELECT student_id,
       points1 + points2 + points3 + points4 AS total_points
FROM Results;

Denna lösning är klart bättre, men det finns fortfarande problem. Även om poängen finns i olika kolumner förutsätts det fortfarande att tenten består av exakt fyra uppgifter. Om antalet uppgifter i tenten ändras måste vi ändra tabellens struktur och alla SQL-kommandon som har med poängen att göra. Lösningen kan alltså ytterligare förbättras.

Steg 3

När vi vill lagra en lista i databasen är en bra lösning att spara varje element på en egen rad. I det här exemplet kan vi skapa en tabell där varje rad anger en viss studerandes poäng för en viss uppgift:

student_id  task_id  points
----------  -------  ------
1           1        6     
1           2        5     
1           3        1     
1           4        4     
2           1        3     
2           2        6     
2           3        6     
2           4        6     
3           1        6     
3           2        4     
3           3        0     
3           4        6     

Vi kan nu hämta varje studerandes totalpoäng på följande sätt:

SELECT student_id, SUM(points) AS total_points
FROM Results
GROUP BY student_id;

Denna allmänna fråga fungerar oavsett antalet uppgifter. Vi kan använda funktionen SUM för att beräkna summan istället för att behöva lista alla uppgifter manuellt.

Att antalet rader i tabellen ökar avsevärt till följd av förändringen är inget man behöver oroa sig för. Databashanterare är utformade så att de fungerar bra även om en tabell innehåller många rader.

Vad är atomär data?

Begreppet atomär data är inte särskilt väl definierat. Det är tydligt att en lista inte är atomär data, men hur är det till exempel med en sträng som innehåller flera ord?

Låt oss som exempel betrakta en situation där en tabellkolumn innehåller ett användarnamn. Samma kolumn innehåller både för- och efternamn. Är denna design dålig?

id  name          
--  --------------
1   Anna Virtanen 
2   Maija Korhonen
3   Pasi Lahtinen 

Vi kunde också lagra för- och efternamnen skilt på följande sät:

id  first_name  last_name
--  ----------  ---------
1   Anna        Virtanen 
2   Maija       Korhonen 
3   Pasi        Lahtinen 

Vilken tabell som är bättre beror på situationen. Om systemet uttryckligen behöver kunna söka information baserat på för- eller efternamn (till exempel hitta alla användare vars förnamn är Anna), är den senare tabellen bättre. Ofta är det ändå inte så, och det finns inget fel i att spara både för- och efternamn i samma kolumn.

På motsvarande sätt, om man sparar ett meddelande som en användare har skickat till databasen, kan det innehålla många ord. Egentligen är meddelandet alltså en lista av ord. Det är ändå en bra lösning att spara hela meddelandet i en och samma kolumn eftersom meddelandet hanteras som en enda helhet i databasen. En dålig lösning skulle vara att ”atomiskt” dela upp orden i egna kolumner.

Man kan alltså tänka så här: om viss data behöver behandlas separat i SQL-kommandon är den atomär och bör ligga i en egen kolumn. Om man däremot inte refererar till datan i SQL-kommandon, kan den vara en del av en större helhet i samma kolumn.

Redundans

Princip: Varje uppgift finns på exakt ett ställe i databasen. I databasen finns det inte information som kan räknas ut eller härledas utgående från annat innehåll i databasen.

Genom att följa den här principen blir det enkelt att uppdatera innehållet i databasen, eftersom uppdateringen bara behöver göras på ett ställe och inte påverkar andra delar av databasen.

Exempel 1

Vi sparar de meddelanden som användarna skickar till systemet på följande sätt i tabellen Messages:

id  user        message       
--  ----------  --------------
1   Anna123     Var är du?   
2   Julgubben   Fortfarande i bussen
3   Anna123     Tar det länge?
4   Julgubben   5 min         

Lösningen fungerar i stort sett bra, men det är svårt att uppdatera innehållet i databasen om användaren bestämmer sig för att byta namn. Till exempel, om Anna123 vill byta sitt namn, måste ändringen göras i varje meddelande som hon har skickat.

En bättre lösning är att användarens namn finns på endast ett ställe i databasen, exempelvis i tabellen Users, där alla användare finns.

id  name      
--  ----------
1   Anna123   
2   Julgubben

I de andra tabellerna finns endast användarens ID-nummer som referens, vilket är oföränderlig information. Till exempel ser tabellen Messages nu ut så här:

id  user_id  message       
--  -------  --------------
1   1        Var är du?   
2   2        Fortfarande i bussen
3   1        Tar det länge?
4   2        5 min         

Nu är det lätt att ändra användarens namn, eftersom det räcker med att göra ändringen i en rad i tabellen Users. Ändringen uppdateras genast överallt, eftersom de andra tabellerna fortfarande refererar till rätt rad.

Över lag är detta en välfungerande lösning. Frågorna blir dock mer komplicerade eftersom informationen nu måste hämtas från flera olika tabeller.

Fortfarande redundans?

Trots den senaste ändringen så kan det fortfarande förekomma redundant information i databasen. Till exempel i följande situation, där användarna skickar ett likadant meddelande, “Hej!”. Borde databasens struktur förbättras?

id  user_id  message
--  -------  -------
1   1        Hej!   
2   2        Hej!   

I det här fallet vore det inte en bra idé att utforma databasen så att om två användare skickar ett meddelande med samma innehåll, sparas innehållet bara på ett ställe.

Även om meddelandena har samma innehåll, så är de separata meddelanden som inte är avsedda att referera till samma sak. Om användare 1 ändrar innehållet i sitt meddelande ska ändringen inte påverka meddelandet från användare 2, även om detta meddelande för tillfället har samma innehåll.

Exempel 2

Vi sparar information om studerandenas prestationer i databasen. Ur databasen kan man ta reda på hur många studiepoäng en studerande har avklarat.

I följande databas har information sparats om hur många studiepoäng varje studerande har avklarat. Tabellen Students innehåller följande:

id  name    total_credits
--  ------  -------------
1   Maija   20           
2   Uolevi  10           

Tabellen Completions innehåller däremot följande rader:

id  student_id  course_id  credits
--  ----------  ---------  -------
1   1           1          5      
2   1           2          5      
3   1           4          10     
4   2           1          5      
5   2           3          5      

Vi kan enkelt hämta den totala summan av en studerandes studiepoäng på följande sätt:

SELECT total_credits FROM Students WHERE name = 'Maija';

I databasen finns dock redundant information: innehållet i kolumnen total_credits i tabellen Students kan beräknas med hjälp av tabellen Completions. Till exempel Maijas totala antal studiepoäng, som i tabellen Students är 20, kan också beräknas som summan 5 + 5 + 10 från tabellen Completions.

Ett problem är nu att för varje prestation som registreras måste en ny rad läggas till i tabellen Completions och det totala antalet studiepoäng i tabellen Students måste uppdateras. Om uppdateringen glöms bort eller misslyckas uppstår motsägelsefull information i databasen.

Vi kan bli av med den redundanta informationen genom att ta bort kolumnen total_credits från tabellen Students:

id  name  
--  ------
1   Maija 
2   Uolevi

Denna ändring gör det dock svårare att ta reda på en studerandes totala antal studiepoäng, eftersom informationen nu måste beräknas utifrån prestationerna:

SELECT SUM(Completions.credits) AS total_credits
FROM Completions, Students
WHERE Completions.student_id = Students.id AND Students.name = 'Maija';

Över lag är detta ändå en bra lösning, eftersom vi nu kan ändra prestationer i tabellen Completions och vara säkra på att vi alltid beräknar det senaste uppdaterade antalet studiepoäng för varje studerande.

Ändringar vs. frågor

Fastän det ideala är att en databas inte ska innehålla redundant information, behövs sådan ibland för att effektivisera sökningar. Redundans gör databasen svårare att ändra, men underlättar frågorna.

Ett ofta förekommande fenomen inom datavetenskap är att vi måste balansera mellan om vi vill kunna ändra eller hämta information effektivt, och hur mycket lagringsutrymme vi kan använda. Det här gäller utöver databaser också till exempel vid utformning av algoritmer.

Om databasen inte innehåller redundant information är ändringar lätta att göra, eftersom varje uppgift finns på endast ett ställe och det således räcker med att ändra en rad i en tabell. Man sparar också på lagringsutrymme genom att inte ha redundant information. Å andra sidan kan frågorna bli mer komplicerade och långsamma, eftersom den information man behöver nu måste samlas in från flera olika delar av databasen.

Genom att lägga till redundant information kan vi snabba upp frågorna. Ändringar i databasen blir dock mer besvärliga, eftersom den ändrade informationen måste uppdateras på flera ställen. Den redundanta informationen gör också att databasen tar upp mer lagringsutrymme.

Det finns ingen allmän regel för hur mycket redundant information det lönar sig att lägga till, utan det beror på databasens innehåll och vilka typer av frågor man vill kunna göra. Ett bra tillvägagångssätt är att börja utan någon redundant information alls i databasen. Varefter det visar sig att frågorna behöver göras mer effektiva, kan redundans införas.

Exempel på design

Låt oss till sist betrakta ett mer omfattande exempel där vårt mål är att designa en databas för bokning av universitetsföreläsningssalar. Databasen ska möjliggöra följande:

Designen steg-för-steg

Databasdesignen utvecklas vanligtvis stegvis, så att nya tabeller och kolumner läggs till i databasen allteftersom behov uppstår.

Låt oss se hur exempel-databasen byggs upp steg för steg.

Inloggning i systemet

Detta är ett vanligt och bra sätt att börja på när man designar en databas. Vi behöver en tabell som innehåller användarnamn och lösenord:

CREATE TABLE Users (
  id INTEGER PRIMARY KEY,
  username TEXT,
  password TEXT
);

Eftersom det finns två typer av användare i systemet (vanliga användare och administratörer) måste denna information också sparas i databasen. Två möjliga lösningar är:

Lösning 1 är vanligtvis bättre ur databasanvändningens synvinkel. Vi lägger därför till en ny kolumn user_role i tabellen Users som anger användarens roll (t.ex. 1 = vanlig användare, 2 = administratör):

CREATE TABLE Users (
  id INTEGER PRIMARY KEY,
  username TEXT,
  password TEXT,
  user_role INTEGER
);

Information om salen som ska bokas

En bra lösning är att skapa separata tabeller Buildings och Categories där information om byggnader respektive prisklasser sparas:

CREATE TABLE Buildings (
  id INTEGER PRIMARY KEY,
  name TEXT
);
CREATE TABLE Categories (
  id INTEGER PRIMARY KEY,
  name TEXT,
);

Därefter kan vi skapa tabellen Rooms, som innehåller information om salarna. Denna tabell refererar till tabellerna Buildings och Categories.

CREATE TABLE Rooms (
  id INTEGER PRIMARY KEY,
  name TEXT,
  building_id INTEGER REFERENCES Buildings,
  seat_count INTEGER,
  category_id INTEGER REFERENCES Categories
);

Vi kan nu lägga till följande information om salen A111 i systemet:

INSERT INTO Buildings (name) VALUES ('Exactum');
INSERT INTO Categories (name) VALUES ('A10');
INSERT INTO Rooms (name, building_id, seat_count, category_id)
       VALUES ('A111', 1, 280, 1);

Totalpriset för bokningen

För denna funktion kan vi lägga till en kolumn price_per_hour i tabellen Categories som anger timpriset för respektive prisklass:

CREATE TABLE Categories (
  id INTEGER PRIMARY KEY,
  name TEXT,
  price_per_hour INTEGER
);

Därefter kan vi beräkna hur mycket en bokning på fyra timmar för sal A111 kostar.

SELECT Categories.price_per_hour * 4 AS total_price
FROM Rooms, Categories
WHERE Rooms.category_id = Categories.id AND Rooms.name = 'A111';

Byggnadernas öpettider

Byggnaderna är vanligtvis öppna på vardagar men stängda på helger och helgdagar. Dessutom kan öppettiderna ändras, till exempel under sommaren.

I praktiken kan det vara svårt att definiera riktlinjer som fungerar i alla situationer och anger när en viss byggnad är öppen. Ett bra alternativ är istället att skapa tabellen OpeningHours som innehåller en rad för varje byggnad och för varje dag den är öppen:

CREATE TABLE OpeningHours (
  id INTEGER PRIMARY KEY,
  building_id INTEGER REFERENCES Buildings,
  start_time TIMESTAMP,
  end_time TIMESTAMP
);

Till exempel lägger följande kommandon till uppgifter om när byggnaden Exactum är öppen under den första veckan i september 2026:

INSERT INTO OpeningHours (building_id, start_time, end_time)
       VALUES (1, '2026-09-01 08:00:00', '2026-09-01 20:00:00');
INSERT INTO OpeningHours (building_id, start_time, end_time)
       VALUES (1, '2026-09-02 08:00:00', '2026-09-02 20:00:00');
INSERT INTO OpeningHours (building_id, start_time, end_time)
       VALUES (1, '2026-09-03 08:00:00', '2026-09-03 20:00:00');
INSERT INTO OpeningHours (building_id, start_time, end_time)
       VALUES (1, '2026-09-04 08:00:00', '2026-09-04 20:00:00');
INSERT INTO OpeningHours (building_id, start_time, end_time)
       VALUES (1, '2026-09-05 08:00:00', '2026-09-05 18:00:00');

Söka efter en lämplig sal

Denna funktion kan utföras med hjälp av de redan befintliga tabellerna. Till exempel söker följande kommando efter en sal i Exactum som rymmer minst 100 personer:

SELECT Rooms.name
FROM Rooms, Buildings
WHERE Rooms.building_id = Buildings.id AND
      Rooms.seat_count >= 100 AND Buildings.name = 'Exactum';

Bokningsförfrågan och bekräftelse

Vi kan skapa tabellen Requests där bokningsförfrågningar sparas. Varje förfrågan är kopplad till en användare, en sal, bokningens start- och sluttid samt en kommentar.

CREATE TABLE Requests (
  id INTEGER PRIMARY KEY
  user_id INTEGER REFERENCES Users,
  room_id INTEGER REFERENCES Rooms,
  start_time TIMESTAMP,
  end_time TIMESTAMP,
  comment TEXT
);

Vad händer när administratören godkänner en bokning? En lösning skulle vara att skapa en annan tabell Reservations, som innehåller de godkända bokningarna:

CREATE TABLE Reservations (
  id INTEGER PRIMARY KEY,
  user_id INTEGER REFERENCES Users,
  room_id INTEGER REFERENCES Rooms,
  start_time TIMESTAMP,
  end_time TIMESTAMP
);

Även om en bokningsförfrågan och en bekräftad bokning i sig är olika saker, uppstår här problemet att tabellerna Requests och Reservations innehåller mycket liknande information. En bättre lösning kunde vara att slå ihop dessa tabeller exempelvis på följande sätt:

CREATE TABLE Reservations (
  id INTEGER PRIMARY KEY,
  user_id INTEGER REFERENCES Users,
  room_id INTEGER REFERENCES Rooms,
  start_time TIMESTAMP,
  end_time TIMESTAMP,
  status INTEGER
);

I den här tabellen anger kolumnen status bokningens status. Vi kan till exempel bestämma att status 1 betyder en bokningsförfrågan och status 2 betyder en bekräftad bokning.

Beskrivning av databasen

Det finns två vanliga sätt att beskriva databasens struktur: ett grafiskt databasschema som visar relationerna mellan tabellerna, och ett SQL-schema som innehåller kommandon för att skapa tabellerna

Databasschema

Ett databasschema är en grafisk representation av databasen där varje tabell visas som en ruta som innehåller tabellens namn och kolumner som en lista. Referenser mellan rader visas som kopplingar mellan rutorna.

Det finns olika sätt att rita ett databasschema. Följande schema har skapats med ett onlineverktyget dbdiagram.io:

SQL-schema

Ett SQL-schema innehåller CREATE TABLE-kommandon som används för att skapa databasen. Följande SQL-schema motsvarar vår exempel-databas:

CREATE TABLE Users (
  id INTEGER PRIMARY KEY,
  username TEXT,
  password TEXT,
  user_role INTEGER
);

CREATE TABLE Buildings (
  id INTEGER PRIMARY KEY,
  name TEXT
);

CREATE TABLE Categories (
  id INTEGER PRIMARY KEY,
  name TEXT,
  price_per_hour INTEGER
);

CREATE TABLE Rooms (
  id INTEGER PRIMARY KEY,
  name TEXT,
  building_id INTEGER REFERENCES Buildings,
  seat_count INTEGER,
  category_id INTEGER REFERENCES Categories
);

CREATE TABLE OpeningHours (
  id INTEGER PRIMARY KEY,
  building_id INTEGER REFERENCES Buildings,
  start_time TIMESTAMP,
  end_time TIMESTAMP
);

CREATE TABLE Reservations (
  id INTEGER PRIMARY KEY,
  user_id INTEGER REFERENCES Users,
  room_id INTEGER REFERENCES Rooms,
  start_time TIMESTAMP,
  end_time TIMESTAMP,
  status INTEGER
);