sql >> Databasteknik >  >> RDS >> Database

Hantera SQL-databaser med PyQt:Grunderna

Att bygga applikationer som använder en SQL-databas är en ganska vanlig programmeringsuppgift. SQL-databaser finns överallt och har bra stöd i Python. I GUI-programmering tillhandahåller PyQt robust och plattformsoberoende SQL-databasstöd som låter dig skapa, ansluta till och hantera dina databaser konsekvent.

PyQts SQL-stöd integreras helt med dess Model-View-arkitektur för att hjälpa dig i processen att bygga databasapplikationer.

I den här självstudien får du lära dig hur du:

  • Använd PyQts SQL-stöd för att tillförlitligt ansluta till en databas
  • Kör SQL-frågor på en databas med PyQt
  • Använd PyQts Model-View-arkitektur i databasapplikationer
  • Visa och redigera data med olika PyQt widgets

Exemplen i denna handledning kräver grundläggande kunskaper i SQL-språket, särskilt om SQLite-databashanteringssystemet. Vissa tidigare kunskaper om GUI-programmering med Python och PyQt kommer också att vara till hjälp.

Gratis bonus: 5 Thoughts On Python Mastery, en gratiskurs för Python-utvecklare som visar dig färdplanen och tankesättet du behöver för att ta dina Python-färdigheter till nästa nivå.


Ansluter PyQt till en SQL-databas

Att koppla en applikation till en relationsdatabas och få applikationen att skapa, läsa, uppdatera och ta bort data som lagras i den databasen är en vanlig uppgift vid programmering. Relationsdatabaser är vanligtvis organiserade i en uppsättning tabeller , eller relationer . En given rad i en tabell kallas en post eller tuppel , och en kolumn kallas ett attribut .

Obs! Termen fält används vanligtvis för att identifiera en enskild data som lagras i en cell i en given post i en tabell. Å andra sidan, termen fältnamn används för att identifiera namnet på en kolumn.

Varje kolumn lagrar en specifik typ av information, såsom namn, datum eller siffror. Varje rad representerar en uppsättning närbesläktade data, och varje rad har samma allmänna struktur. Till exempel, i en databas som lagrar data om de anställda i ett företag, representerar en specifik rad en enskild anställd.

De flesta relationsdatabassystem använder SQL (strukturerat frågespråk) för att fråga, manipulera och underhålla data som finns i databasen. SQL är ett deklarativt och domänspecifikt programmeringsspråk speciellt utformat för att kommunicera med databaser.

Relationella databassystem och SQL används flitigt nuförtiden. Du hittar flera olika databashanteringssystem, som SQLite, PostgreSQL, MySQL, MariaDB och många andra. Du kan ansluta Python till vilket som helst av dessa databassystem med hjälp av ett dedikerat Python SQL-bibliotek.

Obs! Även om PyQts inbyggda SQL-stöd är det föredragna alternativet för att hantera SQL-databaser i PyQt, kan du också använda vilket annat bibliotek som helst för att hantera databasanslutningen. Några av dessa bibliotek inkluderar SQLAlchemy, pandor, SQLite och så vidare.

Men att använda ett annat bibliotek för att hantera dina databaser har vissa nackdelar. Du kommer inte att kunna dra fördel av integrationen mellan PyQts SQL-klasser och Model-View-arkitekturen. Dessutom kommer du att lägga till extra beroenden till din applikation.

När det gäller GUI-programmering med Python och PyQt, tillhandahåller PyQt en robust uppsättning klasser för att arbeta med SQL-databaser. Denna uppsättning klasser kommer att vara din bästa allierade när du behöver ansluta din applikation till en SQL-databas.

Obs! Tyvärr har PyQt5s officiella dokumentation några ofullständiga avsnitt. För att komma runt detta kan du kolla in PyQt4-dokumentationen, Qt For Pythons dokumentation eller den ursprungliga Qt-dokumentationen. I den här handledningen tar några länkar dig till den ursprungliga Qt-dokumentationen, som i de flesta fall är en bättre informationskälla.

