Xây dựng các ứng dụng sử dụng cơ sở dữ liệu SQL là một công việc lập trình khá phổ biến. Cơ sở dữ liệu SQL có ở khắp mọi nơi và có sự hỗ trợ tuyệt vời bằng Python. Trong lập trình GUI, PyQt cung cấp hỗ trợ cơ sở dữ liệu SQL mạnh mẽ và đa nền tảng cho phép bạn tạo, kết nối và quản lý cơ sở dữ liệu của mình một cách nhất quán.
Hỗ trợ PyQt’s SQL hoàn toàn tích hợp với kiến trúc Model-View của nó để giúp bạn trong quá trình xây dựng các ứng dụng cơ sở dữ liệu.
Trong hướng dẫn này, bạn sẽ học cách:
- Sử dụng hỗ trợ SQL của PyQt để kết nối đáng tin cậy với cơ sở dữ liệu
- Thực thi Truy vấn SQL trên cơ sở dữ liệu bằng PyQt
- Sử dụng kiến trúc Chế độ xem mô hình của PyQt trong các ứng dụng cơ sở dữ liệu
- Hiển thị và chỉnh sửa dữ liệu bằng các tiện ích con PyQt khác nhau
Các ví dụ trong hướng dẫn này yêu cầu kiến thức cơ bản về ngôn ngữ SQL, đặc biệt là về hệ quản trị cơ sở dữ liệu SQLite. Một số kiến thức trước đây về lập trình GUI với Python và PyQt cũng sẽ hữu ích.
Phần thưởng miễn phí: 5 Suy nghĩ khi Làm chủ Python, một khóa học miễn phí dành cho các nhà phát triển Python, cho bạn thấy lộ trình và tư duy mà bạn sẽ cần để nâng các kỹ năng Python của mình lên một cấp độ tiếp theo.
Kết nối PyQt với Cơ sở dữ liệu SQL
Kết nối ứng dụng với cơ sở dữ liệu quan hệ và yêu cầu ứng dụng tạo, đọc, cập nhật và xóa dữ liệu được lưu trữ trong cơ sở dữ liệu đó là một nhiệm vụ phổ biến trong lập trình. Cơ sở dữ liệu quan hệ thường được tổ chức thành một tập hợp các bảng hoặc quan hệ . Một hàng nhất định trong bảng được gọi là bản ghi hoặc tuple và một cột được gọi là thuộc tính .
Lưu ý: Thuật ngữ trường thường được sử dụng để xác định một phần dữ liệu được lưu trữ trong một ô của một bản ghi nhất định trong một bảng. Mặt khác, thuật ngữ tên trường được sử dụng để xác định tên của một cột.
Mỗi cột lưu trữ một loại thông tin cụ thể, chẳng hạn như tên, ngày tháng hoặc số. Mỗi hàng đại diện cho một tập hợp dữ liệu có liên quan chặt chẽ và mọi hàng đều có cấu trúc chung giống nhau. Ví dụ:trong cơ sở dữ liệu lưu trữ dữ liệu về nhân viên trong một công ty, một hàng cụ thể đại diện cho từng nhân viên.
Hầu hết các hệ thống cơ sở dữ liệu quan hệ sử dụng SQL (ngôn ngữ truy vấn có cấu trúc) để truy vấn, thao tác và duy trì dữ liệu được lưu giữ trong cơ sở dữ liệu. SQL là một ngôn ngữ lập trình dành riêng cho miền và khai báo được thiết kế đặc biệt để giao tiếp với cơ sở dữ liệu.
Hệ thống cơ sở dữ liệu quan hệ và SQL được sử dụng rộng rãi ngày nay. Bạn sẽ tìm thấy một số hệ thống quản lý cơ sở dữ liệu khác nhau, chẳng hạn như SQLite, PostgreSQL, MySQL, MariaDB và nhiều hệ thống khác. Bạn có thể kết nối Python với bất kỳ hệ thống cơ sở dữ liệu nào trong số này bằng thư viện Python SQL chuyên dụng.
Lưu ý: Mặc dù hỗ trợ SQL tích hợp của PyQt là tùy chọn ưu tiên để quản lý cơ sở dữ liệu SQL trong PyQt, bạn cũng có thể sử dụng bất kỳ thư viện nào khác để xử lý kết nối cơ sở dữ liệu. Một số thư viện này bao gồm SQLAlchemy, pandas, SQLite, v.v.
Tuy nhiên, việc sử dụng một thư viện khác để quản lý cơ sở dữ liệu của bạn có một số hạn chế. Bạn sẽ không thể tận dụng lợi thế của việc tích hợp giữa các lớp SQL của PyQt và kiến trúc Chế độ xem mô hình. Ngoài ra, bạn sẽ thêm các phần phụ thuộc bổ sung vào ứng dụng của mình.
Khi nói đến lập trình GUI với Python và PyQt, PyQt cung cấp một tập hợp các lớp mạnh mẽ để làm việc với cơ sở dữ liệu SQL. Tập hợp các lớp này sẽ là đồng minh tốt nhất của bạn khi bạn cần kết nối ứng dụng của mình với cơ sở dữ liệu SQL.
Lưu ý: Rất tiếc, tài liệu chính thức của PyQt5 có một số phần chưa hoàn chỉnh. Để giải quyết vấn đề này, bạn có thể xem tài liệu PyQt4, tài liệu Qt cho Python hoặc tài liệu Qt gốc. Trong hướng dẫn này, một số liên kết đưa bạn đến tài liệu Qt gốc, đây là nguồn thông tin tốt hơn trong hầu hết các trường hợp.
Trong hướng dẫn này, bạn sẽ tìm hiểu các kiến thức cơ bản về cách sử dụng hỗ trợ SQL của PyQt để tạo các ứng dụng GUI tương tác đáng tin cậy với cơ sở dữ liệu quan hệ để đọc, ghi, xóa và hiển thị dữ liệu.
Tạo kết nối cơ sở dữ liệu
Kết nối các ứng dụng của bạn với cơ sở dữ liệu SQL vật lý là một bước quan trọng trong quá trình phát triển các ứng dụng cơ sở dữ liệu với PyQt. Để thực hiện thành công bước này, bạn cần một số thông tin chung về cách cơ sở dữ liệu của bạn được thiết lập.
Ví dụ:bạn cần biết cơ sở dữ liệu của bạn được xây dựng trên hệ thống quản lý cơ sở dữ liệu nào và bạn cũng có thể cần phải có tên người dùng, mật khẩu, tên máy chủ, v.v.
Trong hướng dẫn này, bạn sẽ sử dụng SQLite 3, là một hệ thống cơ sở dữ liệu đã được thử nghiệm tốt với sự hỗ trợ trên tất cả các nền tảng và yêu cầu cấu hình tối thiểu. SQLite cho phép bạn đọc và ghi trực tiếp vào cơ sở dữ liệu trong đĩa cục bộ của bạn mà không cần một quy trình máy chủ riêng biệt. Điều đó làm cho nó trở thành một tùy chọn thân thiện với người dùng để học phát triển ứng dụng cơ sở dữ liệu.
Một lợi thế khác của việc sử dụng SQLite là thư viện đi kèm với Python và cả PyQt, vì vậy bạn không cần cài đặt bất kỳ thứ gì khác để bắt đầu làm việc với nó.
Trong PyQt, bạn có thể tạo kết nối cơ sở dữ liệu bằng cách sử dụng QSqlDatabase
lớp. Lớp này đại diện cho một kết nối và cung cấp một giao diện để truy cập cơ sở dữ liệu. Để tạo kết nối, chỉ cần gọi .addDatabase()
trên QSqlDatabase
. Phương thức tĩnh này lấy trình điều khiển SQL và tên kết nối tùy chọn làm đối số và trả về kết nối cơ sở dữ liệu:
QSqlDatabase.addDatabase(
driver, connectionName=QSqlDatabase.defaultConnection
)
Đối số đầu tiên, driver
, là một đối số bắt buộc chứa một chuỗi chứa tên của trình điều khiển SQL được PyQt hỗ trợ. Đối số thứ hai, connectionName
, là một đối số tùy chọn chứa một chuỗi với tên của kết nối. connectionName
mặc định thành QSqlDatabase.defaultConnection
, thường chứa chuỗi "qt_sql_default_connection"
.
Nếu bạn đã có một kết nối được gọi là connectionName
, sau đó kết nối đó bị xóa và thay thế bằng kết nối mới và .addDatabase()
trả lại kết nối cơ sở dữ liệu mới được thêm vào người gọi.
Một lệnh gọi đến .addDatabase()
thêm kết nối cơ sở dữ liệu vào danh sách các kết nối có sẵn. Danh sách này là sổ đăng ký toàn cầu mà PyQt duy trì đằng sau hậu trường để theo dõi các kết nối khả dụng trong một ứng dụng. Đăng ký kết nối của bạn bằng một connectionName
có ý nghĩa sẽ cho phép bạn quản lý một số kết nối trong một ứng dụng cơ sở dữ liệu.
Sau khi tạo kết nối, bạn có thể cần đặt một số thuộc tính trên đó. Bộ thuộc tính cụ thể sẽ phụ thuộc vào trình điều khiển bạn đang sử dụng. Nói chung, bạn sẽ cần đặt các thuộc tính như tên cơ sở dữ liệu, tên người dùng và mật khẩu để truy cập cơ sở dữ liệu.
Dưới đây là tóm tắt về các phương thức setter mà bạn có thể sử dụng để đặt các thuộc tính hoặc thuộc tính thường được sử dụng hơn của kết nối cơ sở dữ liệu:
Phương pháp | Mô tả |
---|---|
.setDatabaseName(name) | Đặt tên cơ sở dữ liệu thành name , là một chuỗi đại diện cho một tên cơ sở dữ liệu hợp lệ |
.setHostName(host) | Đặt tên máy chủ thành host , là một chuỗi đại diện cho một tên máy chủ hợp lệ |
.setUserName(username) | Đặt tên người dùng thành username , là một chuỗi đại diện cho tên người dùng hợp lệ |
.setPassword(password) | Đặt mật khẩu thành password , là một chuỗi đại diện cho một mật khẩu hợp lệ |
Lưu ý rằng mật khẩu bạn chuyển làm đối số cho .setPassword()
được lưu trữ dưới dạng văn bản thuần túy và có thể được truy xuất sau này bằng cách gọi .password()
. Đây là một rủi ro bảo mật nghiêm trọng mà bạn nên tránh giới thiệu trong các ứng dụng cơ sở dữ liệu của mình. Bạn sẽ học cách tiếp cận an toàn hơn trong phần Mở Kết nối Cơ sở dữ liệu ở phần sau của hướng dẫn này.
Để tạo kết nối với cơ sở dữ liệu SQLite bằng QSqlDatabase
, mở một phiên tương tác Python và nhập mã sau:
>>> 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'
Mã này sẽ tạo một đối tượng kết nối cơ sở dữ liệu bằng "QSQLITE"
làm trình điều khiển của kết nối và "contacts.sqlite"
làm tên cơ sở dữ liệu của kết nối. Vì bạn không chuyển tên kết nối tới .addDatabase()
, kết nối mới được tạo sẽ trở thành kết nối mặc định của bạn, có tên là "qt_sql_default_connection"
.
Trong trường hợp cơ sở dữ liệu SQLite, tên cơ sở dữ liệu thường là tên tệp hoặc đường dẫn bao gồm tên tệp cơ sở dữ liệu. Bạn cũng có thể sử dụng tên đặc biệt ":memory:"
cho cơ sở dữ liệu trong bộ nhớ.
Xử lý nhiều kết nối
Có thể có những tình huống mà bạn cần sử dụng nhiều kết nối với một cơ sở dữ liệu duy nhất. Ví dụ:bạn có thể muốn ghi lại các tương tác của người dùng với cơ sở dữ liệu bằng cách sử dụng một kết nối cụ thể cho từng người dùng.
Trong các tình huống khác, bạn có thể cần kết nối ứng dụng của mình với một số cơ sở dữ liệu. Ví dụ:bạn có thể muốn kết nối với một số cơ sở dữ liệu từ xa để thu thập dữ liệu nhằm điền hoặc cập nhật cơ sở dữ liệu cục bộ.
Để xử lý những tình huống này, bạn có thể cung cấp tên cụ thể cho các kết nối khác nhau của mình và tham chiếu từng kết nối theo tên của nó. Nếu bạn muốn đặt tên cho kết nối cơ sở dữ liệu của mình, thì hãy chuyển tên đó làm đối số thứ hai cho .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'
Tại đây, bạn tạo hai kết nối khác nhau đến cùng một cơ sở dữ liệu, contacts.sqlite
. Mỗi kết nối có tên kết nối riêng. Bạn có thể sử dụng tên kết nối để nhận tham chiếu đến một kết nối cụ thể bất kỳ lúc nào sau này trong mã tùy theo nhu cầu của bạn. Để thực hiện việc này, bạn có thể gọi .database()
với tên kết nối:
>>> from PyQt5.QtSql import QSqlDatabase
>>> db = QSqlDatabase.database("con1", open=False)
>>> db.databaseName()
'contacts.sqlite'
>>> db.connectionName()
'con1'
Trong ví dụ này, bạn thấy rằng .database()
có hai đối số:
-
connectionName
giữ tên kết nối mà bạn cần sử dụng. Nếu bạn không nhập tên kết nối thì kết nối mặc định sẽ được sử dụng. -
open
giữ một giá trị Boolean cho biết.database()
nếu bạn muốn tự động mở kết nối hay không. Nếuopen
làTrue
(mặc định) và kết nối không mở thì kết nối sẽ tự động mở.
Giá trị trả về của .database()
là một tham chiếu đến đối tượng kết nối được gọi là connectionName
. Bạn có thể sử dụng các tên kết nối khác nhau để nhận tham chiếu đến các đối tượng kết nối cụ thể và sau đó sử dụng chúng để quản lý cơ sở dữ liệu của mình.
Sử dụng các SQL Divers khác nhau
Cho đến nay, bạn đã học cách tạo kết nối cơ sở dữ liệu bằng cách sử dụng trình điều khiển SQLite . Đây không phải là trình điều khiển duy nhất có sẵn trong PyQt. Thư viện cung cấp một bộ trình điều khiển SQL phong phú cho phép bạn sử dụng các loại hệ thống quản lý cơ sở dữ liệu khác nhau tùy theo nhu cầu cụ thể của bạn:
Tên trình điều khiển | Hệ thống quản lý cơ sở dữ liệu |
---|---|
QDB2 | IBM Db2 (phiên bản 7.1 trở lên) |
QIBASE | Borland InterBase |
QMYSQL / MARIADB | MySQL hoặc MariaDB (phiên bản 5.0 trở lên) |
QOCI | Giao diện cuộc gọi Oracle |
QODBC | Mở Kết nối Cơ sở dữ liệu (ODBC) |
QPSQL | PostgreSQL (phiên bản 7.3 trở lên) |
QSQLITE2 | SQLite 2 (lỗi thời kể từ Qt 5.14) |
QSQLITE | SQLite 3 |
QTDS | Máy chủ thích ứng Sybase (lỗi thời kể từ Qt 4.7) |
Cột Tên trình điều khiển chứa chuỗi số nhận dạng mà bạn cần chuyển tới .addDatabase()
làm đối số đầu tiên của nó để sử dụng trình điều khiển được liên kết. Không giống như với trình điều khiển SQLite, khi bạn sử dụng một trình điều khiển khác, bạn có thể cần đặt một số thuộc tính, chẳng hạn như databaseName
, hostName
, userName
và password
, để kết nối hoạt động bình thường.
Trình điều khiển cơ sở dữ liệu được lấy từ QSqlDriver
. Bạn có thể tạo trình điều khiển cơ sở dữ liệu của riêng mình bằng cách phân lớp QSqlDriver
, nhưng chủ đề đó vượt ra ngoài phạm vi của hướng dẫn này. Nếu bạn quan tâm đến việc tạo trình điều khiển cơ sở dữ liệu của riêng mình, hãy xem Cách viết trình điều khiển cơ sở dữ liệu của riêng bạn để biết thêm chi tiết.
Mở kết nối cơ sở dữ liệu
Khi bạn có kết nối cơ sở dữ liệu, bạn cần mở kết nối đó để có thể tương tác với cơ sở dữ liệu của mình. Để làm điều đó, bạn gọi .open()
trên đối tượng kết nối. .open()
có hai biến thể sau:
-
.open()
mở kết nối cơ sở dữ liệu bằng các giá trị kết nối hiện tại. -
.open(username, password)
mở kết nối cơ sở dữ liệu bằngusername
được cung cấp vàpassword
.
Cả hai biến thể đều trả về True
nếu kết nối thành công. Nếu không, chúng trả về False
. Nếu không thể thiết lập kết nối, thì bạn có thể gọi .lastError()
để nhận thông tin về những gì đã xảy ra. Hàm này trả về thông tin về lỗi cuối cùng được cơ sở dữ liệu báo cáo.
Lưu ý: Như bạn đã học trước đây, .setPassword(password)
lưu trữ mật khẩu dưới dạng văn bản thuần túy, đây là một rủi ro bảo mật. Mặt khác, .open()
hoàn toàn không lưu trữ mật khẩu. Nó chuyển mật khẩu trực tiếp cho trình điều khiển khi mở kết nối. Sau đó, nó sẽ loại bỏ mật khẩu. Vì vậy, sử dụng .open()
quản lý mật khẩu là cách nên làm nếu bạn muốn ngăn chặn các vấn đề bảo mật.
Dưới đây là ví dụ về cách mở kết nối cơ sở dữ liệu SQLite bằng cách sử dụng biến thể đầu tiên của .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
Trong ví dụ trên, trước tiên bạn tạo kết nối với cơ sở dữ liệu SQLite của mình và mở kết nối đó bằng .open()
. Kể từ .open()
trả về True
, kết nối thành công. Tại thời điểm này, bạn có thể kiểm tra kết nối bằng .isOpen()
, trả về True
nếu kết nối đang mở và False
nếu không.
Lưu ý: Nếu bạn gọi .open()
trên kết nối sử dụng trình điều khiển SQLite và tệp cơ sở dữ liệu không tồn tại, thì tệp cơ sở dữ liệu mới và trống sẽ được tạo tự động.
Trong các ứng dụng thế giới thực, bạn cần đảm bảo rằng bạn có kết nối hợp lệ với cơ sở dữ liệu của mình trước khi cố gắng thực hiện bất kỳ thao tác nào trên dữ liệu của mình. Nếu không, ứng dụng của bạn có thể bị hỏng và không thành công. Ví dụ:điều gì sẽ xảy ra nếu bạn không có quyền ghi cho thư mục mà bạn đang cố gắng tạo tệp cơ sở dữ liệu đó? Bạn cần đảm bảo rằng bạn đang xử lý mọi lỗi có thể xảy ra khi mở kết nối.
Một cách phổ biến để gọi .open()
là gói nó trong một câu lệnh điều kiện. Điều này cho phép bạn xử lý các lỗi có thể xảy ra khi mở kết nối:
>>> 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)
Kết thúc cuộc gọi đến .open()
trong một câu lệnh điều kiện cho phép bạn xử lý bất kỳ lỗi nào xảy ra khi bạn mở kết nối. Bằng cách này, bạn có thể thông báo cho người dùng của mình về bất kỳ sự cố nào trước khi ứng dụng chạy. Lưu ý rằng ứng dụng thoát với trạng thái thoát 1
, thường được sử dụng để chỉ ra lỗi chương trình.
Trong ví dụ trên, bạn sử dụng .open()
trong một phiên tương tác, vì vậy bạn sử dụng print()
để hiển thị thông báo lỗi cho người dùng. Tuy nhiên, trong các ứng dụng GUI, thay vì sử dụng print()
, bạn thường sử dụng QMessageBox
vật. Với QMessageBox
, bạn có thể tạo các hộp thoại nhỏ để trình bày thông tin cho người dùng của mình.
Dưới đây là ứng dụng GUI mẫu minh họa cách xử lý lỗi kết nối:
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
câu lệnh ở dòng 14 kiểm tra xem kết nối không thành công. Nếu /home/
thư mục không tồn tại hoặc nếu bạn không có quyền ghi vào đó, thì lệnh gọi tới .open()
không thành công vì không thể tạo tệp cơ sở dữ liệu. Trong tình huống này, quy trình thực thi nhập if
khối mã tuyên bố và hiển thị thông báo trên màn hình.
Nếu bạn thay đổi đường dẫn đến bất kỳ thư mục nào khác mà bạn có thể viết, thì lệnh gọi đến .open()
sẽ thành công và bạn sẽ thấy một cửa sổ hiển thị thông báo Connection Successfully Opened!
Bạn cũng sẽ có một tệp cơ sở dữ liệu mới được gọi là contacts.sqlite
trong thư mục đã chọn.
Lưu ý rằng bạn vượt qua None
với tư cách là phụ huynh của thông báo bởi vì, tại thời điểm hiển thị thông báo, bạn chưa tạo cửa sổ, vì vậy bạn không có cha mẹ khả thi cho hộp thông báo.
Chạy truy vấn SQL với PyQt
Với kết nối cơ sở dữ liệu đầy đủ chức năng, bạn đã sẵn sàng bắt đầu làm việc với cơ sở dữ liệu của mình. Để làm điều đó, bạn có thể sử dụng các truy vấn SQL dựa trên chuỗi và QSqlQuery
các đối tượng. QSqlQuery
cho phép bạn chạy bất kỳ loại truy vấn SQL nào trong cơ sở dữ liệu của bạn. Với QSqlQuery
, bạn có thể thực thi các câu lệnh ngôn ngữ thao tác dữ liệu (DML), chẳng hạn như SELECT
, INSERT
, UPDATE
và DELETE
, cũng như các câu lệnh ngôn ngữ định nghĩa dữ liệu (DDL), chẳng hạn như CREATE TABLE
và như vậy.
Hàm tạo của QSqlQuery
có một số biến thể, nhưng trong hướng dẫn này, bạn sẽ tìm hiểu về hai trong số chúng:
-
QSqlQuery(query, connection)
tạo đối tượng truy vấn bằng cách sử dụng truy vấn SQLquery
dựa trên chuỗi và một cơ sở dữ liệuconnection
. Nếu bạn không chỉ định kết nối hoặc nếu kết nối được chỉ định không hợp lệ, thì kết nối cơ sở dữ liệu mặc định sẽ được sử dụng. Nếuquery
không phải là một chuỗi rỗng, sau đó nó sẽ được thực thi ngay lập tức. -
QSqlQuery(connection)
tạo đối tượng truy vấn bằng cách sử dụngconnection
. Nếuconnection
không hợp lệ, khi đó kết nối mặc định sẽ được sử dụng.
Bạn cũng có thể tạo QSqlQuery
các đối tượng mà không chuyển bất kỳ đối số nào cho hàm tạo. Trong trường hợp đó, truy vấn sẽ sử dụng kết nối cơ sở dữ liệu mặc định, nếu có.
Để thực hiện một truy vấn, bạn cần gọi .exec()
trên đối tượng truy vấn. Bạn có thể sử dụng .exec()
theo hai cách khác nhau:
-
.exec(query)
thực thi truy vấn SQL dựa trên chuỗi có trongquery
. Nó trả vềTrue
nếu truy vấn thành công và trả vềFalse
. -
.exec()
thực hiện một truy vấn SQL đã chuẩn bị trước đó. Nó trả vềTrue
nếu truy vấn thành công và trả vềFalse
.
Lưu ý: PyQt cũng triển khai các biến thể của QSqlQuery.exec()
với tên .exec_()
. Những điều này cung cấp khả năng tương thích ngược với các phiên bản Python cũ hơn, trong đó exec
là một từ khóa của ngôn ngữ.
Bây giờ bạn đã biết những điều cơ bản về cách sử dụng QSqlQuery
để tạo và thực thi các truy vấn SQL, bạn đã sẵn sàng học cách áp dụng kiến thức của mình vào thực tế.
Thực thi truy vấn SQL tĩnh
Để bắt đầu tạo và thực thi các truy vấn với PyQt, bạn sẽ kích hoạt trình soạn thảo mã hoặc IDE yêu thích của mình và tạo một tập lệnh Python có tên là queries.py
. Lưu tập lệnh và thêm mã sau vào đó:
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())
Trong tập lệnh này, bạn bắt đầu bằng cách nhập các mô-đun và lớp mà bạn sẽ làm việc. Sau đó, bạn tạo kết nối cơ sở dữ liệu bằng .addDatabase()
với trình điều khiển SQLite. Bạn đặt tên cơ sở dữ liệu thành "contacts.sqlite"
và mở kết nối.
Để tạo truy vấn đầu tiên, bạn khởi tạo QSqlQuery
mà không có bất kỳ đối số. Với đối tượng truy vấn tại chỗ, bạn gọi .exec()
, truyền một truy vấn SQL dựa trên chuỗi làm đối số. Loại truy vấn này được gọi là truy vấn tĩnh bởi vì nó không nhận được bất kỳ tham số nào từ bên ngoài truy vấn.
Truy vấn SQL ở trên tạo một bảng mới có tên là contacts
trong cơ sở dữ liệu của bạn. Bảng đó sẽ có bốn cột sau:
Nội dung | |
---|---|
id | Một số nguyên có khóa chính của bảng |
name | Một chuỗi có tên liên hệ |
job | Một chuỗi có tiêu đề công việc của một số liên lạc |
email | Một chuỗi chứa email của một liên hệ |
Dòng cuối cùng trong đoạn mã trên in ra danh sách các bảng có trong cơ sở dữ liệu của bạn. Nếu bạn chạy tập lệnh, thì bạn sẽ lưu ý rằng một tệp cơ sở dữ liệu mới có tên là contacts.sqlite
được tạo trong thư mục hiện tại của bạn. Bạn cũng sẽ nhận được một cái gì đó như ['contacts', 'sqlite_sequence']
được in trên màn hình của bạn. Danh sách này chứa tên của các bảng trong cơ sở dữ liệu của bạn.
Lưu ý: Truy vấn SQL dựa trên chuỗi phải sử dụng cú pháp thích hợp theo cơ sở dữ liệu SQL cụ thể mà bạn đang truy vấn. Nếu cú pháp sai, thì .exec()
bỏ qua truy vấn và trả về False
.
Trong trường hợp của SQLite, truy vấn chỉ có thể chứa một câu lệnh tại một thời điểm.
Đang gọi .exec()
trên QSqlQuery
đối tượng là một cách phổ biến để thực hiện ngay lập tức các truy vấn SQL dựa trên chuỗi trên cơ sở dữ liệu của bạn, nhưng nếu bạn muốn chuẩn bị trước các truy vấn của mình để thực thi sau này thì sao? Đó là chủ đề của phần tiếp theo.
Thực thi truy vấn động:Định dạng chuỗi
Cho đến nay, bạn đã học cách thực thi các truy vấn tĩnh trên cơ sở dữ liệu. Truy vấn tĩnh là những truy vấn không chấp nhận tham số , vì vậy truy vấn chạy như nó vốn có. Mặc dù các truy vấn này khá hữu ích, nhưng đôi khi bạn cần tạo các truy vấn truy xuất dữ liệu để đáp ứng với các tham số đầu vào nhất định.
Các truy vấn chấp nhận tham số tại thời điểm thực thi được gọi là truy vấn động . Sử dụng các tham số cho phép bạn tinh chỉnh truy vấn và truy xuất dữ liệu theo các giá trị tham số cụ thể. Các giá trị khác nhau sẽ tạo ra các kết quả khác nhau. Bạn có thể nhận các tham số đầu vào trong một truy vấn bằng cách sử dụng một trong hai cách tiếp cận sau:
- Tạo truy vấn động, sử dụng định dạng chuỗi để nội suy các giá trị tham số.
- Chuẩn bị truy vấn bằng cách sử dụng các tham số trình giữ chỗ và sau đó liên kết các giá trị cụ thể với các tham số.
Cách tiếp cận đầu tiên cho phép bạn tạo các truy vấn động một cách nhanh chóng. Tuy nhiên, để sử dụng phương pháp này một cách an toàn, bạn cần đảm bảo rằng các giá trị tham số của bạn đến từ một nguồn đáng tin cậy. Nếu không, bạn có thể phải đối mặt với các cuộc tấn công chèn SQL.
Dưới đây là ví dụ về cách sử dụng định dạng chuỗi để tạo truy vấn động trong 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
Trong ví dụ này, bạn sử dụng chuỗi f để tạo truy vấn động bằng cách nội suy các giá trị cụ thể vào một truy vấn SQL dựa trên chuỗi. Truy vấn cuối cùng chèn dữ liệu vào contacts
của bạn bảng, hiện chứa dữ liệu về Linda
.
Lưu ý: Ở phần sau của hướng dẫn này, bạn sẽ thấy cách truy xuất và điều hướng dữ liệu được lưu trữ trong cơ sở dữ liệu.
Lưu ý rằng để loại truy vấn động này hoạt động, bạn cần đảm bảo rằng các giá trị được chèn có kiểu dữ liệu phù hợp. Vì vậy, bạn sử dụng dấu ngoặc kép xung quanh trình giữ chỗ trong chuỗi f vì những giá trị đó cần phải là chuỗi.
Thực thi truy vấn động:Tham số giữ chỗ
Cách tiếp cận thứ hai để thực thi truy vấn động yêu cầu bạn chuẩn bị trước các truy vấn của mình bằng cách sử dụng mẫu có trình giữ chỗ cho các tham số. PyQt hỗ trợ hai kiểu trình giữ chỗ tham số:
- Kiểu Oracle sử dụng trình giữ chỗ được đặt tên như
:name
hoặc:email
. - Kiểu ODBC sử dụng dấu chấm hỏi (
?
) như một trình giữ chỗ vị trí.
Lưu ý rằng không thể trộn các kiểu này trong cùng một truy vấn. Bạn có thể xem Các phương pháp tiếp cận giá trị ràng buộc để biết thêm các ví dụ về cách sử dụng trình giữ chỗ.
Lưu ý: ODBC là viết tắt của Open Database Connectivity.
Để tạo loại truy vấn động này trong PyQt, trước tiên bạn tạo một mẫu có trình giữ chỗ cho mỗi tham số truy vấn và sau đó chuyển mẫu đó làm đối số cho .prepare()
, phân tích cú pháp, biên dịch và chuẩn bị mẫu truy vấn để thực thi. Nếu mẫu có bất kỳ sự cố nào, chẳng hạn như lỗi cú pháp SQL, thì .prepare()
không biên dịch được mẫu và trả về False
.
Nếu quá trình chuẩn bị thành công, thì prepare()
trả về True
. Sau đó, bạn có thể chuyển một giá trị cụ thể cho từng tham số bằng cách sử dụng .bindValue()
với các tham số được đặt tên hoặc vị trí hoặc sử dụng .addBindValue()
với các tham số vị trí. .bindValue()
có hai biến thể sau:
-
.bindValue(placeholder, val)
-
.bindValue(pos, val)
Trong biến thể đầu tiên, placeholder
đại diện cho một trình giữ chỗ kiểu Oracle. Trong biến thể thứ hai, pos
đại diện cho một số nguyên dựa trên 0 với vị trí của một tham số trong truy vấn. Trong cả hai biến thể, val
giữ giá trị được liên kết với một tham số cụ thể.
.addBindValue()
thêm một giá trị vào danh sách trình giữ chỗ bằng cách sử dụng liên kết vị trí. Điều này có nghĩa là thứ tự của các lệnh gọi đến .addBindValue()
xác định giá trị nào sẽ được liên kết với từng tham số trình giữ chỗ trong truy vấn đã chuẩn bị.
Để bắt đầu sử dụng các truy vấn đã chuẩn bị, bạn có thể chuẩn bị INSERT INTO
Câu lệnh SQL để điền vào cơ sở dữ liệu của bạn một số dữ liệu mẫu. Quay lại tập lệnh mà bạn đã tạo trong phần Thực thi Truy vấn SQL tĩnh và thêm mã sau ngay sau lệnh gọi tới 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()
Bước đầu tiên là tạo một QSqlQuery
vật. Sau đó, bạn gọi .prepare()
trên đối tượng truy vấn. Trong trường hợp này, bạn sử dụng kiểu ODBC cho trình giữ chỗ. Truy vấn của bạn sẽ nhận các giá trị cho name
của liên hệ của bạn , job
và email
, vì vậy bạn cần ba trình giữ chỗ. Kể từ id
là một số nguyên được tăng tự động, bạn không cần cung cấp các giá trị cho nó.
Sau đó, bạn tạo một số dữ liệu mẫu để điền vào cơ sở dữ liệu. data
chứa một danh sách các bộ và mỗi bộ chứa ba mục:tên, công việc và email của từng địa chỉ liên hệ.
Bước cuối cùng là liên kết các giá trị mà bạn muốn chuyển cho từng trình giữ chỗ và sau đó gọi .exec()
để thực hiện truy vấn. Để làm điều đó, bạn sử dụng for
vòng. Tiêu đề vòng lặp giải nén từng bộ dữ liệu trong data
thành ba biến riêng biệt với tên thuận tiện. 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:
Phương pháp | 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.
Lưu ý: 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
table. 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()
lần nữa. 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()
.
Lưu ý: 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:
- Standard widgets include internal containers for storing data.
- 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:
-
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 includeQStandardItemModel
,QFileSystemModel
, and SQL-related models. -
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 areQListView
,QTableView
, andQTreeView
. -
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
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
database:
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 to4
, 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 thecontacts
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 theid
columns are integer numbers, you need to convert them into strings to be able to store them in aQTableWidgetItem
đối tượng.
.setItem()
takes three arguments:
row
holds a zero-based integer that represents the index of a given row in the table.column
holds a zero-based integer that represents the index of a given column in the table.item
holds theQTableWidgetItem
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:
- Outside the widget, in your database
- 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 | Mô tả |
---|---|
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
đối tượng. - 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.