Database
 sql >> Datenbank >  >> RDS >> Database

Umgang mit SQL-Datenbanken mit PyQt:Die Grundlagen

Das Erstellen von Anwendungen, die eine SQL-Datenbank verwenden, ist eine ziemlich häufige Programmieraufgabe. SQL-Datenbanken sind überall und werden in Python hervorragend unterstützt. Bei der GUI-Programmierung bietet PyQt robuste und plattformübergreifende SQL-Datenbankunterstützung, mit der Sie Ihre Datenbanken konsistent erstellen, verbinden und verwalten können.

Die SQL-Unterstützung von PyQt lässt sich vollständig in die Model-View-Architektur integrieren, um Sie beim Erstellen von Datenbankanwendungen zu unterstützen.

In diesem Tutorial erfahren Sie, wie Sie:

  • Verwenden Sie die SQL-Unterstützung von PyQt sich zuverlässig mit einer Datenbank zu verbinden
  • Führen Sie SQL-Abfragen aus auf einer Datenbank mit PyQt
  • Verwenden Sie die Model-View-Architektur von PyQt in Datenbankanwendungen
  • Anzeigen und Bearbeiten von Daten mit verschiedenen PyQt-Widgets

Die Beispiele in diesem Tutorial erfordern Grundkenntnisse der SQL-Sprache, insbesondere des Datenbankverwaltungssystems SQLite. Einige Vorkenntnisse in der GUI-Programmierung mit Python und PyQt sind ebenfalls hilfreich.

Kostenloser Bonus: 5 Gedanken zur Python-Beherrschung, ein kostenloser Kurs für Python-Entwickler, der Ihnen den Fahrplan und die Denkweise zeigt, die Sie benötigen, um Ihre Python-Kenntnisse auf die nächste Stufe zu heben.


PyQt mit einer SQL-Datenbank verbinden

Eine Anwendung mit einer relationalen Datenbank zu verbinden und die Anwendung dazu zu bringen, die in dieser Datenbank gespeicherten Daten zu erstellen, zu lesen, zu aktualisieren und zu löschen, ist eine gängige Aufgabe bei der Programmierung. Relationale Datenbanken sind im Allgemeinen in einer Reihe von Tabellen organisiert , oder Beziehungen . Eine bestimmte Zeile in einer Tabelle wird als Datensatz bezeichnet oder Tupel , und eine Spalte wird als Attribut bezeichnet .

Hinweis: Der Begriff Feld wird häufig verwendet, um ein einzelnes Datenelement zu identifizieren, das in einer Zelle eines bestimmten Datensatzes in einer Tabelle gespeichert ist. Andererseits der Begriff Feldname wird verwendet, um den Namen einer Spalte zu identifizieren.

Jede Spalte speichert eine bestimmte Art von Informationen, z. B. Namen, Daten oder Zahlen. Jede Zeile stellt einen Satz eng verwandter Daten dar, und jede Zeile hat die gleiche allgemeine Struktur. Beispielsweise repräsentiert in einer Datenbank, die Daten über die Mitarbeiter eines Unternehmens speichert, eine bestimmte Zeile einen einzelnen Mitarbeiter.

Die meisten relationalen Datenbanksysteme verwenden SQL (Structured Query Language) zum Abfragen, Bearbeiten und Verwalten der in der Datenbank enthaltenen Daten. SQL ist eine deklarative und domänenspezifische Programmiersprache, die speziell für die Kommunikation mit Datenbanken entwickelt wurde.

Relationale Datenbanksysteme und SQL sind heute weit verbreitet. Sie finden mehrere verschiedene Datenbankverwaltungssysteme wie SQLite, PostgreSQL, MySQL, MariaDB und viele andere. Sie können Python mithilfe einer dedizierten Python-SQL-Bibliothek mit jedem dieser Datenbanksysteme verbinden.

Hinweis: Obwohl die integrierte SQL-Unterstützung von PyQt die bevorzugte Option zum Verwalten von SQL-Datenbanken in PyQt ist, können Sie auch jede andere Bibliothek verwenden, um die Datenbankverbindung zu handhaben. Einige dieser Bibliotheken beinhalten SQLAlchemy, Pandas, SQLite und so weiter.

Die Verwendung einer anderen Bibliothek zur Verwaltung Ihrer Datenbanken hat jedoch einige Nachteile. Sie können die Integration zwischen den SQL-Klassen von PyQt und der Model-View-Architektur nicht nutzen. Außerdem fügen Sie Ihrer Anwendung zusätzliche Abhängigkeiten hinzu.

Wenn es um die GUI-Programmierung mit Python und PyQt geht, bietet PyQt einen robusten Satz von Klassen für die Arbeit mit SQL-Datenbanken. Diese Klassen werden Ihr bester Verbündeter sein, wenn Sie Ihre Anwendung mit einer SQL-Datenbank verbinden müssen.

Hinweis: Leider enthält die offizielle Dokumentation von PyQt5 einige unvollständige Abschnitte. Um dies zu umgehen, können Sie sich die PyQt4-Dokumentation, die Qt For Python-Dokumentation oder die Original-Qt-Dokumentation ansehen. In diesem Tutorial führen Sie einige Links zur ursprünglichen Qt-Dokumentation, die in den meisten Fällen eine bessere Informationsquelle darstellt.

In diesem Lernprogramm lernen Sie die Grundlagen zur Verwendung der SQL-Unterstützung von PyQt zum Erstellen von GUI-Anwendungen, die zuverlässig mit relationalen Datenbanken interagieren, um Daten zu lesen, zu schreiben, zu löschen und anzuzeigen.


Datenbankverbindung erstellen

Das Verbinden Ihrer Anwendungen mit einer physischen SQL-Datenbank ist ein wichtiger Schritt bei der Entwicklung von Datenbankanwendungen mit PyQt. Um diesen Schritt erfolgreich durchzuführen, benötigen Sie einige allgemeine Informationen darüber, wie Ihre Datenbank eingerichtet ist.

Beispielsweise müssen Sie wissen, auf welchem ​​Datenbankverwaltungssystem Ihre Datenbank aufgebaut ist, und Sie benötigen möglicherweise auch einen Benutzernamen, ein Kennwort, einen Hostnamen usw.

In diesem Lernprogramm verwenden Sie SQLite 3, ein gut getestetes Datenbanksystem mit Unterstützung auf allen Plattformen und minimalen Konfigurationsanforderungen. Mit SQLite können Sie direkt in Datenbanken auf Ihrer lokalen Festplatte lesen und schreiben, ohne dass ein separater Serverprozess erforderlich ist. Das macht es zu einer benutzerfreundlichen Option zum Erlernen der Entwicklung von Datenbankanwendungen.

Ein weiterer Vorteil der Verwendung von SQLite ist, dass die Bibliothek mit Python und auch mit PyQt geliefert wird, sodass Sie nichts anderes installieren müssen, um damit zu arbeiten.