I den här handledningen lär du dig grunderna i hur du använder PyQts SQL-stöd för att skapa GUI-applikationer som på ett tillförlitligt sätt interagerar med relationsdatabaser för att läsa, skriva, radera och visa data.


Skapa en databasanslutning

Att ansluta dina applikationer till en fysisk SQL-databas är ett viktigt steg i processen att utveckla databasapplikationer med PyQt. För att utföra det här steget framgångsrikt behöver du lite allmän information om hur din databas är inställd.

Du behöver till exempel veta vilket databashanteringssystem din databas är byggd på, och du kan också behöva ha ett användarnamn, ett lösenord, ett värdnamn och så vidare.

I den här handledningen kommer du att använda SQLite 3, som är ett väl testat databassystem med stöd på alla plattformar och minimala konfigurationskrav. SQLite låter dig läsa och skriva direkt till databaser på din lokala disk utan att behöva en separat serverprocess. Det gör det till ett användarvänligt alternativ för att lära sig utveckling av databasapplikationer.

En annan fördel med att använda SQLite är att biblioteket levereras med Python och även med PyQt, så du behöver inte installera något annat för att börja arbeta med det.

I PyQt kan du skapa en databasanslutning genom att använda QSqlDatabase klass. Denna klass representerar en anslutning och tillhandahåller ett gränssnitt för åtkomst till databasen. För att skapa en anslutning, ring bara .addDatabase()QSqlDatabase . Denna statiska metod tar en SQL-drivrutin och ett valfritt anslutningsnamn som argument och returnerar en databasanslutning:

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

Det första argumentet, driver , är ett obligatoriskt argument som innehåller en sträng som innehåller namnet på en PyQt-stödd SQL-drivrutin. Det andra argumentet, connectionName , är ett valfritt argument som innehåller en sträng med namnet på anslutningen. connectionName har som standard QSqlDatabase.defaultConnection , som normalt innehåller strängen "qt_sql_default_connection" .

Om du redan har en anslutning som heter connectionName , sedan tas den anslutningen bort och ersätts med en ny anslutning, och .addDatabase() returnerar den nyligen tillagda databasanslutningen till den som ringer.

Ett anrop till .addDatabase() lägger till en databasanslutning till en lista över tillgängliga anslutningar. Den här listan är ett globalt register som PyQt underhåller bakom kulisserna för att hålla reda på tillgängliga anslutningar i en applikation. Registrera dina anslutningar med ett meningsfullt connectionName låter dig hantera flera anslutningar i en databasapplikation.

När du har skapat en anslutning kan du behöva ställa in flera attribut på den. Den specifika uppsättningen attribut beror på drivrutinen du använder. I allmänhet måste du ställa in attribut som databasnamnet, användarnamnet och lösenordet för åtkomst till databasen.

Här är en sammanfattning av inställningsmetoderna som du kan använda för att ställa in de vanligare attributen eller egenskaperna för en databasanslutning:

Metod Beskrivning
.setDatabaseName(name) Ställer in databasnamnet till name , som är en sträng som representerar ett giltigt databasnamn
.setHostName(host) Ställer in värdnamnet till host , som är en sträng som representerar ett giltigt värdnamn
.setUserName(username) Ställer in användarnamnet till username , som är en sträng som representerar ett giltigt användarnamn
.setPassword(password) Ställer in lösenordet till password , som är en sträng som representerar ett giltigt lösenord

Observera att lösenordet du skickar som argument till .setPassword() lagras i vanlig text och kan hämtas senare genom att anropa .password() . Detta är en allvarlig säkerhetsrisk som du bör undvika att införa i din databasapplikationer. Du kommer att lära dig ett säkrare tillvägagångssätt i avsnittet Öppna en databasanslutning senare i den här självstudien.

För att skapa en anslutning till en SQLite-databas med QSqlDatabase , öppna en interaktiv Python-session och skriv in följande kod:

>>>
>>> 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'

Denna kod kommer att skapa ett databasanslutningsobjekt med "QSQLITE" som anslutningens drivrutin och "contacts.sqlite" som anslutningens databasnamn. Eftersom du inte skickar ett anslutningsnamn till .addDatabase() , den nyskapade blir din standardanslutning, vars namn är "qt_sql_default_connection" .

