Werken met databanken
Doelstellingen
Op het einde van dit hoofdstuk moet je in staat zijn om:
- Een SELECT-query te doen op een sqlite-database.
- Een UPDATE-, DELETE- of INSERT-query te doen op een sqlute-database.
- Bij deze query's moet je correct gebruik maken van placeholders.
Documentatie
Hoe benader je een database in Python?
Het spreekt vanzelf dat we met Python ook een relationele database willen benaderen. Dat kan voor nagenoeg alle types relationele databases: SQLite, Postgresql, Mysql, Microsoft SQL Sever, Oracle, ...
De stappen die hiervoor gezet moeten worden zijn meestel dezelfde:
- Importeer de module die nodig is om dat type database te benaderen.
- Maak een connectie met de database in kwestie. Daarvoor heb je allerlei connectiegegevens nodig, zoals de locatie van de database (bijv. een IP-adres of een servernaam), de poort waarop de database kan bereikt worden, een gebruikersnaam en een wachtwoord.
- Vanuit die connectie creëer je een cursorobject. Dat is een object waarmee je queries kan uitvoeren.
- Je creëert je query als string.
- Je voert de query uit met je cursorobject.
- Je haalt het resultaat op (één object, de hele lijst, enzovoort).
- Je verwerkt het resultaat.
- Je sluit je connectie.
Voor wijzigingen (insert, update, delete) moet je er ook over waken dat je je niet blootstelt aan SQL-injectie.
Sqlite3
SQLite3 is een lightweight databasesysteem. Er is geen serverinstallatie nodig, je database bestaat uit één bestand en je benadert en beheert de database via software. Ondanks het feit dat SQLite weinig installatie nodig heeft, is dit toch een volwaardig relationeel databasesysteem dat een grote hoeveelheid gegevens kan bevatten.
SQLite is ingebouw in Python.
import sqlite3
Je kunt SQLite benaderen via een command line interface. Hiervoor tik je in je terminal het commando sqlite3 in gevolgd door de naam van de database die je wil openen.
$ sqlite3 chinook.db
Je kan dan de lijst van tabellen opvragen met het commando .tables, het schema van een tabel opvragen met .schema gevolgd door de naam van de tabel, sql-queries uitvoeren, importeren en exporten naar csv enzovoort. Die CLI-interface zal je nodig hebben wanneer je je database op een server wil beheren. Lees de documentatie van de CLI-interface hiervoor na.
Je kan ook je database op een eenvoudigere manier beheren met de grafische tool Db Browser for SQLite. Deze maakt ons het leven gemakkelijker.
Een retrieve-query uitvoeren
Hoe je een eenvoudige query uitvoer in sqlite kunnen we snel aantonen met een voorbeeld. We nemen als voor een query in de chinook-database.
import sqlite3 # stap 1: importeren
dbconnectie = sqlite3.connect("chinook.db") # stap 2: connectie maken
mijncursor = dbconnectie.cursor() # Stap 3: cursor maken
# Stap 4: query definiëren
mijnquery = "SELECT name FROM tracks WHERE lower(composer) like 'johny%'"
# Stap 5: query uitvoeren
mijncursor.execute(mijnquery)
# Stap 6: het resultaat ophalen. Je krijgt nu een lijst van tupels terug.
rows = mijncursor.fetchall()
# Stap 7: verwerk het resulaat
for row in rows:
pass # doe iets
# Stap 8: sluit de connectie
dbconnectie.close()
Je hebt naast het commando fetchall() ook het commando fetchone(). Deze geeft één tupel terug (of None als de query leeg was).
Je kun ook met het with-statement werken. Dan wordt de connectie automatisch gesloten. We zien in dit voorbeeld ook dat er een impliciet gebruik van de cursor is, door het execute-statement direct op de connectie uit te voeren.
import sqlite3
with sqlite3.connect("chinook.db") as dbconnectie:
resultaat = dbconnectie.execute("SELECT name FROM track")
print(resultaat.fetchall())
Een update-query uitvoeren met placeholders
Wanneer we een insert, update of delete-query uitvoeren, is het van essentieel belang dat we placeholders gebruiken om SQL-injectie te vermijden. Placeholders kunnen we ook gebruiken bij een SELECT-query. Je maakt je query hiermee parametriseerbaar.
Er bestaan verschillende manieren op placeholders te gebruiken. De eenvoudigste manier is dat je een vraagteken plaats op de plaats waar een waarde moet komen en dan een tuple toevoegt aan je query met de werkelijke waarden. Een voorbeeld kan dit illustreren:
import sqlite3
with sqlite3.connect("chinook.db") as dbconnectie:
mijncursor = dbconnectie.cursor()
query = """
UPDATE tracks set composer = "Mick Jagger, Keith Richards"
WHERE composer LIKE ? or composer LIKE ?
"""
# definieer nu een tupel met de waarden voor de vraagtekens
parameters = ("Mick Jagger/Keith Richards", "Keith Richards/Mick Jagger")
# Nu voer je de query uit met twee argumenten
mijncursor.execute(query, parameters)
# Vergeet geen commit te doen bij een wijziging, op het niveau van de connectie
dbconnectie.commit()
De meeste database operaties zullen we verpakken in een try-excerpt-blok om fouten op te vangen.
Andere systemen dan SQLite
Om andere systemen te benaderen moet je normaal gezien een module installeren. Dat doe je met het commando pip of pip3, bijv.
pip install psycopg3 # voor Postgresql
Daarna kan je die module importeren en gebruiken. Twee voorbeelden kunnen dit illustreren, één met Postgresql, en één voor Mysql.
Postgresql
import psycopg2
def fetch_albums_by_artist(artist_name):
# Connect to the PostgreSQL database
connection = psycopg2.connect(
dbname="chinook", # Change these connection parameters as needed
user="your_username",
password="your_password",
host="localhost",
port="5432"
)
# Cursor to execute queries
cursor = connection.cursor()
# Use a placeholder to insert the artist's name into the query
query = """
SELECT Album.Title
FROM Album
INNER JOIN Artist ON Album.ArtistId = Artist.ArtistId
WHERE Artist.Name = %s;
"""
# Execute the query
cursor.execute(query, (artist_name,))
# Fetch results
albums = cursor.fetchall()
# Close the cursor and connection
cursor.close()
connection.close()
return albums
if __name__ == "__main__":
artist = "AC/DC" # Just an example; replace with the desired artist name
result = fetch_albums_by_artist(artist)
for album in result:
print(album[0])
Merk op dat we hier geen vraagteken als placeholder gebruiken, maar %s.
Raadpleeg zeker de documentatie.
MySQL
Voor MySQL gebruiken we de module mysql-connector-python
pip install mysql-connector-python
import mysql.connector
def update_track_price(track_id, new_price):
# Connect to the MySQL database
connection = mysql.connector.connect(
host="localhost",
user="your_username",
password="your_password",
database="chinook"
)
# Cursor to execute queries
cursor = connection.cursor()
# Use placeholders for both track ID and the new price
query = """
UPDATE Track
SET UnitPrice = %s
WHERE TrackId = %s;
"""
# Execute the query
cursor.execute(query, (new_price, track_id))
# Commit changes
connection.commit()
# Close the cursor and connection
cursor.close()
connection.close()
if __name__ == "__main__":
track_to_update = 1 # Just an example; replace with the desired track ID
new_unit_price = 1.99 # Replace with the desired new price
update_track_price(track_to_update, new_unit_price)
print(f"Updated TrackId {track_to_update} to new price: ${new_unit_price}")
Datetime
We hebben het in deze cursus nog niet over het omgaan met datums en tijd gehad. Aangezien een database vaak datumaanduidingen bevat, is het een goed moment om het hierover te hebben.
Om met datum en tijd om te gaan gebruike we de module datetime.
Wanneer we het hebben over datums en tijd, moeten we een onderscheid maken tussen objecten die zich bewust zijn van een tijdszone en de objecten die zich daar niet van bewust zijn. Voor de eenvoud hebben we over de tweede soort objecten.
date
Een object van het type date stelt een datum voor, een jaar, een maand en een dag. We maken een datum door het jaar, de maand en de dag op te te geven.
from datetime import date
my_date = date(2023, 09, 28)
print(mydate)
# 2023-09-28
# vandaag opvragen en het jaar, de maand en dag afdrukken
vandaag = date.today()
print("We zijn in het jaar: ", vandaag.year)
print("We zijn in de maand: ", vandaag.month)
print("We zijn op de dag: ", vandaag.day)
time
We hebbeen ook de klasse time, die een tijdstip voorstel in uren, minuten, seconden en eventueel microseconden.
from datetime import time
# time(hour = 0, minute = 0, second = 0)
a = time()
print(a)
# time(hour, minute and second)
b = time(11, 34, 56)
print(b)
# time(hour, minute and second)
c = time(hour = 11, minute = 34, second = 56)
print(c)
# time(hour, minute, second, microsecond)
d = time(11, 34, 56, 234566)
print(d)
Dit geeft als output
a = 00:00:00
b = 11:34:56
c = 11:34:56
d = 11:34:56.234566
from datetime import time
a = time(20, 35, 53)
print("Uur =", a.hour) # 20
print("Minuten =", a.minute) # 34
print("Seconden =", a.second) # 56
print("Microseconden =", a.microsecond) # 0
datetime
De klasse datetime combineert de gegevens van date en time.
from datetime import datetime
a = datetime(2022, 12, 28, 23, 55, 59, 342380)
print("Year =", a.year)
print("Month =", a.month)
print("Hour =", a.hour)
print("Minute =", a.minute)
print("Timestamp =", a.timestamp())
Output:
year = 202
month = 12
day = 28
hour = 23
minute = 55
timestamp = 1511913359.34238
timedelta
Timedelta geeft dan het verschil tussen twee datums of twee tijdstippen aan.
from datetime import datetime, date
from datetime import datetime, date
# using date()
t1 = date(year = 2018, month = 7, day = 12)
t2 = date(year = 2017, month = 12, day = 23)
t3 = t1 - t2
print("t3 =", t3)
# using datetime()
t4 = datetime(year = 2018, month = 7, day = 12, hour = 7, minute = 9, second = 33)
t5 = datetime(year = 2019, month = 6, day = 10, hour = 5, minute = 55, second = 13)
t6 = t4 - t5
print("t6 =", t6)
Output:
t3 = 201 days, 0:00:00
t6 = -333 days, 1:14:20
Datum en tijd formatteren
Om datums of tijdstippen op een aangepaste manier weer te geven, hebben we de functie strftime(). Deze functies is een onderdeel van de klassen date, time en datetime en heeft een formaat als argument.
Een voorbeeld kan dit duidelijk maken:
from datetime import date
vandaag = date.today()
print(vandaag.strftime("%A %d %B %Y"))
Output:
Thursday, 28 September 2023
De formatcodes vind je in de documentatie.
Omgekeerd kan je met de functie strptime() uit een string een datum of tijd afleiden.
from datetime import datetime
tijdstip = datetime.strptime('31/01/22 23:59:59.999999', '%d/%m/%y %H:%M:%S.%f')
# 31 januari 2022 23:59
Als we datum in het Nederlands willen weergeven zullen we gebruik maken van de module locale:
import locale
from datetime import date
locale.setlocale(locale.LC_ALL, 'nl_BE.UTF-8')
vandaag = date.today()
print(vandaag.strftime("%A %d %B %Y"))
# output: donderdag 28 september 2023