In PyQt können Sie eine Datenbankverbindung erstellen, indem Sie die QSqlDatabase verwenden Klasse. Diese Klasse stellt eine Verbindung dar und bietet eine Schnittstelle für den Zugriff auf die Datenbank. Um eine Verbindung herzustellen, rufen Sie einfach .addDatabase() auf auf QSqlDatabase . Diese statische Methode nimmt einen SQL-Treiber und einen optionalen Verbindungsnamen als Argumente und gibt eine Datenbankverbindung zurück:

QSqlDatabase.addDatabase(
    driver, connectionName=QSqlDatabase.defaultConnection
)

Das erste Argument, driver , ist ein erforderliches Argument, das eine Zeichenfolge enthält, die den Namen eines von PyQt unterstützten SQL-Treibers enthält. Das zweite Argument, connectionName , ist ein optionales Argument, das eine Zeichenfolge mit dem Namen der Verbindung enthält. connectionName ist standardmäßig QSqlDatabase.defaultConnection , die normalerweise die Zeichenfolge "qt_sql_default_connection" enthält .

Wenn Sie bereits eine Verbindung mit dem Namen connectionName haben , dann wird diese Verbindung entfernt und durch eine neue Verbindung ersetzt, und .addDatabase() gibt die neu hinzugefügte Datenbankverbindung an den Aufrufer zurück.

Ein Aufruf von .addDatabase() Fügt eine Datenbankverbindung zu einer Liste verfügbarer Verbindungen hinzu. Diese Liste ist eine globale Registrierung die PyQt hinter den Kulissen verwaltet, um die verfügbaren Verbindungen in einer Anwendung zu verfolgen. Registrieren Sie Ihre Verbindungen mit einem aussagekräftigen connectionName ermöglicht es Ihnen, mehrere Verbindungen in einer Datenbankanwendung zu verwalten.

Nachdem Sie eine Verbindung erstellt haben, müssen Sie möglicherweise mehrere Attribute dafür festlegen. Der spezifische Satz von Attributen hängt von dem verwendeten Treiber ab. Im Allgemeinen müssen Sie Attribute wie den Datenbanknamen, den Benutzernamen und das Passwort für den Zugriff auf die Datenbank festlegen.

Hier ist eine Zusammenfassung der Setter-Methoden, die Sie verwenden können, um die häufiger verwendeten Attribute oder Eigenschaften einer Datenbankverbindung festzulegen:

Methode Beschreibung
.setDatabaseName(name) Setzt den Datenbanknamen auf name , die eine Zeichenfolge ist, die einen gültigen Datenbanknamen darstellt
.setHostName(host) Setzt den Hostnamen auf host , die eine Zeichenfolge ist, die einen gültigen Hostnamen darstellt
.setUserName(username) Setzt den Benutzernamen auf username , die eine Zeichenfolge ist, die einen gültigen Benutzernamen darstellt
.setPassword(password) Setzt das Passwort auf password , die ein String ist, der ein gültiges Passwort darstellt

Beachten Sie, dass Sie das Passwort als Argument an .setPassword() übergeben wird im Klartext gespeichert und kann später durch Aufruf von .password() abgerufen werden . Dies ist ein ernsthaftes Sicherheitsrisiko die Sie vermeiden sollten, in Ihre Datenbankanwendungen einzuführen. Einen sichereren Ansatz lernen Sie später in diesem Tutorial im Abschnitt Öffnen einer Datenbankverbindung kennen.

So erstellen Sie eine Verbindung zu einer SQLite-Datenbank mit QSqlDatabase , öffnen Sie eine interaktive Python-Sitzung und geben Sie den folgenden Code ein:

>>>
>>> from PyQt5.QtSql import QSqlDatabase

>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")

>>> con
<PyQt5.QtSql.QSqlDatabase object at 0x7f0facec0c10>

>>> con.databaseName()
'contacts.sqlite'

>>> con.connectionName()
'qt_sql_default_connection'

Dieser Code erstellt ein Datenbankverbindungsobjekt mit "QSQLITE" als Treiber der Verbindung und "contacts.sqlite" als Datenbankname der Verbindung. Da Sie keinen Verbindungsnamen an .addDatabase() übergeben , wird die neu erstellte zu Ihrer Standardverbindung, deren Name "qt_sql_default_connection" ist .

Bei SQLite-Datenbanken ist der Datenbankname normalerweise ein Dateiname oder ein Pfad, der den Dateinamen der Datenbank enthält. Sie können auch den speziellen Namen ":memory:" verwenden für eine In-Memory-Datenbank.



Umgang mit mehreren Verbindungen

Es kann Situationen geben, in denen Sie mehrere Verbindungen zu einer einzelnen Datenbank verwenden müssen. Beispielsweise möchten Sie möglicherweise die Interaktionen der Benutzer mit der Datenbank protokollieren, indem Sie für jeden Benutzer eine bestimmte Verbindung verwenden.

In anderen Situationen müssen Sie Ihre Anwendung möglicherweise mit mehreren Datenbanken verbinden. Beispielsweise möchten Sie möglicherweise eine Verbindung zu mehreren entfernten Datenbanken herstellen, um Daten zum Füllen oder Aktualisieren einer lokalen Datenbank zu sammeln.

Um diese Situationen zu handhaben, können Sie Ihren verschiedenen Verbindungen spezifische Namen geben und auf jede Verbindung mit ihrem Namen verweisen. Wenn Sie Ihrer Datenbankverbindung einen Namen geben möchten, übergeben Sie diesen Namen als zweites Argument an .addDatabase() :

>>>
>>> from PyQt5.QtSql import QSqlDatabase

>>> # First connection
>>> con1 = QSqlDatabase.addDatabase("QSQLITE", "con1")
>>> con1.setDatabaseName("contacts.sqlite")

>>> # Second connection
>>> con2 = QSqlDatabase.addDatabase("QSQLITE", "con2")
>>> con2.setDatabaseName("contacts.sqlite")

>>> con1
<PyQt5.QtSql.QSqlDatabase object at 0x7f367f5fbf90>
>>> con2
<PyQt5.QtSql.QSqlDatabase object at 0x7f3686dd7510>

>>> con1.databaseName()
'contacts.sqlite'
>>> con2.databaseName()
'contacts.sqlite'

>>> con1.connectionName()
'con1'
>>> con2.connectionName()
'con2'

Hier erstellen Sie zwei verschiedene Verbindungen zu derselben Datenbank, contacts.sqlite . Jede Verbindung hat einen eigenen Verbindungsnamen. Sie können den Verbindungsnamen verwenden, um später je nach Bedarf in Ihrem Code einen Verweis auf eine bestimmte Verbindung zu erhalten. Dazu können Sie .database() aufrufen mit einem Verbindungsnamen:

>>>
>>> from PyQt5.QtSql import QSqlDatabase

>>> db = QSqlDatabase.database("con1", open=False)

>>> db.databaseName()
'contacts.sqlite'
>>> db.connectionName()
'con1'