När det gäller SQLite-databaser är databasnamnet normalt ett filnamn eller en sökväg som inkluderar databasfilnamnet. Du kan också använda specialnamnet ":memory:" för en databas i minnet.



Hantera flera anslutningar

Det kan finnas situationer där du behöver använda flera anslutningar till en enda databas. Du kanske till exempel vill logga användarnas interaktioner med databasen med en specifik anslutning för varje användare.

I andra situationer kan du behöva koppla din applikation till flera databaser. Till exempel kanske du vill ansluta till flera fjärrdatabaser för att samla in data för att fylla i eller uppdatera en lokal databas.

För att hantera dessa situationer kan du ange specifika namn för dina olika anslutningar och referera till varje anslutning med dess namn. Om du vill ge din databasanslutning ett namn, skicka det namnet som det andra argumentet till .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'

Här skapar du två olika anslutningar till samma databas, contacts.sqlite . Varje anslutning har sitt eget anslutningsnamn. Du kan använda anslutningsnamnet för att få en referens till en specifik anslutning när som helst senare i din kod enligt dina behov. För att göra detta kan du anropa .database() med ett anslutningsnamn:

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

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

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

I det här exemplet ser du att .database() tar två argument:

  1. connectionName innehåller anslutningsnamnet som du behöver använda. Om du inte skickar ett anslutningsnamn kommer standardanslutningen att användas.
  2. open innehåller ett booleskt värde som talar om för .database() om du vill öppna anslutningen automatiskt eller inte. Om open är True (standard) och anslutningen inte är öppen, öppnas anslutningen automatiskt.

Returvärdet för .database() är en referens till anslutningsobjektet som heter connectionName . Du kan använda olika anslutningsnamn för att få referenser till specifika anslutningsobjekt och sedan använda dem för att hantera din databas.



Använda olika SQL-diverser

Hittills har du lärt dig hur du skapar en databasanslutning med SQLite-drivrutinen . Detta är inte den enda drivrutinen som finns i PyQt. Biblioteket tillhandahåller en rik uppsättning SQL-drivrutiner som låter dig använda olika typer av databashanteringssystem enligt dina specifika behov:

Förarnamn Databashanteringssystem
QDB2 IBM Db2 (version 7.1 och senare)
QIBASE Borland InterBase
QMYSQL/MARIADB MySQL eller MariaDB (version 5.0 och senare)
QOCI Oracle Call Interface
QODBC Open Database Connectivity (ODBC)
QPSQL PostgreSQL (version 7.3 och senare)
QSQLITE2 SQLite 2 (föråldrad sedan Qt 5.14)
QSQLITE SQLite 3
QTDS Sybase Adaptive Server (föråldrad sedan Qt 4.7)

Kolumnen Drivrutinsnamn innehåller identifieringssträngarna som du måste skicka till .addDatabase() som sitt första argument för att använda den associerade drivrutinen. Till skillnad från SQLite-drivrutinen, när du använder en annan drivrutin, kan du behöva ställa in flera attribut, till exempel databaseName , hostName , username och password , för att anslutningen ska fungera korrekt.

Databasdrivrutiner härleds från QSqlDriver . Du kan skapa dina egna databasdrivrutiner genom att underklassa QSqlDriver , men det ämnet går utöver den här handledningens omfattning. Om du är intresserad av att skapa dina egna databasdrivrutiner, kolla in Hur man skriver din egen databasdrivrutin för mer information.



Öppna en databasanslutning

När du har en databasanslutning måste du öppna den för att kunna interagera med din databas. För att göra det, anropar du .open() på anslutningsobjektet. .open() har följande två varianter:

  1. .open() öppnar en databasanslutning med de aktuella anslutningsvärdena.
  2. .open(username, password) öppnar en databasanslutning med det angivna username och password .

Båda varianterna returnerar True om anslutningen lyckas. Annars returnerar de False . Om anslutningen inte kan upprättas kan du anropa .lastError() för att få information om vad som hände. Denna funktion returnerar information om det senaste felet som rapporterats av databasen.

