Tilbake
5.3
Databaser i Python med SQLite

5.3 Databaser i Python med SQLite

Koble til og bruke databaser fra Python-kode.

65 min
6 oppgaver
SQLitePythonCRUDParameteriserte spørringer
Du leser den tradisjonelle versjonen
Din fremgang i kapitlet
0 / 6 oppgaver

Databaser i Python med SQLite

Nå skal vi kombinere Python-kunnskapene dine med databasekompetansen. SQLite er en lettvekts database som kommer innebygd i Python – perfekt for å lage lokale applikasjoner, prototyper og mindre systemer.

I dette kapittelet lærer du:
- Koble Python til en SQLite-database
- Utføre CRUD-operasjoner (Create, Read, Update, Delete)
- Bruke parameteriserte spørringer for å unngå SQL injection
- Håndtere feil og transaksjoner
- Bygge praktiske databaseapplikasjoner

Koble til database med sqlite3

Python har innebygd støtte for SQLite gjennom sqlite3-modulen.

Grunnleggende oppsett:

import sqlite3

# Koble til database (oppretter filen hvis den ikke finnes)
conn = sqlite3.connect('skole.db')

# Opprett en cursor for å utføre SQL-kommandoer
cursor = conn.cursor()

# Utfør SQL-kommandoer her...

# Lukk forbindelsen når du er ferdig
conn.close()

In-memory database (for testing):

# Database som bare eksisterer i RAM
conn = sqlite3.connect(':memory:')

Context manager (anbefalt):

import sqlite3

# Automatisk lukking av forbindelse
with sqlite3.connect('skole.db') as conn:
    cursor = conn.cursor()
    # Gjør databaseoperasjoner her
    # conn lukkes automatisk når blokken er ferdig

Opprette tabeller

import sqlite3

conn = sqlite3.connect('skole.db')
cursor = conn.cursor()

# Opprett tabell
cursor.execute('''
    CREATE TABLE IF NOT EXISTS Elev (
        elevID INTEGER PRIMARY KEY AUTOINCREMENT,
        navn TEXT NOT NULL,
        klasse TEXT,
        epost TEXT UNIQUE
    )
''')

# Lagre endringer
conn.commit()
conn.close()