In diesem Beispiel sehen Sie diese .database() nimmt zwei Argumente:

  1. connectionName enthält den Verbindungsnamen, den Sie verwenden müssen. Wenn Sie keinen Verbindungsnamen übergeben, wird die Standardverbindung verwendet.
  2. open enthält einen booleschen Wert, der .database() mitteilt ob Sie die Verbindung automatisch öffnen möchten oder nicht. Wenn open ist True (Standardeinstellung) und die Verbindung nicht geöffnet ist, wird die Verbindung automatisch geöffnet.

Der Rückgabewert von .database() ist ein Verweis auf das Verbindungsobjekt namens connectionName . Sie können verschiedene Verbindungsnamen verwenden, um Verweise auf bestimmte Verbindungsobjekte zu erhalten, und diese dann zum Verwalten Ihrer Datenbank verwenden.



Verwendung verschiedener SQL Divers

Bisher haben Sie gelernt, wie Sie eine Datenbankverbindung mit dem SQLite-Treiber erstellen . Dies ist nicht der einzige in PyQt verfügbare Treiber. Die Bibliothek bietet einen umfangreichen Satz von SQL-Treibern, mit denen Sie verschiedene Arten von Datenbankverwaltungssystemen gemäß Ihren spezifischen Anforderungen verwenden können:

Treibername Datenbankverwaltungssystem
QDB2 IBM Db2 (Version 7.1 und höher)
QIBASE Borland InterBase
QMYSQL/MARIADB MySQL oder MariaDB (Version 5.0 und höher)
QOCI Oracle Call Interface
QODBC Open Database Connectivity (ODBC)
QPSQL PostgreSQL (Versionen 7.3 und höher)
QSQLITE2 SQLite 2 (veraltet seit Qt 5.14)
QSQLITE SQLite 3
QTDS Sybase Adaptive Server (veraltet seit Qt 4.7)

Die Spalte Treibername enthält die Identifikatorzeichenfolgen die Sie an .addDatabase() übergeben müssen als erstes Argument, um den zugeordneten Treiber zu verwenden. Anders als beim SQLite-Treiber müssen Sie bei Verwendung eines anderen Treibers möglicherweise mehrere Attribute festlegen, z. B. databaseName , hostName , username , und password , damit die Verbindung ordnungsgemäß funktioniert.

Datenbanktreiber werden von QSqlDriver abgeleitet . Sie können Ihre eigenen Datenbanktreiber erstellen, indem Sie QSqlDriver unterklassen , aber dieses Thema geht über den Rahmen dieses Tutorials hinaus. Wenn Sie daran interessiert sind, Ihre eigenen Datenbanktreiber zu erstellen, finden Sie weitere Informationen unter How to Write Your Own Database Driver (So schreiben Sie Ihren eigenen Datenbanktreiber).



Öffnen einer Datenbankverbindung

Sobald Sie eine Datenbankverbindung haben, müssen Sie diese Verbindung öffnen, um mit Ihrer Datenbank interagieren zu können. Dazu rufen Sie .open() auf am Verbindungsobjekt. .open() hat die folgenden zwei Variationen:

  1. .open() öffnet eine Datenbankverbindung mit den aktuellen Verbindungswerten.
  2. .open(username, password) öffnet eine Datenbankverbindung mit dem angegebenen username und password .

Beide Varianten geben True zurück wenn die Verbindung erfolgreich ist. Andernfalls geben sie False zurück . Wenn die Verbindung nicht hergestellt werden kann, können Sie .lastError() aufrufen um Informationen darüber zu erhalten, was passiert ist. Diese Funktion gibt Informationen über den letzten von der Datenbank gemeldeten Fehler zurück.

Hinweis: Wie Sie zuvor gelernt haben, .setPassword(password) speichert Passwörter als Klartext, was ein Sicherheitsrisiko darstellt. Andererseits .open() speichert überhaupt keine Passwörter. Es übergibt das Passwort beim Verbindungsaufbau direkt an den Treiber. Danach wird das Passwort verworfen. Verwenden Sie also .open() Ihre Passwörter zu verwalten ist der richtige Weg, wenn Sie Sicherheitsproblemen vorbeugen möchten.

Hier ist ein Beispiel dafür, wie Sie eine SQLite-Datenbankverbindung mit der ersten Variante von .open() öffnen :

>>>
>>> from PyQt5.QtSql import QSqlDatabase

>>> # Create the connection
>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")

>>> # Open the connection
>>> con.open()
True
>>> con.isOpen()
True

Im obigen Beispiel erstellen Sie zuerst eine Verbindung zu Ihrer SQLite-Datenbank und öffnen diese Verbindung mit .open() . Seit .open() gibt True zurück , die Verbindung ist erfolgreich. An dieser Stelle können Sie die Verbindung mit .isOpen() überprüfen , die True zurückgibt wenn die Verbindung offen ist und False andernfalls.

Hinweis: Wenn Sie .open() aufrufen Bei einer Verbindung, die den SQLite-Treiber verwendet und die Datenbankdatei nicht existiert, wird automatisch eine neue und leere Datenbankdatei erstellt.

In realen Anwendungen müssen Sie sicherstellen, dass Sie eine gültige Verbindung zu Ihrer Datenbank haben, bevor Sie versuchen, Operationen mit Ihren Daten durchzuführen. Andernfalls kann Ihre Anwendung zusammenbrechen und fehlschlagen. Was ist zum Beispiel, wenn Sie keine Schreibrechte für das Verzeichnis haben, in dem Sie versuchen, diese Datenbankdatei zu erstellen? Sie müssen sicherstellen, dass Sie alle Fehler behandeln, die beim Öffnen einer Verbindung auftreten können.

Eine gängige Methode zum Aufrufen von .open() ist, es in eine bedingte Anweisung zu packen. Dadurch können Sie Fehler behandeln, die beim Öffnen der Verbindung auftreten können:

>>>
>>> import sys
>>> from PyQt5.QtSql import QSqlDatabase

>>> # Create the connection
>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")

>>> # Open the connection and handle errors
>>> if not con.open():
...     print("Unable to connect to the database")
...     sys.exit(1)

Wrapping des Aufrufs zu .open() in einer bedingten Anweisung können Sie jeden Fehler behandeln, der beim Öffnen der Verbindung auftritt. Auf diese Weise können Sie Ihre Benutzer über Probleme informieren, bevor die Anwendung ausgeführt wird. Beachten Sie, dass die Anwendung mit dem Exit-Status 1 beendet wird , das normalerweise verwendet wird, um einen Programmfehler anzuzeigen.

Im obigen Beispiel verwenden Sie .open() in einer interaktiven Sitzung, also verwenden Sie print() um den Benutzern Fehlermeldungen anzuzeigen. In GUI-Anwendungen jedoch anstelle von print() verwenden Sie normalerweise eine QMessageBox Objekt. Mit QMessageBox , können Sie kleine Dialoge erstellen, um Ihren Benutzern Informationen zu präsentieren.

Hier ist eine Beispiel-GUI-Anwendung, die zeigt, wie Verbindungsfehler behandelt werden:

 1import sys
 2
 3from PyQt5.QtSql import QSqlDatabase
 4from PyQt5.QtWidgets import QApplication, QMessageBox, QLabel
 5
 6# Create the connection
 7con = QSqlDatabase.addDatabase("QSQLITE")
 8con.setDatabaseName("/home/contacts.sqlite")
 9