Obs! Som du lärt dig tidigare, .setPassword(password) lagrar lösenord som vanlig text, vilket är en säkerhetsrisk. Å andra sidan, .open() lagrar inga lösenord alls. Den skickar lösenordet direkt till föraren när anslutningen öppnas. Efter det kasserar den lösenordet. Så, med .open() att hantera dina lösenord är rätt väg att gå om du vill förhindra säkerhetsproblem.

Här är ett exempel på hur du öppnar en SQLite-databasanslutning med den första varianten av .open() :

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

I exemplet ovan skapar du först en anslutning till din SQLite-databas och öppnar den anslutningen med .open() . Sedan .open() returnerar True , anslutningen lyckades. Vid det här laget kan du kontrollera anslutningen med .isOpen() , som returnerar True om anslutningen är öppen och False annars.

Obs! Om du anropar .open() på en anslutning som använder SQLite-drivrutinen och databasfilen inte existerar, kommer en ny och tom databasfil att skapas automatiskt.

I verkliga applikationer måste du se till att du har en giltig anslutning till din databas innan du försöker utföra några åtgärder på dina data. Annars kan din applikation gå sönder och misslyckas. Till exempel, vad händer om du inte har skrivbehörighet för katalogen där du försöker skapa den databasfilen? Du måste se till att du hanterar alla fel som kan uppstå när du öppnar en anslutning.

Ett vanligt sätt att anropa .open() är att slå in det i ett villkorligt uttalande. Detta låter dig hantera fel som kan uppstå när anslutningen öppnas:

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

Avbryter anropet till .open() i en villkorssats kan du hantera alla fel som inträffar när du öppnar anslutningen. På så sätt kan du informera dina användare om eventuella problem innan programmet körs. Observera att applikationen avslutas med utgångsstatusen 1 , som vanligtvis används för att indikera ett programfel.

I exemplet ovan använder du .open() i en interaktiv session, så du använder print() att presentera felmeddelanden för användarna. Men i GUI-applikationer istället för att använda print() , använder du normalt en QMessageBox objekt. Med QMessageBox , kan du skapa små dialogrutor för att presentera information för dina användare.

Här är ett exempel på ett grafiskt användargränssnitt som illustrerar ett sätt att hantera anslutningsfel:

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

if uttalande i rad 14 kontrollerar om anslutningen misslyckades. Om /home/ katalogen finns inte eller om du inte har behörighet att skriva i den, anropet till .open() misslyckas eftersom databasfilen inte kan skapas. I denna situation, utförandeflödet anger if kodblock och visar ett meddelande på skärmen.

Om du ändrar sökvägen till någon annan katalog som du kan skriva i, kommer anropet till .open() kommer att lyckas och du kommer att se ett fönster som visar meddelandet Connection Successfully Opened! Du kommer också att ha en ny databasfil som heter contacts.sqlite i den valda katalogen.

Observera att du skickar None som meddelandets förälder eftersom du inte har skapat ett fönster än när du visar meddelandet, så du har inte en fungerande förälder för meddelanderutan.




Köra SQL-frågor med PyQt

Med en fullt fungerande databasanslutning är du redo att börja arbeta med din databas. För att göra det kan du använda strängbaserade SQL-frågor och QSqlQuery föremål. QSqlQuery låter dig köra vilken typ av SQL-fråga som helst i din databas. Med QSqlQuery , kan du köra DML-satser (data manipulation language), som SELECT , INSERT , UPDATE och DELETE , såväl som DDL-satser (Data Definition Language), som CREATE TABLE och så vidare.

Konstruktören för QSqlQuery har flera varianter, men i den här handledningen kommer du att lära dig om två av dem:

  1. QSqlQuery(query, connection) konstruerar ett frågeobjekt med hjälp av en strängbaserad SQL query och en databas connection . Om du inte anger en anslutning, eller om den angivna anslutningen är ogiltig, används standarddatabasanslutningen. Om query inte är en tom sträng, kommer den att köras direkt.

  2. QSqlQuery(connection) konstruerar ett frågeobjekt med connection . Om connection är ogiltig används standardanslutningen.

Du kan också skapa QSqlQuery objekt utan att skicka några argument till konstruktorn. I så fall kommer frågan att använda standarddatabasanslutningen, om någon.

För att utföra en fråga måste du anropa .exec() på frågeobjektet. Du kan använda .exec() på två olika sätt:

  1. .exec(query) exekverar den strängbaserade SQL-frågan som finns i query . Den returnerar True om frågan lyckades och annars returnerar False .

  2. .exec() exekverar en tidigare förberedd SQL-fråga. Den returnerar True om frågan lyckades och annars returnerar False .

Obs! PyQt implementerar även varianter av QSqlQuery.exec() med namnet .exec_() . Dessa ger bakåtkompatibilitet med äldre versioner av Python där exec var ett nyckelord för språket.

Nu när du känner till grunderna för att använda QSqlQuery för att skapa och köra SQL-frågor är du redo att lära dig hur du omsätter din kunskap i praktiken.


Köra statiska SQL-frågor

För att börja skapa och köra frågor med PyQt kommer du att starta din favoritkodredigerare eller IDE och skapa ett Python-skript som heter queries.py . Spara skriptet och lägg till följande kod i det:

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

I det här skriptet börjar du med att importera modulerna och klasserna som du ska arbeta med. Sedan skapar du en databasanslutning med .addDatabase() med SQLite-drivrutinen. Du ställer in databasnamnet till "contacts.sqlite" och öppna anslutningen.

För att skapa din första fråga, instansierar du QSqlQuery utan några argument. Med frågeobjektet på plats anropar du .exec() , skickar en strängbaserad SQL-fråga som ett argument. Den här typen av fråga kallas en statisk fråga eftersom den inte får några parametrar utanför frågan.

Ovanstående SQL-fråga skapar en ny tabell som heter contacts i din databas. Den tabellen kommer att ha följande fyra kolumner:

Kolumn Innehåll
id Ett heltal med tabellens primärnyckel
name En sträng med namnet på en kontakt
job En sträng med jobbtiteln för en kontakt
email En sträng med e-postadressen för en kontakt

Den sista raden i skriptet ovan skriver ut listan över tabeller som finns i din databas. Om du kör skriptet kommer du att notera att en ny databasfil som heter contacts.sqlite skapas i din nuvarande katalog. Du får också något som ['contacts', 'sqlite_sequence'] skrivs ut på din skärm. Den här listan innehåller namnen på tabellerna i din databas.

Obs! En strängbaserad SQL-fråga måste använda en lämplig syntax enligt den specifika SQL-databas som du frågar efter. Om syntaxen är fel, då .exec() ignorerar frågan och returnerar False .

I fallet med SQLite kan frågan endast innehålla en sats åt gången.

Anropar .exec() på en QSqlQuery object är ett vanligt sätt att omedelbart exekvera strängbaserade SQL-frågor på dina databaser, men vad händer om du vill förbereda dina frågor i förväg för senare exekvering? Det är ämnet för nästa avsnitt.



Köra dynamiska frågor:Strängformatering

Hittills har du lärt dig hur man kör statiska frågor på en databas. Statiska frågor är sådana som inte accepterar parametrar , så frågan körs som den är. Även om dessa frågor är ganska användbara, behöver du ibland skapa frågor som hämtar data som svar på vissa indataparametrar.

Frågor som accepterar parametrar vid körning kallas dynamiska frågor . Genom att använda parametrar kan du finjustera frågan och hämta data som svar på specifika parametervärden. Olika värden ger olika resultat. Du kan ta indataparametrar i en fråga genom att använda en av följande två metoder:

  1. Skapa frågan dynamiskt, använd strängformatering för att interpolera parametervärden.
  2. Förbered frågan med platshållarparametrar och bind sedan specifika värden till parametrar.

Den första metoden låter dig skapa dynamiska frågor snabbt. Men för att använda detta tillvägagångssätt på ett säkert sätt måste du vara säker på att dina parametervärden kommer från en pålitlig källa. Annars kan du möta SQL-injektionsattacker.

