Databasplanering
Databasplanering är processen att designa och strukturera en databas innan implementering. En välplanerad databas är grunden för ett framgångsrikt system - den avgör prestanda, skalbarhet, underhållbarhet och dataintegritet. Denna guide leder dig genom hela planeringsprocessen från idé till färdig design.
Innehållsförteckning
- Introduktion
- Varför är databasplanering viktigt
- De fyra faserna i databasplanering
- Fas 1: Kravanalys
- Fas 2: Konceptuell design
- Fas 3: Logisk design
- Fas 4: Fysisk design
- Praktiskt exempel: Bibliotekssystem
- Vanliga misstag att undvika
- Best practices
- Slutsats
- TL;DR
Introduktion
Att bygga en databas utan planering är som att bygga ett hus utan ritning - det kan fungera kortvarigt, men strukturella problem dyker upp snabbt. Databasplanering hjälper oss att förstå:
- Vad ska lagras (entiteter och attribut)
- Hur data relaterar till varandra (relationer)
- Varför varje del behövs (affärslogik)
- När data uppdateras (livscykel)
En bra databas växer med företaget, hanterar miljontals poster utan att sakta ner, och håller data korrekt och säkert.
Varför är databasplanering viktigt
Kostnadsbesparingar
Att rätta till designfel i produktion är 10-100 gånger dyrare än att göra rätt från början. En studie av IBM visade att:
- Fel upptäckt under design: 1 kr att fixa
- Fel upptäckt under utveckling: 10 kr att fixa
- Fel upptäckt i produktion: 100 kr att fixa
Prestanda
En väldesignad databas med rätt index och struktur kan vara 1000x snabbare än en dåligt planerad databas.
Exempel:
- Dålig design: Sökning tar 5 sekunder (oanvändbart)
- Bra design: Sökning tar 5 millisekunder (snabbt!)
Dataintegritet
God design förhindrar:
- Duplikerad data
- Inkonsistenta uppdateringar
- Förlorad information
- Ogiltiga relationer
Skalbarhet
En välplanerad databas kan växa från 1000 till 1 miljon användare utan omskrivning.
De fyra faserna i databasplanering
1. KRAVANALYS → Vad behöver systemet göra?
↓
2. KONCEPTUELL DESIGN → Vilka entiteter och relationer finns?
↓
3. LOGISK DESIGN → Hur översätts detta till tabeller?
↓
4. FYSISK DESIGN → Hur optimerar vi implementeringen?
Fas 1: Kravanalys
Mål
Förstå vad systemet ska göra och vilka data som behövs.
Aktiviteter
1.1 Identifiera intressenter
Vem använder systemet?
- Slutanvändare (kunder, anställda)
- Administratörer
- Utvecklare
- Management
1.2 Samla in krav
Funktionella krav (vad systemet ska göra):
- “Kunder ska kunna lägga beställningar”
- “Systemet ska spåra lagerstatus”
- “Administratörer ska kunna generera försäljningsrapporter”
Icke-funktionella krav (hur systemet ska fungera):
- Prestanda: “Svar inom 100ms”
- Säkerhet: “GDPR-kompatibel”
- Skalbarhet: “Hantera 10,000 samtidiga användare”
1.3 Dokumentera affärsregler
Affärsregler definierar begränsningar och logik:
- “En kund måste vara minst 18 år”
- “En beställning kan inte raderas efter leverans”
- “Rabatter tillämpas endast på icke-kampanjvaror”
- “Lagersaldo kan inte vara negativt”
Exempel: Bibliotekssystem - Kravanalys
FUNKTIONELLA KRAV:
✓ Användare ska kunna låna böcker
✓ Systemet ska spåra vilka böcker som är utlånade
✓ Böcker har ett lånedatum på 14 dagar
✓ Användare kan reservera utlånade böcker
AFFÄRSREGLER:
• Max 5 samtidiga lån per användare
• Böter: 10 kr/dag för försenade böcker
• Reservationer giltig i 3 dagar efter återlämning
Fas 2: Konceptuell design
Mål
Skapa en abstrakt modell av data utan tekniska detaljer.
Aktiviteter
2.1 Identifiera entiteter
Entiteter är “saker” som systemet behöver hålla reda på.
Tips för att hitta entiteter:
- Leta efter substantiv i kraven: “kund”, “produkt”, “beställning”
- Fråga: “Vad behöver vi spara information om?”
Exempel:
BIBLIOTEKSSYSTEM:
• Bok
• Användare (låntagare)
• Författare
• Lån
• Reservation
2.2 Identifiera attribut
Attribut är egenskaper hos entiteter.
Exempel:
BOK:
- Titel
- ISBN
- Publiceringsa år
- Sidantal
- Språk
ANVÄNDARE:
- Personnummer
- Namn
- E-post
- Telefonnummer
- Registreringsdatum
2.3 Identifiera relationer
Relationer beskriver hur entiteter är kopplade.
Typer av relationer:
En-till-en (1:1)
Användare ←→ ProfilBild
(Varje användare har max en profilbild)
En-till-många (1:N)
Författare ←→ Böcker
(En författare kan skriva många böcker,
men varje bok har en huvudförfattare)
Många-till-många (M:N)
Böcker ←→ Kategorier
(En bok kan tillhöra flera kategorier,
en kategori innehåller många böcker)
2.4 Rita ER-diagram
Entity-Relationship diagram visualiserar designen.
[Användare] ────< lånar >──── [Bok]
│
- Lånedatum
- Återlämningsdatum
- Förfallodatum
[Bok] ────< skriven av >──── [Författare]
[Bok] ────< tillhör >──── [Kategori]
Fas 3: Logisk design
Mål
Översätt konceptuell design till databasschema (tabeller, kolumner, nycklar).
Aktiviteter
3.1 Skapa tabeller från entiteter
Varje entitet blir en tabell.
-- Bok-entitet → Books tabell
CREATE TABLE Books (
-- Kolumner här
);
-- Användare-entitet → Users tabell
CREATE TABLE Users (
-- Kolumner här
);
3.2 Definiera primärnycklar
Varje tabell behöver en unik identifierare.
CREATE TABLE Books (
BookId INTEGER PRIMARY KEY AUTOINCREMENT,
ISBN TEXT UNIQUE NOT NULL,
Title TEXT NOT NULL,
PublicationYear INTEGER
);
Val av primärnyckel:
Surrogatnyckel (BookId): Artificiell ID skapad av databasen
- ✓ Enkelt, alltid unikt, oföränderligt
- ✗ Inget affärsmässigt värde
Naturlig nyckel (ISBN): Verklig värld-identifierare
- ✓ Affärsmässigt meningsfull
- ✗ Kan ändras, komplex
Rekommendation: Använd surrogatnycklar för primärnycklar, men behåll naturliga nycklar som UNIQUE constraints.
3.3 Definiera främmande nycklar
Främmande nycklar skapar relationer mellan tabeller.
CREATE TABLE Loans (
LoanId INTEGER PRIMARY KEY AUTOINCREMENT,
UserId INTEGER NOT NULL,
BookId INTEGER NOT NULL,
LoanDate TEXT NOT NULL,
DueDate TEXT NOT NULL,
ReturnDate TEXT,
FOREIGN KEY (UserId) REFERENCES Users(UserId),
FOREIGN KEY (BookId) REFERENCES Books(BookId)
);
3.4 Hantera många-till-många relationer
Många-till-många kräver en kopplingstabell.
Exempel: Böcker ←→ Kategorier
-- Huvudtabeller
CREATE TABLE Books (
BookId INTEGER PRIMARY KEY,
Title TEXT NOT NULL
);
CREATE TABLE Categories (
CategoryId INTEGER PRIMARY KEY,
CategoryName TEXT NOT NULL
);
-- Kopplingstabell (junction table)
CREATE TABLE BookCategories (
BookId INTEGER,
CategoryId INTEGER,
PRIMARY KEY (BookId, CategoryId),
FOREIGN KEY (BookId) REFERENCES Books(BookId),
FOREIGN KEY (CategoryId) REFERENCES Categories(CategoryId)
);
3.5 Definiera constraints
Constraints säkerställer dataintegritet.
CREATE TABLE Users (
UserId INTEGER PRIMARY KEY AUTOINCREMENT,
PersonalNumber TEXT UNIQUE NOT NULL,
Name TEXT NOT NULL,
Email TEXT UNIQUE NOT NULL,
PhoneNumber TEXT,
RegistrationDate TEXT NOT NULL,
-- Constraints
CHECK (length(PersonalNumber) = 13),
CHECK (Email LIKE '%@%.%')
);
Vanliga constraints:
NOT NULL- Värde måste finnasUNIQUE- Värde måste vara uniktCHECK- Värde måste uppfylla villkorDEFAULT- Standardvärde om inget angesFOREIGN KEY- Referens till annan tabell
Fas 4: Fysisk design
Mål
Optimera databasen för prestanda och lagring.
Aktiviteter
4.1 Välj datatyper
Rätt datatyp sparar utrymme och förbättrar prestanda.
CREATE TABLE Products (
ProductId INTEGER PRIMARY KEY,
-- Text
Name TEXT NOT NULL, -- Variabel längd
SKU CHAR(10) NOT NULL, -- Fix längd (bättre för sökning)
-- Numeriska
Price DECIMAL(10,2) NOT NULL, -- Pengar (exakta värden)
Stock INTEGER NOT NULL DEFAULT 0, -- Heltal
Weight REAL, -- Flyttal
-- Datum
CreatedAt TEXT NOT NULL, -- ISO 8601: 'YYYY-MM-DD HH:MM:SS'
-- Boolean (SQLite har inte BOOLEAN, använd INTEGER)
IsActive INTEGER DEFAULT 1, -- 0 = false, 1 = true
CHECK (Price >= 0),
CHECK (Stock >= 0),
CHECK (IsActive IN (0, 1))
);
4.2 Skapa index
Index accelererar sökningar men saktar ner inserts/updates.
-- Index för ofta sökt kolumn
CREATE INDEX idx_books_title ON Books(Title);
-- Index för främmande nyckel (viktigt för JOINs!)
CREATE INDEX idx_loans_userid ON Loans(UserId);
CREATE INDEX idx_loans_bookid ON Loans(BookId);
-- Sammansatt index för kombinerade sökningar
CREATE INDEX idx_loans_dates ON Loans(LoanDate, DueDate);
-- Unique index (automatiskt för UNIQUE constraints)
CREATE UNIQUE INDEX idx_users_email ON Users(Email);
När ska man använda index?
- ✓ Främmande nycklar
- ✓ Kolumner i WHERE-clause
- ✓ Kolumner i JOIN
- ✓ Kolumner i ORDER BY
- ✗ Små tabeller (< 1000 rader)
- ✗ Kolumner som ofta uppdateras
- ✗ Kolumner med få unika värden
4.3 Definiera vyer (views)
Vyer är “virtuella tabeller” för ofta använda queries.
-- Vy som visar aktiva lån
CREATE VIEW ActiveLoans AS
SELECT
u.Name AS UserName,
b.Title AS BookTitle,
l.LoanDate,
l.DueDate,
CASE
WHEN l.DueDate < date('now') THEN 'Försenad'
ELSE 'Aktiv'
END AS Status
FROM Loans l
JOIN Users u ON l.UserId = u.UserId
JOIN Books b ON l.BookId = b.BookId
WHERE l.ReturnDate IS NULL;
-- Användning
SELECT * FROM ActiveLoans WHERE Status = 'Försenad';
4.4 Planera för skalbarhet
Partitionering (för mycket stora tabeller):
-- Exempel: Dela upp lån per år
CREATE TABLE Loans_2024 (...);
CREATE TABLE Loans_2025 (...);
Arkivering:
-- Flytta gamla poster till arkivtabell
CREATE TABLE Loans_Archive AS
SELECT * FROM Loans WHERE ReturnDate < date('now', '-5 years');
DELETE FROM Loans WHERE ReturnDate < date('now', '-5 years');
Praktiskt exempel: Bibliotekssystem
Fullständigt schema
-- ====================================
-- ANVÄNDARE
-- ====================================
CREATE TABLE Users (
UserId INTEGER PRIMARY KEY AUTOINCREMENT,
PersonalNumber TEXT UNIQUE NOT NULL,
Name TEXT NOT NULL,
Email TEXT UNIQUE NOT NULL,
PhoneNumber TEXT,
RegistrationDate TEXT NOT NULL DEFAULT (datetime('now')),
CHECK (length(PersonalNumber) = 13),
CHECK (Email LIKE '%@%.%')
);
-- ====================================
-- FÖRFATTARE
-- ====================================
CREATE TABLE Authors (
AuthorId INTEGER PRIMARY KEY AUTOINCREMENT,
Name TEXT NOT NULL,
BirthYear INTEGER,
Nationality TEXT,
CHECK (BirthYear > 1000 AND BirthYear < 2100)
);
-- ====================================
-- BÖCKER
-- ====================================
CREATE TABLE Books (
BookId INTEGER PRIMARY KEY AUTOINCREMENT,
ISBN TEXT UNIQUE NOT NULL,
Title TEXT NOT NULL,
AuthorId INTEGER NOT NULL,
PublicationYear INTEGER,
Pages INTEGER,
Language TEXT DEFAULT 'Swedish',
FOREIGN KEY (AuthorId) REFERENCES Authors(AuthorId),
CHECK (Pages > 0)
);
-- ====================================
-- KATEGORIER
-- ====================================
CREATE TABLE Categories (
CategoryId INTEGER PRIMARY KEY AUTOINCREMENT,
CategoryName TEXT UNIQUE NOT NULL,
Description TEXT
);
-- ====================================
-- BOK-KATEGORI KOPPLING (M:N)
-- ====================================
CREATE TABLE BookCategories (
BookId INTEGER,
CategoryId INTEGER,
PRIMARY KEY (BookId, CategoryId),
FOREIGN KEY (BookId) REFERENCES Books(BookId) ON DELETE CASCADE,
FOREIGN KEY (CategoryId) REFERENCES Categories(CategoryId) ON DELETE CASCADE
);
-- ====================================
-- LÅN
-- ====================================
CREATE TABLE Loans (
LoanId INTEGER PRIMARY KEY AUTOINCREMENT,
UserId INTEGER NOT NULL,
BookId INTEGER NOT NULL,
LoanDate TEXT NOT NULL DEFAULT (date('now')),
DueDate TEXT NOT NULL,
ReturnDate TEXT,
FOREIGN KEY (UserId) REFERENCES Users(UserId),
FOREIGN KEY (BookId) REFERENCES Books(BookId),
CHECK (DueDate >= LoanDate),
CHECK (ReturnDate IS NULL OR ReturnDate >= LoanDate)
);
-- ====================================
-- RESERVATIONER
-- ====================================
CREATE TABLE Reservations (
ReservationId INTEGER PRIMARY KEY AUTOINCREMENT,
UserId INTEGER NOT NULL,
BookId INTEGER NOT NULL,
ReservationDate TEXT NOT NULL DEFAULT (datetime('now')),
ExpiryDate TEXT NOT NULL,
Status TEXT DEFAULT 'Active',
FOREIGN KEY (UserId) REFERENCES Users(UserId),
FOREIGN KEY (BookId) REFERENCES Books(BookId),
CHECK (Status IN ('Active', 'Fulfilled', 'Expired', 'Cancelled'))
);
-- ====================================
-- INDEX FÖR PRESTANDA
-- ====================================
CREATE INDEX idx_books_title ON Books(Title);
CREATE INDEX idx_books_authorid ON Books(AuthorId);
CREATE INDEX idx_loans_userid ON Loans(UserId);
CREATE INDEX idx_loans_bookid ON Loans(BookId);
CREATE INDEX idx_loans_dates ON Loans(LoanDate, DueDate);
CREATE INDEX idx_reservations_userid ON Reservations(UserId);
CREATE INDEX idx_reservations_bookid ON Reservations(BookId);
-- ====================================
-- VYER
-- ====================================
CREATE VIEW AvailableBooks AS
SELECT
b.BookId,
b.ISBN,
b.Title,
a.Name AS Author,
b.Language,
CASE
WHEN l.LoanId IS NULL THEN 'Tillgänglig'
ELSE 'Utlånad'
END AS Status
FROM Books b
JOIN Authors a ON b.AuthorId = a.AuthorId
LEFT JOIN Loans l ON b.BookId = l.BookId AND l.ReturnDate IS NULL;
CREATE VIEW OverdueLoans AS
SELECT
u.Name AS UserName,
u.Email,
b.Title AS BookTitle,
l.DueDate,
julianday('now') - julianday(l.DueDate) AS DaysOverdue,
CAST((julianday('now') - julianday(l.DueDate)) * 10 AS INTEGER) AS Fine
FROM Loans l
JOIN Users u ON l.UserId = u.UserId
JOIN Books b ON l.BookId = b.BookId
WHERE l.ReturnDate IS NULL
AND l.DueDate < date('now');
Vanliga misstag att undvika
1. Ingen primärnyckel
-- DÅLIGT ❌
CREATE TABLE Products (
Name TEXT,
Price REAL
);
-- Problem: Kan inte identifiera unika rader
-- BRA ✓
CREATE TABLE Products (
ProductId INTEGER PRIMARY KEY AUTOINCREMENT,
Name TEXT NOT NULL,
Price REAL NOT NULL
);
2. Lagra flera värden i en kolumn
-- DÅLIGT ❌
CREATE TABLE Products (
ProductId INTEGER PRIMARY KEY,
Name TEXT,
Categories TEXT -- "Elektronik,Hemelektronik,Datorer"
);
-- Problem: Kan inte söka eller filtrera effektivt
-- BRA ✓
-- Använd kopplingstabell (se BookCategories ovan)
3. Array-dilemmat i C#-klasser 🎯
VIKTIGT för C#-utvecklare: När du designar klasser för databasen är det lätt att skapa designproblem med arrayer/listor!
Problemet: Flera oberoende listor
DÅLIG C# klassdesign:
public class Teacher {
public int TeacherId { get; set; }
public string Name { get; set; }
public List<string> Courses { get; set; } // ← Multi-valued!
public List<string> PhoneNumbers { get; set; } // ← Multi-valued!
}
Fråga: Hur lagrar vi detta i databasen?
Dåligt alternativ 1: Serialisera till JSON
CREATE TABLE Teachers (
TeacherId INTEGER PRIMARY KEY,
Name TEXT,
Courses TEXT, -- '["Math","Physics"]'
PhoneNumbers TEXT -- '["070-1111111","070-2222222"]'
);
Varför dåligt:
- ❌ Kan inte söka efter lärare som undervisar “Math”
- ❌ Kan inte filtrera eller JOIN på kurser
- ❌ Kan inte lägga till constraints
- ❌ Bryter mot första normalformen (1NF)
Dåligt alternativ 2: Kartesisk produkt (bryter mot 4NF)
CREATE TABLE Teachers (
TeacherId INTEGER PRIMARY KEY,
Name TEXT,
Course TEXT,
PhoneNumber TEXT
);
-- Data:
1 | Dr. Smith | Math | 070-1111111
1 | Dr. Smith | Math | 070-2222222 ← Samma kurs, annat nummer
1 | Dr. Smith | Physics | 070-1111111 ← Annan kurs, samma nummer
1 | Dr. Smith | Physics | 070-2222222 ← KARTESISK PRODUKT!
Varför dåligt:
- ❌ Namn upprepas 4 gånger (redundans)
- ❌ Lägg till ett telefonnummer → måste lägga till 2 rader (en per kurs!)
- ❌ Ta bort en kurs → måste radera flera rader
- ❌ Explosiv dataväxt: 3 kurser × 3 nummer = 9 rader!
Lösningen: Separata tabeller för oberoende listor
BRA C# klassdesign:
public class Teacher {
public int TeacherId { get; set; }
public string Name { get; set; }
// Inga listor av oberoende data här!
}
public class TeacherCourse {
public int TeacherId { get; set; }
public string Course { get; set; }
}
public class TeacherPhone {
public int TeacherId { get; set; }
public string PhoneNumber { get; set; }
}
SQL-schema:
CREATE TABLE Teachers (
TeacherId INTEGER PRIMARY KEY AUTOINCREMENT,
Name TEXT NOT NULL
);
CREATE TABLE TeacherCourses (
TeacherId INTEGER,
Course TEXT,
PRIMARY KEY (TeacherId, Course),
FOREIGN KEY (TeacherId) REFERENCES Teachers(TeacherId)
);
CREATE TABLE TeacherPhones (
TeacherId INTEGER,
PhoneNumber TEXT,
PRIMARY KEY (TeacherId, PhoneNumber),
FOREIGN KEY (TeacherId) REFERENCES Teachers(TeacherId)
);
Data:
Teachers:
1 | Dr. Smith
TeacherCourses: TeacherPhones:
1 | Math 1 | 070-1111111
1 | Physics 1 | 070-2222222
Fördelar:
- ✓ Lägg till kurs: 1 rad
- ✓ Lägg till telefonnummer: 1 rad
- ✓ Kan söka:
SELECT * FROM TeacherCourses WHERE Course = 'Math' - ✓ Ingen redundans
- ✓ Korrekt normaliserad (4NF)
När är listor i klasser OK?
Beroende listor är OK:
public class Order {
public int OrderId { get; set; }
public DateTime OrderDate { get; set; }
public List<OrderLine> Lines { get; set; } // ← OK! OrderLines beror på Order
}
public class OrderLine {
public int ProductId { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
}
SQL:
CREATE TABLE Orders (
OrderId INTEGER PRIMARY KEY,
OrderDate TEXT NOT NULL
);
CREATE TABLE OrderLines (
OrderLineId INTEGER PRIMARY KEY,
OrderId INTEGER NOT NULL, -- Beror på Order!
ProductId INTEGER NOT NULL,
Quantity INTEGER NOT NULL,
Price REAL NOT NULL,
FOREIGN KEY (OrderId) REFERENCES Orders(OrderId)
);
Detta är OK eftersom OrderLines beror på Order - en orderrad kan inte existera utan order.
Tumregel för C#-klassdesign
Om din klass har två eller fler listor av OBEROENDE data: → Du behöver troligen separata tabeller i databasen!
Test: Är listan oberoende?
- Telefonnummer oberoende av kurser? → JA → Separata tabeller
- Skills oberoende av certifieringar? → JA → Separata tabeller
- OrderLines oberoende av Order? → NEJ → En tabell med foreign key
Se också: Dokumentet om normalisering innehåller djupare förklaring av 4NF och multi-valued dependencies.
4. Använd inte reserverade ord som tabellnamn
-- DÅLIGT ❌
CREATE TABLE Order (...); -- "Order" är reserverat ord
CREATE TABLE Group (...);
-- BRA ✓
CREATE TABLE Orders (...);
CREATE TABLE UserGroups (...);
4. Bristande constraints
-- DÅLIGT ❌
CREATE TABLE Users (
UserId INTEGER PRIMARY KEY,
Email TEXT
);
-- Problem: Kan spara tomma emails, duplikater
-- BRA ✓
CREATE TABLE Users (
UserId INTEGER PRIMARY KEY,
Email TEXT UNIQUE NOT NULL,
CHECK (Email LIKE '%@%.%')
);
5. För bred datatyp
-- DÅLIGT ❌
CREATE TABLE Products (
Status TEXT -- Kan vara vad som helst!
);
-- BRA ✓
CREATE TABLE Products (
Status TEXT CHECK(Status IN ('Active', 'Discontinued', 'OutOfStock'))
);
Best practices
1. Namnkonventioner
Tabeller:
- Använd plural:
Users,Products,Orders - PascalCase eller snake_case:
OrderItemsellerorder_items - Var konsekvent!
Kolumner:
- Beskrivande namn:
FirstNameinteFN - Inkludera enhet om relevant:
PriceInSEK,WeightKg - Booleans:
Is/Has/Can prefix:IsActive,HasPaid
Nycklar:
- Primary key:
TableNameIdellerId - Foreign key: Samma som refererade kolumnen
2. Dokumentation
Kommentera komplexa constraints och affärslogik:
CREATE TABLE Discounts (
DiscountId INTEGER PRIMARY KEY,
ProductId INTEGER,
PercentOff INTEGER,
ValidFrom TEXT,
ValidTo TEXT,
-- Affärsregel: Rabatt kan max vara 80%
CHECK (PercentOff > 0 AND PercentOff <= 80),
-- Affärsregel: Kampanjer kan inte vara längre än 90 dagar
CHECK (julianday(ValidTo) - julianday(ValidFrom) <= 90),
FOREIGN KEY (ProductId) REFERENCES Products(ProductId)
);
3. Versionshantering
Spara SQL-scripts i version control (Git):
/database
/migrations
001_create_users.sql
002_create_products.sql
003_add_email_index.sql
/seeds
test_data.sql
4. Säkerhet från start
-- Lagra aldrig lösenord i klartext!
CREATE TABLE Users (
UserId INTEGER PRIMARY KEY,
Username TEXT UNIQUE NOT NULL,
PasswordHash TEXT NOT NULL, -- Hash, inte password!
Salt TEXT NOT NULL,
-- GDPR: Spara samtycke
MarketingConsent INTEGER DEFAULT 0,
ConsentDate TEXT
);
5. Planera för framtiden
-- Lägg till timestamp-kolumner från början
CREATE TABLE Products (
ProductId INTEGER PRIMARY KEY,
Name TEXT NOT NULL,
-- Audit trail
CreatedAt TEXT NOT NULL DEFAULT (datetime('now')),
UpdatedAt TEXT NOT NULL DEFAULT (datetime('now')),
CreatedBy INTEGER,
-- Soft delete istället för DELETE
IsDeleted INTEGER DEFAULT 0,
DeletedAt TEXT,
FOREIGN KEY (CreatedBy) REFERENCES Users(UserId)
);
Slutsats
Databasplanering är en investering som betalar sig många gånger om. Genom att följa en strukturerad process:
- Kravanalys - Förstå vad systemet behöver
- Konceptuell design - Identifiera entiteter och relationer
- Logisk design - Översätt till tabeller och nycklar
- Fysisk design - Optimera för prestanda
…skapar du en databas som är:
- ✓ Skalbar (växer med företaget)
- ✓ Performant (snabba queries)
- ✓ Underhållbar (lätt att förstå och ändra)
- ✓ Säker (dataintegritet garanterad)
Kom ihåg: Tid spenderad på planering sparar veckor av omskrivning senare!
TL;DR
Databasplanering i 4 steg:
- Kravanalys → Vad behöver systemet? Vilka affärsregler?
- Konceptuell design → Entiteter, attribut, relationer (ER-diagram)
- Logisk design → Tabeller, primär-/främmande nycklar, constraints
- Fysisk design → Datatyper, index, vyer, optimering
Grundregler:
- Varje tabell har primärnyckel
- Använd främmande nycklar för relationer
- Många-till-många → kopplingstabell
- Lägg till constraints (NOT NULL, UNIQUE, CHECK)
- Skapa index för ofta sökta kolumner
- Dokumentera affärsregler i SQL-kommentarer
- Planera för framtiden (audit trail, soft delete)
Undvik:
- ✗ Flera värden i en kolumn
- ✗ Saknade primärnycklar
- ✗ Bristande constraints
- ✗ Reserverade ord som tabellnamn
- ✗ För breda datatyper
- ✗ C# Array-dilemmat: Två oberoende listor i en klass → behöver separata tabeller!
C#-utvecklare:
- Om klass har
List<Course>OCHList<Phone>(oberoende) → Skapa separata tabeller! - Om klass har
List<OrderLine>(beroende av Order) → En tabell med foreign key!
God planering = snabb, säker, skalbar databas! 🚀
Snabba steg (Code First eller SQL-first)
-- Tidigt DDL för minimal shop
CREATE TABLE Products (
ProductId INTEGER PRIMARY KEY,
Name TEXT NOT NULL,
PriceInCents INTEGER NOT NULL
);
CREATE TABLE Customers (
CustomerId INTEGER PRIMARY KEY,
Email TEXT UNIQUE NOT NULL
);
Checklist:
- Identifiera entiteter, relationer, nycklar
- Bestäm nullable/NOT NULL och datatyper
- Planera migrations och seed-data