這期是PyQt5與資料庫互聯小例子的最後一期,這期是完成主程序。
本期我們將運用到兩個類:QSqlDatabase類、QSqlQuery類。
QSqlDatabase類:主要用在資料庫連接方面。
QSqlQuery類:主要用在資料庫查詢方面。
具體的介紹如下:
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()查看給定的連接名稱是否在連接列表中。
一些實用方法:
注意:不推薦使用QSqlDatabase.exec()。 請改用QSqlQuery.exec()。
注意:使用事務時,必須在創建查詢之前啟動事務。
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()。
使用以下功能執記錄:
檢索結果中的下一條記錄(如果可用),並將查詢定位在檢索到的記錄上。 請注意,結果必須處於活動狀態,並且isSelect()必須在調用此函數之前返回True,否則它將不執行任何操作並返回False。
以下規則適用:
如果結果當前位於第一條記錄之前,在執行查詢後立即嘗試檢索第一條記錄。
檢索結果中的上一條記錄(如果可用),並將查詢定位在檢索到的記錄上。 請注意,結果必須處於活動狀態,並且isSelect()必須在調用此函數之前返回True,否則它將不執行任何操作並返回False。
如果結果當前位於第一條記錄之前,則沒有更改,並返回False。
如果結果位於中間某處,則嘗試檢索先前的記錄。
檢索結果中的第一條記錄(如果可用),並將查詢定位在檢索到的記錄上。 請注意,結果必須處於活動狀態,並且isSelect()必須在調用此函數之前返回True,否則它將不執行任何操作並返回False。 如果成功則返回True。 如果不成功,則將查詢位置設置為無效位置並返回False。
檢索結果中的最後一條記錄(如果可用),並將查詢定位在檢索到的記錄上。 請注意,結果必須處於活動狀態,並且isSelect()必須在調用此函數之前返回True,否則它將不執行任何操作並返回False。 如果成功則返回True。 如果不成功,則將查詢位置設置為無效位置並返回False。
檢索位置索引處的記錄(如果可用),並將查詢定位在檢索到的記錄上。第一個記錄位於位置0。請注意,查詢必須處於活動狀態,並且isSelect()必須在調用此函數之前返回True。
如果relative為False(默認值),則適用以下規則:
如果index為負數,則結果位於第一個記錄之前,並返回False。否則,嘗試移動到位置索引處的記錄。如果無法檢索位置索引處的記錄,則結果將定位在最後一條記錄之後,並返回False。如果成功檢索到記錄,則返回True。
如果relative為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,可以獲得相應的源碼!歡迎修改、完善、分享。加油!(? ??_??)? (*????)
分享地址: