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

Databasar i Python med SQLite

No skal vi kombinere Python-kunnskapane dine med databasekompetansen. SQLite er ein lettvekts database som kjem innebygd i Python – perfekt for å lage lokale applikasjonar, prototypar og mindre system.

I dette kapittelet lærer du:
- Kople Python til ein SQLite-database
- Utføre CRUD-operasjonar (Create, Read, Update, Delete)
- Bruke parameteriserte spørjingar for å unngå SQL injection
- Handtere feil og transaksjonar
- Byggje praktiske databaseapplikasjonar

Kople til database med sqlite3

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

Grunnleggjande 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 tabellar

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 punkt:
- IF NOT EXISTS hindrar feil dersom tabellen allereie finst
- AUTOINCREMENT genererer automatisk aukande ID-ar
- commit() må kallast for å lagre endringar
- Flerlinja SQL i triple quotes (''') for lesbarheit

CRUD-operasjonar

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 tryggleik

SQL injection er ein av dei farlegaste tryggleikssårbarheitene i webapplikasjonar.

Farleg kode (ALDRI gjer dette):

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

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

Kva kan gå gale?

Dersom brukaren skriv:

Ole' OR '1'='1

Blir spørjinga:

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

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

Verre: brukaren kan skrive:

'; DROP TABLE Elev; --

Dette kan slette heile tabellen!

RETT måte (parameteriserte spørjingar):

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

Viktig: Bruk ALLTID ? for verdiar, 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å resultat som dictionaries

Som standard returnerer SQLite resultat 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 gjer koden meir leseleg og mindre feilutsett.

Feilhandtering og transaksjonar

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()

Transaksjonar:

Ein transaksjon er ein sekvens av operasjonar som anten blir utførte heilt eller ikkje i det heile.

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: Python sin innebygde modul for å jobbe med SQLite-databasar.

Cursor: Objekt som utfører SQL-kommandoar og hentar resultat.

commit(): Lagrar endringar til databasen.

rollback(): Angrar endringar sidan siste commit.

fetchone(): Hentar éi rad frå resultatet.

fetchall(): Hentar alle rader frå resultatet.

Parameterisert spørjing: SQL-spørjing med ? for verdiar, hindrar SQL injection.

SQL injection: Tryggleikssårbarheit der vondsinna SQL-kode blir injisert via brukarinput.

Transaksjon: Sekvens av operasjonar som blir utførte som ei atomisk eining.

📝Oppgave

Kvifor er parameteriserte spørjingar viktige?

📝Oppgave

Kva gjer conn.commit()?

📝Oppgave

Skriv ein Python-funksjon søk_bøker(søkeord) som:
- Koplar til ein database 'bibliotek.db'
- Søkjer etter bøker der tittelen inneheld søkeordet (case-insensitive)
- Returnerer ei liste med tuplar (tittel, forfattar, år)
- Brukar parameteriserte spørjingar

Gitt tabell:

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

📝Oppgave

Forklar kvifor denne koden er farleg:

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

Gi eit eksempel på kva ein vondsinna brukar kunne skrive, og skriv ein trygg versjon av koden.

📝Oppgave

Lag ein klasse ProduktDatabase som handterer ein produktdatabase:

Produkt (produktID, navn, pris, antall)

Klassen skal ha metodar for:
- __init__(db_fil): Kople til og opprett tabell
- legg_til_produkt(navn, pris, antall): Legg til nytt produkt
- hent_alle_produkter(): Hent alle produkt
- oppdater_antall(produktID, endring): Endre tal (kan vere negativt for sal)
- finn_lave_lagre(grense=10): Finn produkt med tal under grense

Bruk row_factory for dictionary-resultat.

📝Oppgave

// --- Samleoppgaver ---

Lag eit komplett biblioteksystem med følgjande funksjonalitet:

Tabellar:

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)

Oppgåver:
a) Lag ein BibliotekDatabase-klasse som handterer alle tabellane
b) Implementer metodar for:
- Registrere ny bok
- Registrere nytt medlem
- Låne ut ei bok (sjekk at ho er tilgjengeleg)
- Levere inn ei bok
- Finne alle aktive utlån for eit medlem
- Finne medlemmer med forfalne lån (innleveringsfrist passert, ikkje innlevert)
- Finn mest populære bøker (flest utlån totalt)
c) Lag eit enkelt tekstbasert menygrensesnitt for å teste systemet
d) Bruk transaksjonar der det er nødvendig

Oppsummering

I dette kapittelet har du lært:

- sqlite3: Python sin modul for SQLite-databasar.
- Cursor og connection: utfører SQL og lagrar endringar.
- CRUD-operasjonar: INSERT, SELECT, UPDATE og DELETE.
- SQL injection: bruk parameteriserte spørjingar for tryggleik.
- Feilhandtering og transaksjonar: trygg databasebruk.

Nøkkelbegrep


BegrepForklaring
sqlite3Python sin innebygde SQLite-modul
SQL injectionAngrep via uvalidert input i SQL
Parameterisert spørjingTrygg måte å sende verdiar 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.