Datenbanken

Für einfache Datenbankanwendungen mit Python bietet sich sqlite an, welches eine abgespeckte Version einer SQL-Datenbank (mySQL, mariaDB, ...) ist. Dies hat den Vorteil, dass man das Problem der Zugriffsrechte etwas umgehen kann. So erfordert beispielsweise die Installation und der Betrieb einer Datenbank in der ZEDAT, dass man sich auf dem Login-Rechner der ZEDAT befindet oder den ZUgriff von Webseiten, die auf einem User-Account liegen, vornimmt. sqlite unterstützt die folgenden Datentypen: INTEGER, REAL, TEXT, BLOB und NULL. Theoretisch auch noch DATE, wobei dieses aber intern als TEXT gespeichert wird. Datumsoperationen müssen daher auf Python-Ebene erfolgen und nicht mit Datenbankbefehlen.

Zuerst wird die Datenbank mit einer entsprechenden Struktur erstellt. Sollte die Datenbank in einem Unterverzeichnis liegen, so muss dieses vorher erstellt werden, was allerdings auch aus Python heraus geschehen könnte.

Die sql-Anweisung muss in den Mehrzeilenkommentar """ .... """ gesetzt werden, wenn die Parameter über mehrere Zeilen gehen. Grundsätzlich besteht der Ablauf aus immer den gleichen Schritten:

  • Verbindung zur Datenbank herstellen (connect)
  • sql-Objekt zum Zugriff auf die Datenbank erstellen (cursor)
  • sql-Query übergeben (execute)
  • Änderungen an der Datenbank endgültig bestätigen (commit)
  • Schließen der Datenbankverbindung (Ordnung muss sein)
  • import sqlite3    # braucht Modul sqlite3-api
    verbindung = sqlite3.connect("kursliste.db")
    sql = verbindung.cursor()
    sql_anweisung = """ CREATE TABLE IF NOT EXISTS personen ( 
            vorname VARCHAR(20), nachname VARCHAR(30), studienfach VARCHAR(30),
            eintrittFU DATE, matrikel VARCHAR(10));"""
    
    sql.execute(sql_anweisung)
    verbindung.commit()
    verbindung.close()
    
    # Zur Kontrolle kann die Dateiliste ausgegeben werden:
    #import os
    #files = [f for f in os.listdir('.') if os.path.isfile(f)]
    #for f in files:
    #  print(f)

    Nach Ausführen dieses Programmes existiert im angebenen Verzeichnis die leere Datenbank:

    # Hinweis
    voss@h2710458:~/tmp/$ ls -la
    insgesamt 16
    drwxrwxr-x  2 voss voss 4096 Feb  1 21:20 .
    drwxrwxr-x 22 voss voss 4096 Feb  1 21:20 ..
    -rw-r--r--  1 voss voss 8192 Feb  1 21:20 kursliste.db

    Möchte man diese komplett löschen, so geht das auch aus Python heraus. Damit können alle folgenden Beispiele wieder mit einer leeren Datenbank beginnen.

    import os # operating system
    try:
      os.remove("/tmp/kursliste.db")
    except OSError as err:  
      print ("Fehler: %s - %s." % (err.filename, err.strerror))
    else:
      print("File removed successfully")

    Wenn Sie dieses selbst ausprobieren wollen, nehmen Sie einen Namen für die Datenbank, der eindeutig ist, ansonsten wird die Datenbank sofort wieder überschrieben, wenn jemand anderes das Programm laufen lässt. Ein sinnvoller Name wäre beispielsweise: kurs-voss.db. Dann erkennt jeder, was passieren dürfte, wenn er denselben Namen verwendet. Das folgende Beispiel fügt einige Einträge in die Datenbank ein, wobei hier noch eine ziemlich umständliche Methode gewählt wurde.

    import sqlite3    # braucht Modul sqlite3-api
    verbindung = sqlite3.connect("/tmp/kursliste2.db")
    sql = verbindung.cursor()
    
    sql_anweisung = """ CREATE TABLE IF NOT EXISTS personen ( 
            vorname VARCHAR(20), nachname VARCHAR(30), studienfach VARCHAR(30),
            eintrittFU DATE, matrikel VARCHAR(10));"""
    sql.execute(sql_anweisung)
    
    sql_anweisung = "INSERT INTO personen VALUES ('Erasmus von', 'Rotterdam', 'Politik', '12.10.1713', '1234')"
    sql.execute(sql_anweisung)
    sql_anweisung = "INSERT INTO personen VALUES ('Herbert', 'Voß', 'Informatik', '1.10.1975', '12341234')"
    sql.execute(sql_anweisung)
    sql_anweisung = "INSERT INTO personen VALUES ('Johnny', 'Mauser', 'Biologie', '2.3.2001', '7643876')"
    sql.execute(sql_anweisung)
    verbindung.commit() # Namen sollten jetzt definitiv gespeichert sein
    
    print("Ausgabe der Namen:")
    sql.execute ("SELECT * FROM personen")
    db_liste = sql.fetchall()
    print(db_liste)
    verbindung.close()

    Die Einträge für die Datenbank können auch zwischendurch in Python-Variablen gespeichert werden, um sie dann an die Datenbank zu übergeben. Dies geschieht über einen zweiten Parameter, der dafür sorgt, dass die Fragezeichen als Platzhalter durch die richtigen Werte ersetzt werden.

    import sqlite3    # braucht Modul sqlite3-api
    verbindung = sqlite3.connect("/tmp/kursliste.db")
    sql = verbindung.cursor()
    
    sql_anweisung = """ CREATE TABLE IF NOT EXISTS personen ( 
            vorname VARCHAR(20), nachname VARCHAR(30), studienfach VARCHAR(30),
            eintrittFU DATE, matrikel VARCHAR(10));"""
    sql.execute(sql_anweisung)
    
    Vorname = "Heinrich"
    Nachname = "Böll"
    Fach = "Literaturwissenschaft"
    Eintritt = "23.11.1975"
    Matrikel = "1236547"
    sql.execute("INSERT INTO personen VALUES (?,?,?,?,?)", (Vorname, Nachname, Fach, Eintritt, Matrikel) )
    
    print("Ausgabe der Namen:")
    sql.execute ("SELECT * FROM personen")
    db_liste = sql.fetchall()
    for zeile in db_liste:
      print(zeile)
    verbindung.commit()
    verbindung.close()

    Es besteht auch die Möglichkeit eine komplette Liste zu übergeben, wobei diese logischerweise die Struktur der Datenbank abbilden muss. Anstell von sql.execute wird dann der Befehl sql.executemany gewählt:

    import sqlite3    # braucht Modul sqlite3-api
    verbindung = sqlite3.connect("/tmp/kursliste.db")
    sql = verbindung.cursor()
    
    sql_anweisung = """ CREATE TABLE IF NOT EXISTS personen ( 
            vorname VARCHAR(20), nachname VARCHAR(30), studienfach VARCHAR(30),
            eintrittFU DATE, matrikel VARCHAR(10));"""
    sql.execute(sql_anweisung)
    
    studies = [('Georg Wilhelm Friedrich', 'Hegel', 'Philosophie', '27.08.1795', '187'), 
               ('Johann Christian Friedrich', 'Hölderlin', 'Germanistik', '20.03.1801', '96'), 
               ('Rudolf Ludwig Carl', 'Virchow', 'Medizin', '13.10.1845', '286')]
    sql.executemany("INSERT INTO personen VALUES (?,?,?,?,?)", studies )
    
    print("Ausgabe der Namen:")
    sql.execute ("SELECT * FROM personen")
    db_liste = sql.fetchall()
    for zeile in db_liste:
      print(zeile)
    verbindung.commit()
    verbindung.close()

    Die einzelnen SQL-Befehle sind hier zwar nicht Inhalt des Kurses, dennoch sollen einige Möglichkeiten der Ausgabe gezeigt werden.

    import sqlite3    # braucht Modul sqlite3-api
    verbindung = sqlite3.connect("/tmp/kursliste.db")
    sql = verbindung.cursor()
    
    sql_anweisung = """ CREATE TABLE IF NOT EXISTS personen ( 
            vorname VARCHAR(20), nachname VARCHAR(30), studienfach VARCHAR(30),
            eintrittFU DATE, matrikel VARCHAR(10));"""
    sql.execute(sql_anweisung)
    
    sql.execute ("SELECT nachname, vorname FROM personen")
    db_liste = sql.fetchall()
    print("Ausgabe der Namen:")
    for zeile in db_liste:
      print(zeile)
    print()
    sql.execute("SELECT nachname, vorname, studienfach FROM personen ORDER BY nachname")
    db_liste = sql.fetchall()
    for zeile in db_liste:
      print(zeile)
    print()
    sql.execute("SELECT nachname, vorname, studienfach FROM personen ORDER BY nachname DESC")# ASC ist Vorgabe
    db_liste = sql.fetchall()
    for zeile in db_liste:
      print(zeile)
    verbindung.close()

    Als Anwendungstest soll eine Datenbank mit allen Passagieren der gesunkenen Titanic erstellt werden. Eine kommaseparierte Liste der Passagiere steht zur Verfügung. Die Liste enthält dabei folgende Einträge (hier die ersten beiden Zeilen der Liste):

    PassengerId,Survived,Pclass,Name,Sex,Age,SibSp,Parch,Ticket,Fare,Cabin,Embarked
    1,0,3,"Braund, Mr. Owen Harris",male,22,1,0,A/5 21171,7.25,,S
    

    Die Datenbank kann jederzeit erweitert, gekürzt oder geändert werden. Die Erweiterung entspricht dem obogen Vorgehen und das Löschen wird in der Regel an eine Bedingung geknppft, um nicht versehentlich zu viele oder gar alle Einträge zu löschen. Im folgenden Beispiel wird nur der Vorname geändert, wenn der Nachname "Rotterdam" lautet:

    import sqlite3    # braucht Modul sqlite3-api
    verbindung = sqlite3.connect("/tmp/kursliste3.db")
    sql = verbindung.cursor()
    
    sql_anweisung = """ CREATE TABLE IF NOT EXISTS personen ( 
            vorname VARCHAR(20), nachname VARCHAR(30), studienfach VARCHAR(30),
            eintrittFU DATE, matrikel VARCHAR(10));"""
    sql.execute(sql_anweisung)
    
    sql.execute("SELECT nachname, vorname, studienfach FROM personen ORDER BY nachname DESC")# ASC ist Vorgabe
    db_liste = sql.fetchall()
    print("Namensliste vorher:")
    for zeile in db_liste:
      print(zeile)
    
    nachname = "Rotterdam"
    vorname  = "Desiderius Erasmus von"#  war nur Erasmus von
    
    sql.execute("UPDATE personen SET vorname=? WHERE nachname=?", (vorname, nachname))
    sql.execute("SELECT nachname, vorname, studienfach FROM personen WHERE nachname='Rotterdam'")# ASC ist Vorgabe
    db_liste = sql.fetchall()
    
    print()
    print("Namensliste nachher:")
    for zeile in db_liste:
      print(zeile)
    verbindung.commit()
    verbindung.close()

    Insbesondere beim Löschen von Datenbankeinträgen ist es ratsam Vorsicht walten zu lassen, denn eine falsche Option kann zum Verlust der gesamten Datanbank führen. Ein Backup ist immer sinnvoll! In der Regel wird man die zu löschenden Einträge dem Schlüsselwort "WHERE" übergeben. Das folgende Beispiel löscht "Johnny Mauser" aus der Tabelle. Aus Gründen der soeben erläuterten Vorsicht, wird sowohl nach dem Vornamen als Nachnamen gefragt, obwohl das für diese Demonstrationstabelle uninteressant wäre, denn es gibt nur einen Johnny und einern Mause ...

    import sqlite3    # braucht Modul sqlite3-api
    verbindung = sqlite3.connect("/tmp/kursliste.db")
    sql = verbindung.cursor()
    
    sql_anweisung = """ CREATE TABLE IF NOT EXISTS personen ( 
            vorname VARCHAR(20), nachname VARCHAR(30), studienfach VARCHAR(30),
            eintrittFU DATE, matrikel VARCHAR(10));"""
    sql.execute(sql_anweisung)
    
    sql.execute("SELECT nachname, vorname, studienfach FROM personen ORDER BY nachname DESC")# ASC ist Vorgabe
    db_liste = sql.fetchall()
    print("Namensliste vorher:")
    for zeile in db_liste:
      print(zeile)
    
    sql.execute("DELETE FROM personen WHERE nachname='Mauser' AND vorname='Johnny'")
    sql.execute("SELECT * FROM personen ORDER BY nachname")
    db_liste = sql.fetchall()
    
    print()
    print("Namensliste nachher:")
    for zeile in db_liste:
      print(zeile)
    verbindung.commit()
    verbindung.close()

    Kursbeispiel:

    import os
    import sqlite3
    import sys
    
    def switch(argument):
        if argument == "a":
            ausgabe()
        elif argument == "e":
            eingabe()
        elif argument == "l":
            loeschen()
        elif argument == "d":
            del_datenbank()
    
    def del_datenbank():
        pass
    
    def ausgabe():
        print("Ausgabe der Namen:")
        sql.execute ("SELECT * FROM personen")
        db_liste = sql.fetchall()
        for zeile in db_liste:
            print(zeile)
    
    def oeffneDB(db):
        eingabe = input("Bitte Datenbank angeben ["+db+"]: ")
        if eingabe != "":
          db = eingabe
        """
        if not os.path.exists("daten\kursliste.db"):
            print("Datei existiert nicht!")
            eingabe = input("Erstellen? [J]: ")
        """
        verbindung = sqlite3.connect(db)
        sql = verbindung.cursor()
        sql_anweisung = """ CREATE TABLE IF NOT EXISTS personen (
            vorname VARCHAR(20), nachname VARCHAR(30), studienfach
    VARCHAR(30));"""
        sql.execute(sql_anweisung)
        return sql, verbindung
    
    def loeschen():
        pass
    
    def eingabe():
        while True:
            NN = input("Nachname: ")
            if NN == "":
                return
            VN = input("Vorname : ")
            SF = input("Fach    : ")
            sql.execute("INSERT INTO personen VALUES (?,?,?)", (NN, VN, SF) )
    
    
    def waehle():
        wahl = input("Auswahl: E)ingabe, A)ausgabe, Eintrag L)öschen, D)B
    löschen, Q)uit")
        return wahl.lower()
    
    sql, verbindung = oeffneDB("daten/kursliste.db")
    
    while True:
      wahl = waehle()
      print(wahl)
      if wahl != "q":
        switch(wahl)
      else:
        break
    
    
    verbindung.commit()
    verbindung.close()

    Beispiele

    Beispiel 1 (Grafische Eingabefenster)

    from tkinter import *
    fields = 'Vorname','Nachname','Straße','PLZ','Wohnort','Telefon','e-Mail'
    entries = {}
    
    def fetch():
        for field in fields:
            print ('Feld %s => "%s"' % (field,entries[field].get()))  # hole Text
    
    def makeform(root, fields):
        for field in fields:
            textStr = StringVar()
            textStr.set(field)
            row = Frame(root)                          # eine neue Zeile
            lab = Label(row, width=15, text=field)     # label, entry
            ent = Entry(row,text=textStr)
            row.pack(side=TOP, fill=X)                 # packe row on top
            lab.pack(side=LEFT)
            ent.pack(side=RIGHT, expand=YES, fill=X)   # horizontal streckem
            entries[field] = textStr
    
    if __name__ == '__main__':
        root = Tk()
        root.title('Eingabemaske')
        makeform(root, fields)
        Button(root, text='nächster Eintrag',command=fetch).pack(side=LEFT)
        Button(root, text='Ende',command=quit).pack(side=RIGHT)
        root.mainloop()

    Beispiel 2 (Webbasierte Adressdatenbank)

    Muss als adressen.cgi auf dem ZEDAT-Rechner gespeichert werden.

    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
    
    import os
    import cgi
    import cgitb; 
    cgitb.enable()
    #import psycopg2
    import sqlite3
    #import html
    #import sys
    
    print("Content-Type: text/html; charset=utf-8")
    print()
    
    #print(sys.stdout.encoding)
    
    fs = cgi.FieldStorage()
    
    daten = False
    delete = None
    if fs:   # Daten vorhanden?
      vorname   = fs.getvalue("vorname", None)
      nachname  = fs.getvalue("nachname", None)
      strasse   = fs.getvalue("strasse", None)
      plz       = fs.getvalue("plz", None)
      ort       = fs.getvalue("ort", None)
      email     = fs.getvalue("email", None)
      info      = fs.getvalue("info", None)
      delete    = fs.getvalue("delete",None)
      if delete == None:
        daten  = True
    
    # Connection öffnen
    verbindung = sqlite3.connect("adressen.db")
    #verbindung.text_factory = str
    sql = verbindung.cursor()
    sql_anweisung = """ CREATE TABLE IF NOT EXISTS adressen (
        no VARCHAR(10),
        vorname VARCHAR(50),
        nachname VARCHAR(50),
        strasse VARCHAR(50),
        plz VARCHAR(10),
        ort VARCHAR(50),
        email VARCHAR(50),
        info VARCHAR(300)
        );"""
    
    sql.execute(sql_anweisung)
    
    if delete == None:
      if os.path.exists("adressen.id"):
        idFile = open('adressen.id', 'r')  
        maxNo = int(idFile.readline().rstrip('\r\n'))
        idFile.close()
        os.remove("adressen.id")
        maxNo += 1
      else:
        maxNo = 1
      idFile = open('adressen.id','w')  
      idFile.write(str(maxNo))
      idFile.close()
    
    # Neuen Datensatz anlegen
    if daten == True:
      sql.execute("INSERT INTO adressen VALUES (?,?,?,?,?,?,?,?)", (maxNo, vorname, nachname, strasse, plz, ort, email, info)) 
      verbindung.commit()
    elif delete != None:
      sql_anweisung = ('DELETE FROM adressen WHERE no='+delete+";")
      sql.execute(sql_anweisung)
    
    # Meldung zurück geben
    print('''
        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
        <html>
        <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8">
        <title>Adressdatenbank</title>
        </head>
        <body> ''')
    if daten:
      print("<h1>Fertig</h1> <p>Die neue Adresse wurde erstellt.</p> <p>Die Liste:</p>")
    else:
      print("<h1>Aktuelle Datenbank</h1> <p></p>")
    
    #print("<p>",vorname, nachname, strasse, plz, ort, email, info,"</p>\n")
    print('<form action="adressen.cgi">')
    print('<table border="1"><tr><th>ID</th><th>Vorname</th><th>Nachname</th><th>Straße</th><th>PLZ</th><th>Ort</th><th>E-Mail</th><th>Info</th></tr>')
    sql.execute("SELECT * FROM adressen")
    #print("Erstelle Liste")
    db_list = sql.fetchall()
    
    #print(db_list)
    for line in db_list:
        print('<tr><td>',end="")
        print('''<input type="button" name="loeschen" style="BACKGROUND-COLOR:#555555; COLOR:white;" value="''',end="")
        print(line[0],end="")
        print('''" onClick="location.href='adressen.cgi?delete= ''',end="")
        print(line[0],end="")
        print(""" ';"></td>""")
        for column in line[1:]:
            print('<td>',column,'</td>')
        print("</tr>\n")
    
    print('''
    </table>
    </form>
    <p></p>
    <p>Zum Löschen auf die entsprechende Nummer klicken! Vorsicht! Es erfolgt keine Nachfrage!!!</p>
    
    <p><a href="adressen.html">Zurück zur Eingabe</a></p>
    </body>
    </html>''')
    
    # Connection schließen
    verbindung.commit()
    verbindung.close()

    Muss als adressen.html auf dem ZEDAT-Rechner gespeichert werden.

    # Hinweis
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Kleine Adressdatenbank</title>
    </head>
    <body>
    
    <h1>Füllen Sie bitte folgende Felder aus</h1>
    
    
    <form action="adressen.cgi" accept-charset="utf-8">
      <table border="0" cellpadding="5" cellspacing="0" bgcolor="#E0E0E0">
        <tr>
          <td align="right">Vorname:</td>
          <td colspan="4"><input name="vorname" type="text" size="30" maxlength="30"></td>
        </tr>
        <tr>
          <td align="right">Nachname:</td>
          <td colspan="4"><input name="nachname" type="text" size="40" maxlength="50" accept-charset="utf-8"></td>
        </tr>
    
        <tr>
          <td align="right">Straße:</td>
          <td colspan="4"><input name="strasse" type="text" size="30" maxlength="40" accept-charset="utf-8"></td>
        </tr>
        <tr>
          <td align="right">Postleitzahl:</td>
          <td><input name="plz" type="text" size="6" maxlength="10"><td>
          <td align="right">Ort:</td>
          <td><input name="ort" type="text" size="40" maxlength="50"></td>
        </tr>
        <tr>
          <td align="right">E-mail Adresse:</td>
          <td colspan="4"><input name="email" type="text" size="30" maxlength="40"></td>
        </tr>
        <tr>
          <td align="right" valign="top">Zusätzliche Informationen:</td>
          <td colspan="4"><textarea name="info" rows="10" cols="50" value="--"></textarea></td>
        </tr>
        <tr>
          <td align="right">Daten:</td>
          <td colspan="4">
            <input type="submit" value=" Absenden ">
            <input type="reset" value=" Abbrechen">
          </td>
        </tr>
      </table>
    </form>
    
    </body>
    </html>
    

    Die Datei adressen2.cgi (als zip)

    Beispiel 3 (Webserver)

    Server.zip herunterladen und entpacken. Danach serv.py starten und im Webbrowser http://localhost:8080 aufrufen.


    Nächste Einheit: 10 OOP