10# Create the application
11app = QApplication(sys.argv)
12
13# Try to open the connection and handle possible errors
14if not con.open():
15    QMessageBox.critical(
16        None,
17        "App Name - Error!",
18        "Database Error: %s" % con.lastError().databaseText(),
19    )
20    sys.exit(1)
21
22# Create the application's window
23win = QLabel("Connection Successfully Opened!")
24win.setWindowTitle("App Name")
25win.resize(200, 100)
26win.show()
27sys.exit(app.exec_())

Der if Die Anweisung in Zeile 14 prüft, ob die Verbindung fehlgeschlagen ist. Wenn die /home/ Verzeichnis nicht existiert oder wenn Sie keine Berechtigung haben, darin zu schreiben, dann der Aufruf von .open() schlägt fehl, da die Datenbankdatei nicht erstellt werden kann. In dieser Situation der Ausführungsablauf gibt den if ein Anweisungscodeblock und zeigt eine Nachricht auf dem Bildschirm an.

Wenn Sie den Pfad in ein beliebiges anderes Verzeichnis ändern, in das Sie schreiben können, wird der Aufruf von .open() ausgeführt wird erfolgreich sein und Sie sehen ein Fenster mit der Meldung Connection Successfully Opened! Sie haben auch eine neue Datenbankdatei namens contacts.sqlite im ausgewählten Verzeichnis.

Beachten Sie, dass Sie None übergeben als übergeordnetes Element der Nachricht weil Sie zum Zeitpunkt der Anzeige der Nachricht noch kein Fenster erstellt haben, also haben Sie kein brauchbares übergeordnetes Element für das Nachrichtenfeld.




SQL-Abfragen mit PyQt ausführen

Mit einer voll funktionsfähigen Datenbankverbindung sind Sie bereit, mit Ihrer Datenbank zu arbeiten. Dazu können Sie Zeichenfolgen-basierte SQL-Abfragen und QSqlQuery verwenden Objekte. QSqlQuery ermöglicht es Ihnen, jede Art von SQL-Abfrage in Ihrer Datenbank auszuführen. Mit QSqlQuery , können Sie DML-Anweisungen (Data Manipulation Language) wie SELECT ausführen , INSERT , UPDATE , und DELETE sowie DDL-Anweisungen (Data Definition Language) wie CREATE TABLE und so weiter.

Der Konstruktor von QSqlQuery hat mehrere Variationen, aber in diesem Tutorial lernen Sie zwei davon kennen:

  1. QSqlQuery(query, connection) erstellt ein Abfrageobjekt unter Verwendung einer Zeichenfolgen-basierten SQL query und eine connection . Wenn Sie keine Verbindung angeben oder die angegebene Verbindung ungültig ist, wird die Standarddatenbankverbindung verwendet. Wenn query kein leerer String ist, wird er sofort ausgeführt.

  2. QSqlQuery(connection) erstellt ein Abfrageobjekt mit connection . Wenn connection ungültig ist, wird die Standardverbindung verwendet.

Sie können auch QSqlQuery erstellen Objekte, ohne irgendwelche Argumente an den Konstruktor zu übergeben. In diesem Fall verwendet die Abfrage die Standard-Datenbankverbindung, falls vorhanden.

Um eine Abfrage auszuführen, müssen Sie .exec() aufrufen auf das Abfrageobjekt. Sie können .exec() verwenden auf zwei verschiedene Arten:

  1. .exec(query) führt die in query enthaltene Zeichenfolgen-basierte SQL-Abfrage aus . Es gibt True zurück wenn die Abfrage erfolgreich war und ansonsten False zurückgibt .

  2. .exec() führt eine zuvor vorbereitete SQL-Abfrage aus. Es gibt True zurück wenn die Abfrage erfolgreich war und ansonsten False zurückgibt .

Hinweis: PyQt implementiert auch Variationen von QSqlQuery.exec() mit dem Namen .exec_() . Diese bieten Abwärtskompatibilität mit älteren Python-Versionen, in denen exec war ein Schlüsselwort der Sprache.

Jetzt kennen Sie die Grundlagen der Verwendung von QSqlQuery zum Erstellen und Ausführen von SQL-Abfragen sind Sie bereit zu lernen, wie Sie Ihr Wissen in die Praxis umsetzen können.


Statische SQL-Abfragen ausführen

Um mit dem Erstellen und Ausführen von Abfragen mit PyQt zu beginnen, starten Sie Ihren bevorzugten Code-Editor oder Ihre bevorzugte IDE und erstellen ein Python-Skript mit dem Namen queries.py . Speichern Sie das Skript und fügen Sie den folgenden Code hinzu:

 1import sys
 2
 3from PyQt5.QtSql import QSqlDatabase, QSqlQuery
 4
 5# Create the connection
 6con = QSqlDatabase.addDatabase("QSQLITE")
 7con.setDatabaseName("contacts.sqlite")
 8
 9# Open the connection
10if not con.open():
11    print("Database Error: %s" % con.lastError().databaseText())
12    sys.exit(1)
13
14# Create a query and execute it right away using .exec()
15createTableQuery = QSqlQuery()
16createTableQuery.exec(
17    """
18    CREATE TABLE contacts (
19        id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL,
20        name VARCHAR(40) NOT NULL,
21        job VARCHAR(50),
22        email VARCHAR(40) NOT NULL
23    )
24    """
25)
26
27print(con.tables())

In diesem Skript importieren Sie zunächst die Module und Klassen, mit denen Sie arbeiten werden. Dann erstellen Sie eine Datenbankverbindung mit .addDatabase() mit dem SQLite-Treiber. Sie setzen den Datenbanknamen auf "contacts.sqlite" und öffnen Sie die Verbindung.

Um Ihre erste Abfrage zu erstellen, instanziieren Sie QSqlQuery ohne Argumente. Wenn das Abfrageobjekt vorhanden ist, rufen Sie .exec() auf , wobei eine Zeichenfolgen-basierte SQL-Abfrage als Argument übergeben wird. Diese Art von Abfrage wird als statische Abfrage bezeichnet weil es keine Parameter von außerhalb der Abfrage bekommt.

Die obige SQL-Abfrage erstellt eine neue Tabelle namens contacts in Ihrer Datenbank. Diese Tabelle hat die folgenden vier Spalten:

Spalte Inhalt
id Eine Ganzzahl mit dem Primärschlüssel der Tabelle
name Ein String mit dem Namen eines Kontakts
job Ein String mit der Berufsbezeichnung eines Kontakts
email Ein String mit der E-Mail eines Kontakts

Die letzte Zeile im obigen Skript gibt die Liste der in Ihrer Datenbank enthaltenen Tabellen aus. Wenn Sie das Skript ausführen, werden Sie feststellen, dass eine neue Datenbankdatei mit dem Namen contacts.sqlite wird in Ihrem aktuellen Verzeichnis erstellt. Sie erhalten auch so etwas wie ['contacts', 'sqlite_sequence'] auf Ihrem Bildschirm gedruckt. Diese Liste enthält die Namen der Tabellen in Ihrer Datenbank.

