這期是PyQt5與資料庫互聯小例子的最後一期,這期是完成主程序。

基礎知識

本期我們將運用到兩個類:QSqlDatabase類、QSqlQuery類。

QSqlDatabase類:主要用在資料庫連接方面。

QSqlQuery類:主要用在資料庫查詢方面。

具體的介紹如下:

QSqlDatabase

QSqlDatabase類處理與資料庫的連接。

QSqlDatabase類提供了通過連接訪問資料庫的介面。

QSqlDatabase的實例表示連接。該連接通過一個受支持的資料庫驅動程序提供對資料庫的訪問,這些驅動程序源自QSqlDriver。

目前可用的數據支持類型如下:

通過調用其中一個靜態addDatabase()函數創建連接(即QSqlDatabase的實例),您可以在其中指定要使用的驅動程序或驅動程序類型(取決於資料庫的類型)和連接名稱。

您可以與一個資料庫建立多個連接。 QSqlDatabase還支持默認連接的概念,即未命名的連接。要創建默認連接,請在調用addDatabase()時不要傳遞連接名稱參數。隨後,如果在未指定連接名稱的情況下調用任何靜態成員函數,則將假定默認連接。以下代碼段顯示瞭如何創建和打開PostgreSQL資料庫的默認連接:

db = QSqlDatabase.addDatabase("QPSQL")
db.setHostName("acidalia")
db.setDatabaseName("customdb")
db.setUserName("mojito")
db.setPassword("J0a1m8")
ok = db.open()

創建QSqlDatabase對象後,使用setDatabaseName(),setUserName(),setPassword(),setHostName(),setPort()和setConnectOptions()設置連接參數。然後調用open()以激活與資料庫的物理連接。在打開連接之前,該連接無法使用。

上面定義的連接將是默認連接,因為我們沒有為addDatabase()提供連接名稱。隨後,您可以通過調用沒有連接名稱參數的database()來獲取默認連接:

db = QSqlDatabase.database()

QSqlDatabase是一個值類。通過一個QSqlDatabase實例對資料庫連接所做的更改將影響表示同一連接的其他QSqlDatabase實例。使用cloneDatabase()基於現有資料庫連接創建獨立的資料庫連接。

強烈建議您不要將QSqlDatabase的副本作為類的成員保留,因為這會阻止在關閉時正確清除實例。如果需要訪問現有的QSqlDatabase,則應使用database()訪問它。如果您選擇擁有QSqlDatabase成員變數,則需要在刪除QCoreApplication實例之前將其刪除,否則可能會導致未定義的行為。

如果創建多個資料庫連接,請在調用addDatabase()時為每個連接指定唯一的連接名稱。使用帶有連接名稱的database()來獲取該連接。將removeDatabase()與連接名稱一起使用以刪除連接。如果嘗試刪除其他QSqlDatabase對象引用的連接,QSqlDatabase將輸出警告。使用contains()查看給定的連接名稱是否在連接列表中。

一些實用方法:

  • tables():返回表的列表
  • primaryIndex():返回表的主索引
  • record():返回有關表的欄位的元信息
  • transaction():啟動一個事務
  • commit():保存並完成事務
  • rollback():取消事務
  • hasFeature():檢查驅動程序是否支持事務
  • lastError():返回有關上一個錯誤的信息
  • drivers():返回可用SQL驅動程序的名稱
  • isDriverAvailable():檢查特定驅動程序是否可用
  • registerSqlDriver():註冊一個定製的驅動程序

注意:不推薦使用QSqlDatabase.exec()。 請改用QSqlQuery.exec()。

注意:使用事務時,必須在創建查詢之前啟動事務。

QSqlQuery

QSqlQuery類提供了一種執行和操作SQL語句的方法。

QSqlQuery封裝了在QSqlDatabase上執行的SQL查詢中創建,導航和檢索數據所涉及的功能。它可用於執行DML(數據操作語言)語句,例如SELECT,INSERT,UPDATE和DELETE,以及DDL(數據定義語言)語句,例如CREATE TABLE。它還可以用於執行非標準SQL的特定於資料庫的命令(例如,對於PostgreSQL,SET DATESTYLE = ISO)。

成功執行SQL語句將查詢的狀態設置為活動狀態,以便isActive()返回True。否則,查詢的狀態將設置為非活動狀態。在任何一種情況下,執行新的SQL語句時,查詢都位於無效記錄上。必須先將活動查詢導航到有效記錄(以便isValid()返回True),然後才能檢索值。