Viktige punkter:
- IF NOT EXISTS forhindrer feil hvis tabellen allerede finnes
- AUTOINCREMENT genererer automatisk økende ID-er
- commit() må kalles for å lagre endringer
- Flerlinjet SQL i triple quotes (''') for lesbarhet

CRUD-operasjoner

Create (INSERT):

# FARLIG – ikke gjør dette! (SQL injection-risiko)
navn = "Ole Olsen"
cursor.execute(f"INSERT INTO Elev (navn, klasse) VALUES ('{navn}', '3A')")

# RIKTIG – bruk parameteriserte spørringer:
cursor.execute(
    "INSERT INTO Elev (navn, klasse, epost) VALUES (?, ?, ?)",
    ("Ole Olsen", "3A", "ole@example.com")
)
conn.commit()

# Hent ID-en til den nye raden:
ny_id = cursor.lastrowid
print(f"Ny elev opprettet med ID: {ny_id}")

Read (SELECT):

# Hent én rad
cursor.execute("SELECT * FROM Elev WHERE elevID = ?", (1,))
elev = cursor.fetchone()
print(elev)  # Tuple: (1, 'Ole Olsen', '3A', 'ole@example.com')

# Hent alle rader
cursor.execute("SELECT navn, klasse FROM Elev ORDER BY navn")
alle_elever = cursor.fetchall()
for elev in alle_elever:
    print(f"{elev[0]} - {elev[1]}")

# Hent rad for rad (for store resultater)
cursor.execute("SELECT * FROM Elev")
for rad in cursor:
    print(rad)

Update (UPDATE):

# Oppdater én elev
cursor.execute(
    "UPDATE Elev SET klasse = ? WHERE elevID = ?",
    ("3B", 1)
)
conn.commit()
print(f"Endret {cursor.rowcount} rad(er)")

Delete (DELETE):

# Slett én elev
cursor.execute("DELETE FROM Elev WHERE elevID = ?", (1,))
conn.commit()
print(f"Slettet {cursor.rowcount} rad(er)")

SQL injection og sikkerhet

SQL injection er en av de farligste sikkerhetssårbarhetene i webapplikasjoner.

Farlig kode (ALDRI gjør dette):

# Brukerinput
bruker_input = input("Skriv navn: ")

# FARLIG: String formatting
query = f"SELECT * FROM Elev WHERE navn = '{bruker_input}'"
cursor.execute(query)

Hva kan gå galt?

Hvis brukeren skriver:

Ole' OR '1'='1

Blir spørringen:

SELECT * FROM Elev WHERE navn = 'Ole' OR '1'='1'

Dette returnerer ALLE elever fordi '1'='1' alltid er sant!

Verre: brukeren kan skrive:

'; DROP TABLE Elev; --

Dette kan slette hele tabellen!

RIKTIG måte (parameteriserte spørringer):

# Trygt: Python escaper verdien automatisk
bruker_input = input("Skriv navn: ")
cursor.execute(
    "SELECT * FROM Elev WHERE navn = ?",
    (bruker_input,)
)

Viktig: Bruk ALLTID ? for verdier, aldri string formatting (f"") eller konkatenering (+)!

Eksempel: Komplett CRUD-applikasjon

import sqlite3

class ElevDatabase:
    def __init__(self, db_fil='skole.db'):
        self.conn = sqlite3.connect(db_fil)
        self.cursor = self.conn.cursor()
        self._opprett_tabell()

    def _opprett_tabell(self):
        self.cursor.execute('''
            CREATE TABLE IF NOT EXISTS Elev (
                elevID INTEGER PRIMARY KEY AUTOINCREMENT,
                navn TEXT NOT NULL,
                klasse TEXT,
                epost TEXT UNIQUE
            )
        ''')
        self.conn.commit()

    def legg_til_elev(self, navn, klasse, epost):
        """Create - legg til ny elev"""
        try:
            self.cursor.execute(
                "INSERT INTO Elev (navn, klasse, epost) VALUES (?, ?, ?)",
                (navn, klasse, epost)
            )
            self.conn.commit()
            return self.cursor.lastrowid
        except sqlite3.IntegrityError:
            return None  # Epost finnes allerede

    def hent_alle_elever(self):
        """Read - hent alle elever"""
        self.cursor.execute("SELECT * FROM Elev ORDER BY navn")
        return self.cursor.fetchall()

    def hent_elev(self, elevID):
        """Read - hent én elev"""
        self.cursor.execute("SELECT * FROM Elev WHERE elevID = ?", (elevID,))
        return self.cursor.fetchone()

    def oppdater_elev(self, elevID, navn=None, klasse=None, epost=None):
        """Update - oppdater elev"""
        if navn:
            self.cursor.execute(
                "UPDATE Elev SET navn = ? WHERE elevID = ?",
                (navn, elevID)
            )
        if klasse:
            self.cursor.execute(
                "UPDATE Elev SET klasse = ? WHERE elevID = ?",
                (klasse, elevID)
            )
        if epost:
            self.cursor.execute(
                "UPDATE Elev SET epost = ? WHERE elevID = ?",
                (epost, elevID)
            )
        self.conn.commit()
        return self.cursor.rowcount > 0

    def slett_elev(self, elevID):
        """Delete - slett elev"""
        self.cursor.execute("DELETE FROM Elev WHERE elevID = ?", (elevID,))
        self.conn.commit()
        return self.cursor.rowcount > 0

    def lukk(self):
        self.conn.close()

# Bruk av klassen
if __name__ == "__main__":
    db = ElevDatabase()

    # Legg til elever
    id1 = db.legg_til_elev("Ole Olsen", "3A", "ole@example.com")
    id2 = db.legg_til_elev("Kari Hansen", "3B", "kari@example.com")

    # Hent alle
    print("Alle elever:")
    for elev in db.hent_alle_elever():
        print(elev)

    # Oppdater
    db.oppdater_elev(id1, klasse="3C")

    # Hent én
    print("\nEn elev:")
    print(db.hent_elev(id1))

    # Slett
    db.slett_elev(id2)

    db.lukk()

Row factory – få resultater som dictionaries

Som standard returnerer SQLite resultater som tuples. Vi kan endre dette:

import sqlite3

conn = sqlite3.connect('skole.db')

# Få resultater som dictionaries
conn.row_factory = sqlite3.Row

cursor = conn.cursor()
cursor.execute("SELECT * FROM Elev WHERE elevID = ?", (1,))
elev = cursor.fetchone()

# Nå kan vi bruke kolonnenavn:
print(elev['navn'])
print(elev['klasse'])

# Eller konvertere til dict:
elev_dict = dict(elev)
print(elev_dict)

conn.close()

Dette gjør koden mer leselig og mindre feilutsatt.

Feilhåndtering og transaksjoner

Try-except for database-feil:

import sqlite3

try:
    conn = sqlite3.connect('skole.db')
    cursor = conn.cursor()

    cursor.execute(
        "INSERT INTO Elev (navn, epost) VALUES (?, ?)",
        ("Ole", "ole@example.com")
    )
    conn.commit()

except sqlite3.IntegrityError as e:
    print(f"Integritetsfeil (f.eks. duplikat epost): {e}")

except sqlite3.OperationalError as e:
    print(f"Operasjonsfeil (f.eks. tabellen finnes ikke): {e}")

except sqlite3.Error as e:
    print(f"Database-feil: {e}")

finally:
    conn.close()

Transaksjoner:

En transaksjon er en sekvens av operasjoner som enten utføres helt eller ikke i det hele tatt.

try:
    conn = sqlite3.connect('skole.db')
    cursor = conn.cursor()

    # Start transaksjon (implisitt)
    cursor.execute("INSERT INTO Elev (navn) VALUES (?)", ("Ole",))
    cursor.execute("INSERT INTO Karakter (elevID, fag, karakter) VALUES (?, ?, ?)",
                   (cursor.lastrowid, "Matte", 5))

    # Hvis alt går bra:
    conn.commit()

except sqlite3.Error as e:
    # Hvis noe går galt, angre alle endringer:
    conn.rollback()
    print(f"Feil: {e}")

finally:
    conn.close()
sqlite3: Pythons innebygde modul for å jobbe med SQLite-databaser.

Cursor: Objekt som utfører SQL-kommandoer og henter resultater.

commit(): Lagrer endringer til databasen.

rollback(): Angrer endringer siden siste commit.

fetchone(): Henter én rad fra resultatet.

fetchall(): Henter alle rader fra resultatet.

Parameterisert spørring: SQL-spørring med ? for verdier, forhindrer SQL injection.

SQL injection: Sikkerhetssårbarhet der ondsinnet SQL-kode injiseres via brukerinput.

Transaksjon: Sekvens av operasjoner som utføres som en atomisk enhet.

📝Oppgave

Hvorfor er parameteriserte spørringer viktige?

📝Oppgave

Hva gjør conn.commit()?

📝Oppgave

Skriv en Python-funksjon søk_bøker(søkeord) som:
- Kobler til en database 'bibliotek.db'
- Søker etter bøker der tittelen inneholder søkeordet (case-insensitive)
- Returnerer en liste med tupler (tittel, forfatter, år)
- Bruker parameteriserte spørringer

Gitt tabell:

Bok (bokID, tittel, forfatter, utgivelsesår)

📝Oppgave

Forklar hvorfor denne koden er farlig:

navn = input("Skriv navn: ")
cursor.execute(f"DELETE FROM Elev WHERE navn = '{navn}'")
conn.commit()

Gi et eksempel på hva en ondsinnet bruker kunne skrive, og skriv en trygg versjon av koden.

📝Oppgave

Lag en klasse ProduktDatabase som håndterer en produktdatabase:

Produkt (produktID, navn, pris, antall)

Klassen skal ha metoder for:
- __init__(db_fil): Koble til og opprett tabell
- legg_til_produkt(navn, pris, antall): Legg til nytt produkt
- hent_alle_produkter(): Hent alle produkter
- oppdater_antall(produktID, endring): Endre antall (kan være negativt for salg)
- finn_lave_lagre(grense=10): Finn produkter med antall under grense

Bruk row_factory for dictionary-resultater.

📝Oppgave

// --- Samleoppgaver ---

Lag et komplett biblioteksystem med følgende funksjonalitet:

Tabeller:

Bok (ISBN PRIMARY KEY, tittel, forfatter, utgivelsesår, antall_eksemplarer)
Medlem (medlemsID PRIMARY KEY, navn, epost UNIQUE, registrert_dato)
Utlån (utlånID PRIMARY KEY, ISBN, medlemsID, utlånsdato, innleveringsfrist, innlevert_dato)

Oppgaver:
a) Lag en BibliotekDatabase-klasse som håndterer alle tabellene
b) Implementer metoder for:
- Registrere ny bok
- Registrere nytt medlem
- Låne ut en bok (sjekk at den er tilgjengelig)
- Levere inn en bok
- Finne alle aktive utlån for et medlem
- Finne medlemmer med forfalte lån (innleveringsfrist passert, ikke innlevert)
- Finn mest populære bøker (flest utlån totalt)
c) Lag et enkelt tekstbasert menygrensesnitt for å teste systemet
d) Bruk transaksjoner der det er nødvendig

Oppsummering

I dette kapittelet har du lært:

- sqlite3: Pythons modul for SQLite-databaser.
- Cursor og connection: utfører SQL og lagrer endringer.
- CRUD-operasjoner: INSERT, SELECT, UPDATE og DELETE.
- SQL injection: bruk parameteriserte spørringer for sikkerhet.
- Feilhaandtering og transaksjoner: trygg databasebruk.

Noekkelbegreper


BegrepForklaring
sqlite3Pythons innebygde SQLite-modul
SQL injectionAngrep via uvalidert input i SQL
Parameterisert spørringTrygg måte å sende verdier til SQL på

Oppgaver

Dette kapitlet er skrevet av Anthropics toppmodeller (Claude Opus og Claude Fable) og er foreløpig ikke manuelt gjennomgått — kvalitetskontrollen gjøres av uavhengige KI-agenter, og innmeldte feil rettes fortløpende. Funnet en feil? Meld fra, så retter vi den. Les mer om hvordan innholdet lages.