Hinweis: Eine zeichenfolgenbasierte SQL-Abfrage muss eine geeignete Syntax entsprechend der spezifischen SQL-Datenbank verwenden, die Sie abfragen. Wenn die Syntax falsch ist, dann .exec() ignoriert die Abfrage und gibt False zurück .

Im Fall von SQLite kann die Abfrage jeweils nur eine Anweisung enthalten.

Aufruf von .exec() auf einer QSqlQuery -Objekt ist eine gängige Methode zum sofortigen Ausführen von Zeichenfolgen-basierten SQL-Abfragen in Ihren Datenbanken, aber was ist, wenn Sie Ihre Abfragen im Voraus für die spätere Ausführung vorbereiten möchten? Das ist das Thema des nächsten Abschnitts.



Ausführen dynamischer Abfragen:Zeichenfolgenformatierung

Bisher haben Sie gelernt, wie Sie statische Abfragen in einer Datenbank ausführen. Statische Abfragen sind solche, die keine Parameter akzeptieren , sodass die Abfrage unverändert ausgeführt wird. Obwohl diese Abfragen ziemlich nützlich sind, müssen Sie manchmal Abfragen erstellen, die Daten als Antwort auf bestimmte Eingabeparameter abrufen.

Abfragen, die Parameter zur Ausführungszeit akzeptieren, werden als dynamische Abfragen bezeichnet . Durch die Verwendung von Parametern können Sie die Abfrage optimieren und Daten als Reaktion auf bestimmte Parameterwerte abrufen. Unterschiedliche Werte führen zu unterschiedlichen Ergebnissen. Sie können Eingabeparameter in eine Abfrage übernehmen, indem Sie einen der beiden folgenden Ansätze verwenden:

  1. Erstellen Sie die Abfrage dynamisch, indem Sie die Zeichenfolgenformatierung verwenden, um Parameterwerte zu interpolieren.
  2. Bereiten Sie die Abfrage mit Platzhalterparametern vor und binden Sie dann bestimmte Werte an Parameter.

Mit dem ersten Ansatz können Sie schnell dynamische Abfragen erstellen. Um diesen Ansatz sicher verwenden zu können, müssen Sie jedoch sicher sein, dass Ihre Parameterwerte aus einer vertrauenswürdigen Quelle stammen. Andernfalls könnten Sie SQL-Injection-Angriffen ausgesetzt sein.

Hier ist ein Beispiel für die Verwendung der Zeichenfolgenformatierung zum Erstellen dynamischer Abfragen in PyQt:

>>>
>>> from PyQt5.QtSql import QSqlQuery, QSqlDatabase

>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")
>>> con.open()
True

>>> name = "Linda"
>>> job = "Technical Lead"
>>> email = "[email protected]"

>>> query = QSqlQuery()
>>> query.exec(
...     f"""INSERT INTO contacts (name, job, email)
...     VALUES ('{name}', '{job}', '{email}')"""
... )
True

In diesem Beispiel verwenden Sie einen f-String, um eine dynamische Abfrage zu erstellen, indem Sie bestimmte Werte in eine zeichenfolgenbasierte SQL-Abfrage interpolieren. Die letzte Abfrage fügt Daten in Ihre contacts ein Tabelle, die nun Daten über Linda enthält .

Hinweis: Später in diesem Lernprogramm erfahren Sie, wie Sie die in einer Datenbank gespeicherten Daten abrufen und darin navigieren.

Beachten Sie, dass Sie sicherstellen müssen, dass die einzufügenden Werte den richtigen Datentyp haben, damit diese Art von dynamischer Abfrage funktioniert. Sie verwenden also einfache Anführungszeichen um den Platzhalter im f-String, weil diese Werte Strings sein müssen.



Dynamische Abfragen ausführen:Platzhalterparameter

Der zweite Ansatz zum Ausführen dynamischer Abfragen erfordert, dass Sie Ihre Abfragen zuvor mithilfe einer Vorlage mit Platzhaltern vorbereiten für Parameter. PyQt unterstützt zwei Parameter-Platzhalterstile:

  1. Oracle-Stil verwendet benannte Platzhalter wie :name oder :email .
  2. ODBC-Stil verwendet ein Fragezeichen (? ) als Positionsplatzhalter.

Beachten Sie, dass diese Stile nicht in derselben Abfrage gemischt werden können. Unter Approaches to Binding Values ​​finden Sie weitere Beispiele zur Verwendung von Platzhaltern.

Hinweis: ODBC steht für Open Database Connectivity.

Um diese Art von dynamischer Abfrage in PyQt zu erstellen, erstellen Sie zunächst eine Vorlage mit einem Platzhalter für jeden Abfrageparameter und übergeben diese Vorlage dann als Argument an .prepare() , die die Abfragevorlage analysiert, kompiliert und für die Ausführung vorbereitet. Wenn die Vorlage irgendwelche Probleme hat, wie z. B. einen SQL-Syntaxfehler, dann .prepare() kann die Vorlage nicht kompilieren und gibt False zurück .

Wenn der Vorbereitungsprozess erfolgreich ist, dann prepare() gibt True zurück . Danach können Sie jedem Parameter mit .bindValue() einen bestimmten Wert übergeben mit benannten oder positionellen Parametern oder mit .addBindValue() mit Positionsparametern. .bindValue() hat die folgenden zwei Variationen:

  1. .bindValue(placeholder, val)
  2. .bindValue(pos, val)

In der ersten Variante placeholder stellt einen Platzhalter im Oracle-Stil dar. In der zweiten Variante pos stellt eine nullbasierte Ganzzahl mit der Position eines Parameters in der Abfrage dar. In beiden Varianten val enthält den Wert, der an einen bestimmten Parameter gebunden werden soll.

.addBindValue() Fügt der Liste der Platzhalter einen Wert hinzu, der die Positionsbindung verwendet. Das bedeutet, dass die Reihenfolge der Aufrufe von .addBindValue() legt fest, welcher Wert an jeden Platzhalterparameter in der vorbereiteten Abfrage gebunden wird.

Um vorbereitete Abfragen zu verwenden, können Sie ein INSERT INTO vorbereiten SQL-Anweisung, um Ihre Datenbank mit einigen Beispieldaten zu füllen. Kehren Sie zu dem Skript zurück, das Sie im Abschnitt Statische SQL-Abfragen ausführen erstellt haben, und fügen Sie den folgenden Code direkt nach dem Aufruf von print() hinzu :

28# Creating a query for later execution using .prepare()
29insertDataQuery = QSqlQuery()
30insertDataQuery.prepare(
31    """
32    INSERT INTO contacts (
33        name,
34        job,
35        email
36    )
37    VALUES (?, ?, ?)
38    """
39)
40
41# Sample data
42data = [
43    ("Joe", "Senior Web Developer", "[email protected]"),
44    ("Lara", "Project Manager", "[email protected]"),
45    ("David", "Data Analyst", "[email protected]"),
46    ("Jane", "Senior Python Developer", "[email protected]"),
47]
48
49# Use .addBindValue() to insert data
50for name, job, email in data:
51    insertDataQuery.addBindValue(name)
52    insertDataQuery.addBindValue(job)
53    insertDataQuery.addBindValue(email)
54    insertDataQuery.exec()