對於某些資料庫,如果在調用commit()或rollback()時存在作為SELECT語句的活動查詢,則提交或回滾將失敗。有關詳細信息,請參閱isActive()。

使用以下功能執記錄:

  • next():

檢索結果中的下一條記錄(如果可用),並將查詢定位在檢索到的記錄上。 請注意,結果必須處於活動狀態,並且isSelect()必須在調用此函數之前返回True,否則它將不執行任何操作並返回False。

以下規則適用:

如果結果當前位於第一條記錄之前,在執行查詢後立即嘗試檢索第一條記錄。

如果結果當前位於最後一條記錄之後,則沒有更改,並返回False。如果結果位於中間某處,則嘗試檢索下一條記錄。如果無法檢索記錄,則結果將定位在最後一條記錄之後,並返回False。 如果成功檢索到記錄,則返回True。
  • previous():

檢索結果中的上一條記錄(如果可用),並將查詢定位在檢索到的記錄上。 請注意,結果必須處於活動狀態,並且isSelect()必須在調用此函數之前返回True,否則它將不執行任何操作並返回False。

以下規則適用:

如果結果當前位於第一條記錄之前,則沒有更改,並返回False。

如果結果當前位於最後一條記錄之後,則嘗試檢索最後一條記錄。

如果結果位於中間某處,則嘗試檢索先前的記錄。

如果無法檢索記錄,則結果將位於第一個記錄之前,並返回False。 如果成功檢索到記錄,則返回True。
  • first():

檢索結果中的第一條記錄(如果可用),並將查詢定位在檢索到的記錄上。 請注意,結果必須處於活動狀態,並且isSelect()必須在調用此函數之前返回True,否則它將不執行任何操作並返回False。 如果成功則返回True。 如果不成功,則將查詢位置設置為無效位置並返回False。

  • last():

檢索結果中的最後一條記錄(如果可用),並將查詢定位在檢索到的記錄上。 請注意,結果必須處於活動狀態,並且isSelect()必須在調用此函數之前返回True,否則它將不執行任何操作並返回False。 如果成功則返回True。 如果不成功,則將查詢位置設置為無效位置並返回False。

  • seek(index, relative = False):

檢索位置索引處的記錄(如果可用),並將查詢定位在檢索到的記錄上。第一個記錄位於位置0。請注意,查詢必須處於活動狀態,並且isSelect()必須在調用此函數之前返回True。

如果relative為False(默認值),則適用以下規則:

如果index為負數,則結果位於第一個記錄之前,並返回False。否則,嘗試移動到位置索引處的記錄。如果無法檢索位置索引處的記錄,則結果將定位在最後一條記錄之後,並返回False。如果成功檢索到記錄,則返回True。

如果relative為True,則適用以下規則:

如果結果當前位於第一條記錄之前,則:

  • index為負數或零,沒有變化,返回False。
  • index為正數,嘗試將結果置於絕對位置index-1,遵循上述非相對搜索的相同規則。

如果結果當前位於最後一條記錄之後,則:

  • index為正數或零,沒有變化,返回False。
  • index為負,嘗試按照以下規則將結果定位在距上一條記錄的索引+ 1相對位置。
  • 如果結果當前位於中間某處,並且相對偏移索引將結果移動到零以下,則結果將位於第一個記錄之前,並返回False。
  • 否則,嘗試移動到當前記錄之前的記錄索引記錄(如果索引為負,則轉移到當前記錄之後的索引記錄)。如果無法檢索偏移索引處的記錄,則結果將定位在最後一條記錄之後,如果index> = 0,(或者在第一條記錄之前,如果索引為負數),則返回False。如果成功檢索到記錄,則返回True。

這些函數允許程序員通過查詢返回的記錄向前,向後或任意移動。 如果您只需要繼續查看結果(例如,使用next()),則可以使用setForwardOnly(),這將節省大量內存開銷並提高某些資料庫的性能。 將活動查詢定位在有效記錄上後,可以使用value()檢索數據。 使用QVariants從SQL後端傳輸所有數據。

例如:

query = QSqlQuery("SELECT country FROM artist")
while query.next():
country = query.value(0)
doSomething(country)