Här är ett exempel på hur man använder strängformatering för att skapa dynamiska frågor i 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

I det här exemplet använder du en f-sträng för att skapa en dynamisk fråga genom att interpolera specifika värden till en strängbaserad SQL-fråga. Den sista frågan infogar data i dina contacts tabell, som nu innehåller data om Linda .

Obs! Längre fram i den här handledningen kommer du att se hur du hämtar och navigerar i data som lagras i en databas.

Observera att för att den här typen av dynamisk fråga ska fungera måste du se till att värdena som ska infogas har rätt datatyp. Så du använder enkla citattecken runt platshållaren i f-strängen eftersom dessa värden måste vara strängar.



Köra dynamiska frågor:Platshållarparametrar

Den andra metoden för att utföra dynamiska frågor kräver att du förbereder dina frågor i förväg med en mall med platshållare för parametrar. PyQt stöder två parameterplatshållarstilar:

  1. Oracle-stil använder namngivna platshållare som :name eller :email .
  2. ODBC-stil använder ett frågetecken (? ) som en positionell platshållare.

Observera att dessa stilar inte kan blandas i samma fråga. Du kan kolla in Approaches to Binding Values ​​för extra exempel på hur du använder platshållare.

Obs! ODBC står för Open Database Connectivity.

För att skapa den här typen av dynamisk fråga i PyQt skapar du först en mall med en platshållare för varje frågeparameter och skickar sedan den mallen som ett argument till .prepare() , som analyserar, kompilerar och förbereder frågemallen för exekvering. Om mallen har några problem, till exempel ett SQL-syntaxfel, .prepare() misslyckas med att kompilera mallen och returnerar False .

Om förberedelseprocessen lyckas, prepare() returnerar True . Efter det kan du skicka ett specifikt värde till varje parameter med .bindValue() med namngivna eller positionella parametrar eller med .addBindValue() med positionsparametrar. .bindValue() har följande två varianter:

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

I den första varianten placeholder representerar en platshållare i Oracle-stil. I den andra varianten, pos representerar ett nollbaserat heltal med positionen för en parameter i frågan. I båda varianterna, val innehåller värdet som ska bindas till en specifik parameter.

.addBindValue() lägger till ett värde till listan över platshållare som använder positionsbindning. Detta innebär att ordningen på anropen till .addBindValue() bestämmer vilket värde som kommer att bindas till varje platshållarparameter i den förberedda frågan.

För att börja använda förberedda frågor kan du förbereda en INSERT INTO SQL-sats för att fylla din databas med några exempeldata. Gå tillbaka till skriptet som du skapade i avsnittet Executing Static SQL Queries och lägg till följande kod direkt efter anropet till print() :

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

Det första steget är att skapa en QSqlQuery objekt. Sedan anropar du .prepare() på frågeobjektet. I det här fallet använder du ODBC-stilen för platshållarna. Din fråga tar värden för din kontakts name , job och email , så du behöver tre platshållare. Sedan id kolumn är ett autoinkrementerat heltal, du behöver inte ange värden för det.

Sedan skapar du några exempeldata för att fylla i databasen. data innehåller en lista med tupler, och varje tupel innehåller tre objekt:namn, jobb och e-postadress för varje kontakt.

Det sista steget är att binda de värden som du vill skicka till varje platshållare och sedan anropa .exec() för att utföra frågan. För att göra det använder du en for slinga. Slinghuvudet packar upp varje tupel i data i tre separata variabler med lämpliga namn. Then you call .addBindValue() on the query object to bind the values to the placeholders.

Note that you’re using positional placeholders , 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:

Method 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.

Obs! 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 tabell. 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() again. 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() returns -1 . 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() .

Obs! 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 objects. 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 objekt:

  • 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 databas:

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 Beskrivning
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.

That’s it! 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.



Conclusion

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.



  1. Eftersom du behöver känna till PowerShell

  2. hur gör man anslutningspooling i java?

  3. Oracle 12c Toppnya funktioner

  4. Hur man får poster från de senaste 10 minuterna i MySQL