Der erste Schritt besteht darin, eine QSqlQuery zu erstellen Objekt. Dann rufen Sie .prepare() auf auf das Abfrageobjekt. In diesem Fall verwenden Sie für die Platzhalter den ODBC-Stil. Ihre Abfrage nimmt Werte für den name Ihres Kontakts an , job , und email , Sie benötigen also drei Platzhalter. Da die id Spalte eine automatisch inkrementierte Ganzzahl ist, müssen Sie keine Werte dafür angeben.

Dann erstellen Sie einige Beispieldaten, um die Datenbank zu füllen. data enthält eine Liste von Tupeln, und jedes Tupel enthält drei Elemente:den Namen, den Job und die E-Mail-Adresse jedes Kontakts.

Der letzte Schritt besteht darin, die Werte, die Sie übergeben möchten, an jeden Platzhalter zu binden und dann .exec() aufzurufen um die Abfrage auszuführen. Dazu verwenden Sie einen for Schleife. Der Loop-Header entpackt jedes Tupel in data in drei separate Variablen mit praktischen Namen. Dann rufen Sie .addBindValue() auf auf das Abfrageobjekt, um die Werte an die Platzhalter zu binden.

Beachten Sie, dass Sie Positionsplatzhalter verwenden , so the order in which you call .addBindValue() will define the order in which each value is passed to the corresponding placeholder.

This approach for creating dynamic queries is handy when you want to customize your queries using values that come from your user’s input. Anytime you take the user’s input to complete a query on a database, you face the security risk of SQL injection.

In PyQt, combining .prepare() , .bindValue() , and .addBindValue() fully protects you against SQL injection attacks, so this is the way to go when you’re taking untrusted input to complete your queries.



Navigating the Records in a Query

If you execute a SELECT statement, then your QSqlQuery object will retrieve zero or more records from one or more tables in your database. The query will hold records containing data that matches the query’s criteria. If no data matches the criteria, then your query will be empty.

QSqlQuery provides a set of navigation methods that you can use to move throughout the records in a query result:

Methode Retrieves
.next() The next record
.previous() The previous record
.first() The first record
.last() The last record
.seek(index, relative=False) The record at position index

All these methods position the query object on the retrieved record if that record is available. Most of these methods have specific rules that apply when using them. With these methods, you can move forward, backward, or arbitrarily through the records in a query result. Since they all return either True or False , you can use them in a while loop to navigate all the records in one go.

These methods work with active queries . A query is active when you’ve successfully run .exec() on it, but the query isn’t finished yet. Once an active query is on a valid record, you can retrieve data from that record using .value(index) . This method takes a zero-based integer number, index , and returns the value at that index (column) in the current record.

Hinweis: If you execute a SELECT * type of query, then the columns in the result won’t follow a known order. This might cause problems when you use .value() to retrieve the value at a given column because there’s no way of knowing if you’re using the right column index.

You’ll look at a few examples of how to use some of the navigation methods to move throughout a query below. But first, you need to create a connection to your database:

>>>
>>> from PyQt5.QtSql import QSqlDatabase, QSqlQuery

>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")
>>> con.open()
True

Here, you create and open a new connection to contacts.sqlite . If you’ve been following along with this tutorial so far, then this database already contains some sample data. Now you can create a QSqlQuery object and execute it on that data:

>>>
>>> # Create and execute a query
>>> query = QSqlQuery()
>>> query.exec("SELECT name, job, email FROM contacts")
True

This query retrieves data about the name , job , and email of all the contacts stored in the contacts Tisch. Since .exec() returned True , the query was successful and is now an active query. You can navigate the records in this query using any of the navigation methods you saw before. You can also retrieve the data at any column in a record using .value() :

>>>
>>> # First record
>>> query.first()
True

>>> # Named indices for readability
>>> name, job, email = range(3)

>>> # Retrieve data from the first record
>>> query.value(name)
'Linda'

>>> # Next record
>>> query.next()
True
>>> query.value(job)
'Senior Web Developer'

>>> # Last record
>>> query.last()
True
>>> query.value(email)
'[email protected]'

With the navigation methods, you can move around the query result. With .value() , you can retrieve the data at any column in a given record.

You can also iterate through all the records in your query using a while loop along with .next() :

>>>
>>> query.exec()
True

>>> while query.next():
...     print(query.value(name), query.value(job), query.value(email))
...
Linda Technical Lead [email protected]
Joe Senior Web Developer [email protected]
...

With .next() , you navigate all the records in a query result. .next() works similar to the iterator protocol in Python. Once you’ve iterated over the records in a query result, .next() starts returning False until you run .exec() wieder. A call to .exec() retrieves data from a database and places the query object’s internal pointer one position before the first record, so when you call .next() , you get the first record again.

You can also loop in reverse order using .previous() :

>>>
>>> while query.previous():
...     print(query.value(name), query.value(job), query.value(email))
...
Jane Senior Python Developer [email protected]
David Data Analyst [email protected]
...

.previous() works similar to .next() , but the iteration is done in reverse order. In other words, the loop goes from the query pointer’s position back to the first record.

Sometimes you might want to get the index that identifies a given column in a table by using the name of that column. To do that, you can call .indexOf() on the return value of .record() :

>>>
>>> query.first()
True

>>> # Get the index of name
>>> name = query.record().indexOf("name")

>>> query.value(name)
'Linda'

>>> # Finish the query object if unneeded
>>> query.finish()
>>> query.isActive()
False

The call to .indexOf() on the result of .record() returns the index of the "name" column. If "name" doesn’t exist, then .indexOf() gibt -1 zurück . This is handy when you use a SELECT * statement in which the order of columns is unknown. Finally, if you’re done with a query object, then you can turn it inactive by calling .finish() . This will free the system memory associated with the query object at hand.




Closing and Removing Database Connections

In practice, some of your PyQt applications will depend on a database, and others won’t. An application that depends on a database often creates and opens a database connection just before creating any window or graphical component and keeps the connection open until the application is closed.

On the other hand, applications that don’t depend on a database but use a database to provide some of their functionalities typically connect to that database only when needed, if at all. In these cases, you can close the connection after use and free the resources associated with that connection, such as system memory.

To close a connection in PyQt, you call .close() on the connection. This method closes the connection and frees any acquired resources. It also invalidates any associated QSqlQuery objects because they can’t work properly without an active connection.

Here’s an example of how to close an active database connection using .close() :

>>>
>>> from PyQt5.QtSql import QSqlDatabase

>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")

>>> con.open()
True
>>> con.isOpen()
True

>>> con.close()
>>> con.isOpen()
False

You can call .close() on a connection to close it and free all its associated resources. To make sure that a connection is closed, you call .isOpen() .