要訪問查詢返回的數據,請使用value(int)。 SELECT語句返回的數據中的每個欄位都是通過從0開始在語句中傳遞欄位的位置來訪問的。這使得使用SELECT *查詢是不可取的,因為返回的欄位的順序是不確定的。

為了提高效率,沒有按名稱訪問欄位的功能(除非您使用帶有名稱的預準備查詢,如下所述)。要將欄位名稱轉換為索引,請使用record()。indexOf(),例如:

query = QSqlQuery("SELECT * FROM artist")
fieldNo = query.record().indexOf("country")
while query.next():
country = query.value(fieldNo)
doSomething(country)

核心代碼講解

def connectDB():
db = QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName("./res/db/book.db")
if not db.open():
QMessageBox.critical(None, "嚴重錯誤", "數據連接失敗,程序無法使用,請按取消鍵退出", QMessageBox.Cancel)
return False
else:
return db

我們首先要連接資料庫,addDatabase()使用驅動程序類型和連接名稱connectionName(這裡是」QSQLITE」)將資料庫添加到資料庫連接列表中。如果已存在名為connectionName的資料庫連接,則刪除該連接。

設置資料庫名稱,並調用open()打開,我們會看看是否打開成功?成功返回db,資料庫連接對象;否則返回False。

def setTableModel(self):
self.tablemodel = BookSqlTableModel()
self.tableView.setModel(self.tablemodel)
self.tablemodel.setEditStrategy(QSqlTableModel.OnFieldChange)
self.tablemodel.setTable("books")
self.tablemodel.select()
self.tablemodel.setHeaderData(0, Qt.Horizontal, "國家(地區)")
self.tablemodel.setHeaderData(1, Qt.Horizontal, "ISBN")
self.tablemodel.setHeaderData(2, Qt.Horizontal, "書名")
self.tablemodel.setHeaderData(3, Qt.Horizontal, "作者")
self.tablemodel.setHeaderData(4, Qt.Horizontal, "出版單位")
self.tablemodel.setHeaderData(5, Qt.Horizontal, "圖書分類")
self.tablemodel.setHeaderData(6, Qt.Horizontal, "定價")
self.tableView.hideColumn(7)
self.tableView.hideColumn(8)
self.tableView.hideColumn(9)
self.tableView.hideColumn(10)

對資料庫以及數據模型進行一些設置。

self.tablemodel = BookSqlTableModel()
self.tableView.setModel(self.tablemodel)

設置要顯示的視圖的模型,這裡的模型就是在

學點編程吧:PyQt5系列教程(62):PyQt5與資料庫互聯的小例子2?

zhuanlan.zhihu.com圖標

裡面提到自定義數據模型裡面提到自定義數據模型

self.tablemodel.setEditStrategy(QSqlTableModel.OnFieldChange)

設置資料庫中的值編輯策略。這裡有三種,如下表:

self.tablemodel.setTable("books")
self.tablemodel.select()

將模型操作的資料庫表設置為tableName。 不從表中選擇數據,而是獲取其欄位信息。

要使用表的數據填充模型,請調用select()。可以使用lastError()檢索錯誤信息。

self.tablemodel.setHeaderData(0, Qt.Horizontal, "國家(地區)")
self.tablemodel.setHeaderData(1, Qt.Horizontal, "ISBN")
self.tablemodel.setHeaderData(2, Qt.Horizontal, "書名")
self.tablemodel.setHeaderData(3, Qt.Horizontal, "作者")
self.tablemodel.setHeaderData(4, Qt.Horizontal, "出版單位")
self.tablemodel.setHeaderData(5, Qt.Horizontal, "圖書分類")
self.tablemodel.setHeaderData(6, Qt.Horizontal, "定價")

設置表頭內容,沒有這幾句是這樣的:

self.tableView.hideColumn(7)
self.tableView.hideColumn(8)
self.tableView.hideColumn(9)
self.tableView.hideColumn(10)

隱藏一些我不想顯示的欄位,如:圖書封面的路徑。沒有這幾句效果如下:

def contextMenuEvent(self, event):
pmenu = QMenu(self)
pDeleteAct = QAction(刪除行,self.tableView)
pmenu.addAction(pDeleteAct)
pmenu.popup(self.mapToGlobal(event.pos()))
pDeleteAct.triggered.connect(self.deleterows)

右鍵菜單,單擊後直接連接到deleterows()函數。