Note that QSqlQuery objects remain in memory after closing their associated connection, so you must make your queries inactive by calling .finish() or .clear() , or by deleting the QSqlQuery object before closing the connection. Otherwise, residual memory is left out in your query object.

You can reopen and reuse any previously closed connection. That’s because .close() doesn’t remove connections from the list of available connections, so they remain usable.

You can also completely remove your database connections using .removeDatabase() . To do this safely, first finish your queries using .finish() , then close the database using .close() , and finally remove the connection. You can use .removeDatabase(connectionName) to remove the database connection called connectionName from the list of available connections. Removed connections are no longer available for use in the application at hand.

To remove the default database connection, you can call .connectionName() on the object returned by .database() and pass the result to .removeDatabase() :

>>>
>>> # The connection is closed but still in the list of connections
>>> QSqlDatabase.connectionNames()
['qt_sql_default_connection']

>>> # Remove the default connection
>>> QSqlDatabase.removeDatabase(QSqlDatabase.database().connectionName())

>>> # The connection is no longer in the list of connections
>>> QSqlDatabase.connectionNames()
[]

>>> # Try to open a removed connection
>>> con.open()
False

Here, the call to .connectionNames() returns the list of available connections. In this case, you have only one connection, the default. Then you remove the connection using .removeDatabase() .

Hinweis: Before closing and removing a database connection, you need to make sure that everything that uses the connection is deleted or set to use a different data source. Otherwise, you can have a resource leak .

Since you need a connection name to use .removeDatabase() , you call .connectionName() on the result of .database() to get the name of the default connection. Finally, you call .connectionNames() again to make sure that the connection is no longer in the list of available connections. Trying to open a removed connection will return False because the connection no longer exists.



Displaying and Editing Data With PyQt

A common requirement in GUI applications that use databases is the ability to load, display, and edit data from the database using different widgets. Table, list, and tree widgets are commonly used in GUIs to manage data.

PyQt provides two different kind of widgets for managing data:

  1. Standard widgets include internal containers for storing data.
  2. View widgets don’t maintain internal data containers but use models to access data.

For small GUI applications that manage small databases, you can use the first approach. The second approach is handy when you’re building complex GUI applications that manage large databases.

The second approach takes advantage of PyQt’s Model-View programming. With this approach, you have widgets that represent views such as tables, lists, and trees on one hand and model classes that communicate with your data on the other hand.


Understanding PyQt’s Model-View Architecture

The Model-View-Controller (MVC) design pattern is a general software pattern intended to divide an application’s code into three general layers, each with a different role.

The model takes care of the business logic of the application, the view provides on-screen representations, and the controller connects the model and the view to make the application work.

Qt provides a custom variation of MVC. They call it the Model-View architecture, and it’s available for PyQt as well. The pattern also separates the logic into three components:

  1. Models communicate with and access the data. They also define an interface that’s used by views and delegates to access the data. All models are based on QAbstractItemModel . Some commonly used models include QStandardItemModel , QFileSystemModel , and SQL-related models.

  2. Views are responsible for displaying the data to the user. They also have similar functionality to the controller in the MVC pattern. All views are based on QAbstractItemView . Some commonly used views are QListView , QTableView , and QTreeView .

  3. Delegates paint view items and provide editor widgets for modifying items. They also communicate back with the model if an item has been modified. The base class is QAbstractItemDelegate .

Separating classes into these three components implies that changes on models will be reflected on associated views or widgets automatically, and changes on views or widgets through delegates will update the underlying model automatically.

In addition, you can display the same data in different views without the need for multiple models.



Using Standard Widget Classes

PyQt provides a bunch of standard widgets for displaying and editing data in your GUI applications. These standard widgets provide views such as tables, trees, and lists. They also provide an internal container for storing data and convenient delegates for editing the data. All these features are grouped into a single class.

Here are three of these standard classes:

Standard Class Displays
QListWidget A list of items
QTreeWidget A hierarchical tree of items
QTableWidget A table of items

QTableWidget is arguably the most popular widget when it comes to displaying and editing data. It creates a 2D array of QTableWidgetItem Objekte. Each item holds an individual value as a string. All these values are displayed and organized in a table of rows and columns.

You can perform at least the following operations on a QTableWidget object:

  • Editing the content of its items using delegate objects
  • Adding new items using .setItem()
  • Setting the number of rows and columns using .setRowCount() and .setColumnCount()
  • Adding vertical and horizontal header labels using setHorizontalHeaderLabels() and .setVerticalHeaderLabels

Here’s a sample application that shows how to use a QTableWidget object to display data in a GUI. The application uses the database you created and populated in previous sections, so if you want to run it, then you need to save the code into the same directory in which you have the contacts.sqlite Datenbank:

If you double-click any cell of the table, then you’ll be able to edit the content of the cell. However, your changes won’t be saved to your database.

Here’s the code for your application:

 1import sys
 2
 3from PyQt5.QtSql import QSqlDatabase, QSqlQuery
 4from PyQt5.QtWidgets import (
 5    QApplication,
 6    QMainWindow,
 7    QMessageBox,
 8    QTableWidget,
 9    QTableWidgetItem,
10)
11
12class Contacts(QMainWindow):
13    def __init__(self, parent=None):
14        super().__init__(parent)
15        self.setWindowTitle("QTableView Example")
16        self.resize(450, 250)
17        # Set up the view and load the data
18        self.view = QTableWidget()
19        self.view.setColumnCount(4)
20        self.view.setHorizontalHeaderLabels(["ID", "Name", "Job", "Email"])
21        query = QSqlQuery("SELECT id, name, job, email FROM contacts")
22        while query.next():
23            rows = self.view.rowCount()
24            self.view.setRowCount(rows + 1)
25            self.view.setItem(rows, 0, QTableWidgetItem(str(query.value(0))))
26            self.view.setItem(rows, 1, QTableWidgetItem(query.value(1)))
27            self.view.setItem(rows, 2, QTableWidgetItem(query.value(2)))
28            self.view.setItem(rows, 3, QTableWidgetItem(query.value(3)))
29        self.view.resizeColumnsToContents()
30        self.setCentralWidget(self.view)
31
32def createConnection():
33    con = QSqlDatabase.addDatabase("QSQLITE")
34    con.setDatabaseName("contacts.sqlite")
35    if not con.open():
36        QMessageBox.critical(
37            None,
38            "QTableView Example - Error!",
39            "Database Error: %s" % con.lastError().databaseText(),
40        )
41        return False
42    return True
43
44app = QApplication(sys.argv)
45if not createConnection():
46    sys.exit(1)
47win = Contacts()
48win.show()
49sys.exit(app.exec_())

Here’s what’s happening in this example:

  • Lines 18 to 20 create a QTableWidget object, set the number of columns to 4 , and set user-friendly labels for each column’s header.
  • Line 21 creates and executes a SELECT SQL query on your database to get all the data in the contacts table.
  • Line 22 starts a while loop to navigate the records in the query result using .next() .
  • Line 24 increments the number of rows in the table by 1 using .setRowCount() .
  • Lines 25 to 28 add items of data to your table using .setItem() . Note that since the values in the id columns are integer numbers, you need to convert them into strings to be able to store them in a QTableWidgetItem object.

.setItem() takes three arguments:

  1. row holds a zero-based integer that represents the index of a given row in the table.
  2. column holds a zero-based integer that represents the index of a given column in the table.
  3. item holds the QTableWidgetItem object that you need to place at a given cell in the table.

Finally, you call .resizeColumnsToContents() on your view to adjust the size of the columns to their content and provide a better rendering of the data.

Displaying and editing database tables using standard widgets can become a challenging task. That’s because you’ll have two copies of the same data. In other words you’ll have a copy of the data in two locations:

  1. Outside the widget, in your database
  2. Inside the widget, in the widget’s internal containers

You’re responsible for synchronizing both copies of your data manually, which can be an annoying and error-prone operation. Luckily, you can use PyQt’s Model-View architecture to avoid most of these problems, as you’ll see in the following section.



Using View and Model Classes

PyQt’s Model-View classes eliminate the problems of data duplication and synchronization that may occur when you use standard widget classes to build database applications. The Model-View architecture allows you to use several views to display the same data because you can pass one model to many views.

Model classes provide an application programming interface (API) that you can use to manipulate data. View classes provide convenient delegate objects that you can use to edit data in the view directly. To connect a view with a given module, you need to call .setModel() on the view object.

PyQt offers a set of view classes that support the Model-View architecture:

View Class Displays
QListView A list of items that take values directly from a model class
QTreeView A hierarchical tree of items that take values directly from a model class
QTableView A table of items that take values directly from a model class

You can use these view classes along with model classes to create your database applications. This will make your applications more robust, faster to code, and less error-prone.

Here are some of the model classes that PyQt provides for working with SQL databases:

Model Class Beschreibung
QSqlQueryModel A read-only data model for SQL queries
QSqlTableModel An editable data model for reading and writing records in a single table
QSqlRelationalTableModel An editable data model for reading and writing records in a relational table

Once you’ve connected one of these models to a physical database table or query, you can use them to populate your views. Views provide delegate objects that allow you to modify the data directly in the view. The model connected to the view will update the data in your database to reflect any change in the view. Note that you don’t have to update the data in the database manually. The model will do that for you.

Here’s an example that shows the basics of how to use a QTableView object and a QSqlTableModel object together to build a database application using PyQt’s Model-View architecture:

To edit the data in a cell of the table, you can double-click the cell. A convenient delegate widget will show in the cell, allowing you to edit its content. Then you can hit Enter to save the changes.

The ability to automatically handle and save changes in the data is one of the more important advantages of using PyQt’s Model-View classes. The Model-View architecture will improve your productivity and reduce the errors that can appear when you have to write data manipulation code by yourself.

Here’s the code to create the application:

 1import sys
 2
 3from PyQt5.QtCore import Qt
 4from PyQt5.QtSql import QSqlDatabase, QSqlTableModel
 5from PyQt5.QtWidgets import (
 6    QApplication,
 7    QMainWindow,
 8    QMessageBox,
 9    QTableView,
10)
11
12class Contacts(QMainWindow):
13    def __init__(self, parent=None):
14        super().__init__(parent)
15        self.setWindowTitle("QTableView Example")
16        self.resize(415, 200)
17        # Set up the model
18        self.model = QSqlTableModel(self)
19        self.model.setTable("contacts")
20        self.model.setEditStrategy(QSqlTableModel.OnFieldChange)
21        self.model.setHeaderData(0, Qt.Horizontal, "ID")
22        self.model.setHeaderData(1, Qt.Horizontal, "Name")
23        self.model.setHeaderData(2, Qt.Horizontal, "Job")
24        self.model.setHeaderData(3, Qt.Horizontal, "Email")
25        self.model.select()
26        # Set up the view
27        self.view = QTableView()
28        self.view.setModel(self.model)
29        self.view.resizeColumnsToContents()
30        self.setCentralWidget(self.view)
31
32def createConnection():
33    con = QSqlDatabase.addDatabase("QSQLITE")
34    con.setDatabaseName("contacts.sqlite")
35    if not con.open():
36        QMessageBox.critical(
37            None,
38            "QTableView Example - Error!",
39            "Database Error: %s" % con.lastError().databaseText(),
40        )
41        return False
42    return True
43
44app = QApplication(sys.argv)
45if not createConnection():
46    sys.exit(1)
47win = Contacts()
48win.show()
49sys.exit(app.exec_())

Here’s what’s happening in this code:

  • Line 18 creates an editable QSqlTableModel object.
  • Line 19 connects your model with the contacts table in your database using .setTable() .
  • Line 20 sets the edit strategy of the model to OnFieldChange . This strategy allows the model to automatically update the data in your database if the user modifies any of the data directly in the view.
  • Lines 21 to 24 set some user-friendly labels to the horizontal headers of the model using .setHeaderData() .
  • Line 25 loads the data from your database and populates the model by calling .select() .
  • Line 27 creates the table view object to display the data contained in the model.
  • Line 28 connects the view with the model by calling .setModel() on the view with your data model as an argument.
  • Line 29 calls .resizeColumnsToContents() on the view object to adjust the table to its content.

Das ist es! You now have a fully-functional database application.




Using SQL Databases in PyQt:Best Practices

When it comes to using PyQt’s SQL support effectively, there are some best practices that you might want to use in your applications:

  • Favor PyQt’s SQL support over Python standard library or third-party libraries to take advantage of the natural integration of these classes with the rest of PyQt’s classes and infrastructure, mostly with the Model-View architecture.

  • Use previously prepared dynamic queries with placeholders for parameters and bind values to those parameters using .addBindValue() and .bindValue() . This will help prevent SQL injection attacks.

  • Handle errors that can occur when opening a database connection to avoid unexpected behaviors and application crashes.

  • Close and remove unneeded database connections and queries to free any acquired system resources.

  • Minimize the use of SELECT * queries to avoid problems when retrieving data with .value() .

  • Pass your passwords to .open() instead of to .setPassword() to avoid the risk of compromising your security.

  • Take advantage of PyQt’s Model-View architecture and its integration with PyQt’s SQL support to make your applications more robust.

This list isn’t complete, but it’ll help you make better use of PyQt’s SQL support when developing your database applications.



Schlussfolgerung

Using PyQt’s built-in support to work with SQL databases is an important skill for any Python developer who’s creating PyQt GUI applications and needs to connect them to a database. PyQt provides a consistent set of classes for managing SQL databases.

These classes fully integrate with PyQt’s Model-View architecture, allowing you to develop GUI applications that can manage databases in a user-friendly way.

In this tutorial, you’ve learned how to:

  • Use PyQt’s SQL support to connect to a database
  • Execute SQL queries on a database with PyQt
  • Build database applications using PyQt’s Model-View architecture
  • Display and edit data from a database using PyQt widgets

With this knowledge, you can improve your productivity when creating nontrivial database applications and make your GUI applications more robust.