def deleterows(self):
rr = QMessageBox.warning(self, "注意", "確認刪除數據?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if rr == QMessageBox.Yes:
selectedIndexes = self.tableView.selectedIndexes()
selectedRows = self.filter(selectedIndexes)
for row in reversed(selectedRows):
self.tablemodel.removeRow(row)
self.tablemodel.select()

def filter(self, selectedIndexes):
filtered = []
for s in selectedIndexes:
filtered.append(s.row())
return list(set(filtered))

這兩個函數的主要目的是取得我們要刪除的行,然後再加以刪除。

selectedIndexes = self.tableView.selectedIndexes()

返回所有選定模型項索引的列表。

selectedRows = self.filter(selectedIndexes)
def filter(self, selectedIndexes):
filtered = []
for s in selectedIndexes:
filtered.append(s.row())
return list(set(filtered))

我們根據返回的索引列表,去重一下,取得相應的行號。大家可以看一下,如果我們選擇一整行,如下圖這種:

會有很多重複的行號。為什麼會這樣呢?因為一行裡面有很多的QModelIndex對象組成的列表,這些QModelIndex對象的行號都是一樣的。

@pyqtSlot(QModelIndex)
def on_tableView_clicked(self, index):
row = index.row()
self.label_country.setText(self.tablemodel.record(row).value("country"))
self.label_isbn.setText(self.tablemodel.record(row).value("isbn"))
self.label_bookname.setText(self.tablemodel.record(row).value("subtitle"))
self.label_author.setText(self.tablemodel.record(row).value("author"))
self.label_publisher.setText(self.tablemodel.record(row).value("publisher"))
self.label_price.setText(str(self.tablemodel.record(row).value("price")))
self.label_pubdate.setText(self.tablemodel.record(row).value("pubdate"))
self.label_classification.setText(self.tablemodel.record(row).value("classification"))
self.label_pages.setText(str(self.tablemodel.record(row).value("pages")))
self.textBrowser.setText(self.tablemodel.record(row).value("summary"))
imgPath = self.tablemodel.record(row).value("img")
self.label.setPixmap(QPixmap(imgPath))

單擊顯示圖書詳細信息。

row = index.row()
self.tablemodel.record(row).value("subtitle")

當我們單擊表格的時候,取得行號。

因為我們的數據來源都是數據模型,我們就根據模型中第幾行第幾個欄位取得相應的值,如下,下圖表示的是第3行(0行開始)」subtitle」的內容。

@pyqtSlot()
def on_pushButton_search_clicked(self):
searchtext = self.lineEdit.text()
if searchtext:
if self.comboBox.currentText() == "ISBN":
queryIsbn = "isbn = {}".format(searchtext)
self.tablemodel.setFilter(queryIsbn)
elif self.comboBox.currentText() == "書名":
querySubtile = "subtitle = {}".format(searchtext)
self.tablemodel.setFilter(querySubtile)
elif self.comboBox.currentText() == "作者":
queryAuthor= "author = {}".format(searchtext)
self.tablemodel.setFilter(queryAuthor)
else:
self.setTableModel()

這裡的重點語句就是:

self.tablemodel.setFilter(queryIsbn)

設置要過濾的當前過濾器。過濾器是不帶關鍵字WHERE的SQL WHERE子句(例如,name =』Josephine』)。

如果模型已填充了資料庫中的數據,則模型將使用新過濾器重新選擇它。 否則,將在下次調用select()時應用過濾器。

如:

querySubtile = "subtitle = {}".format(searchtext)
self.tablemodel.setFilter(querySubtile)

就表示類似如下的SQL語句:

select * from books where subtitle = 你一生的故事

最後

好的,關於PyQt5與資料庫互聯的介紹就到這裡了。如果你喜歡本篇文章,請給我點贊

讚賞(推薦)

分享給你的好友們吧!

歡迎關注微信公眾號:學點編程吧,發送:pyqt564,可以獲得相應的源碼!歡迎修改、完善、分享。加油!(? ??_??)? (*????)

分享地址:

學點編程吧-百度貼吧--計算機程序學習的園地!--學點編程吧,讓我們的生活更簡單,更高效!能用計算機解決的事情,盡量不要讓人解決。如果你在學習當中有任何疑問、學習心得、職業發展等內容歡迎在貼吧中分享,讓我?

tieba.baidu.com
圖標

推薦閱讀:
相關文章