30. 数据库操作

一、SQL与数据库

  数据库 (database)是统一管理的、有组织的、可共享的大量数据的集合。数据库将数据存储在一个或多个表格中,管理这个数据库的软件称为 数据库管理系统(database management system, DBMS)。

  数据库不是针对具体的应用程序,而是立足于数据本身的管理,它将所有数据保存在数据库中,进行科学的组织,并借助于数据库管理系统,通过 SQL 与各种应用程序或应用系统连接,使之能方便地使用数据库中的数据。

  关系型数据库管理系统一般都支持结构化查询语言 (structured query language, SQL),SQL 是数据库的基础,通过它可以实现对关系型数据库进行查询、新增、更新、删除、求和和排序等操作。关系型数据库由数据表 (table)构成,数据表的一行数据称为字段,可以在数据库中添加和删除数据表,在数据表中可以查询、添加和删除字段。

  SQL 语句主要分为以下几类:

  • 数据定义语言(data definition language, DDL)。DDL 是 SQL 中定义数据结构和数据库对象的语言,主要关键字有CREATE、ALTER 和 DROP。
  • 数据操纵语言(data manipulation language, DML)。DML 是 SQL 中操纵数据库中数据的语言,可对数据库中的数据进行插入、删除、更新和选择,主要关键字有 INSERT、UPDATE、DELETE 和 SELECT。
  • 事务控制语言(transaction control language, TCL)。TCL 用于管理 DML 对数据所作的修改,主要关键字有提交 COMMIT 和撤销 ROLLBACK。
    • 对数据库的操作需要用 COMMIT 进行确认,事务一经提交就不能撤销,如果要在提交前撤销对数据库的操作,可以用 ROLLBACK 来 “回滚” 到相应事务开始时的状态。
  • 数据控制语言(data control language, DCL)。DCL 是对数据访问权限进行控制、定义安全级别及创建用户的语言,主要关键字有 GRANT 和 REVOKE。

二、SQLite数据库

  SQLite 数据库是一个常用的开源、跨平台、轻量化本机版数据库,可以作为嵌入式数据库使用。Python 自带的 sqlite3 可以实现对 SQLite 数据库的查询,使用前需要用 import sqlite3 语句导入 sqlite3。

  sqlite3 提供了两个基本的类:数据库连接 Connection 和游标 Cursor。用 sqlite3 的 connect(database:str) 方法 打开一个已经存在的 SQLite 数据库或新建一个数据库,并返回 Connection 对象,其中 databaseName本机上已经存储的数据库文件,或者新建立的数据库文件名称,还可取 ":memory:",表示 在内存中创建临时数据库。用 Connection 对象的 cursor() 方法 获取 Cursor 对象,用 Cursor 对象提供的方法可以执行 SQL 指令。

  数据库连接 Connection 类的常用方法如下:

cursor() -> Cursor                                                              # 创建并返回Cursor对象
commit() -> None                                                                # 提交当前的事务
interrupt() -> None                                                             # 停止还未执行的任务
rollback() -> None                                                              # 放弃对数据库的操作,返回到调用commit()时的状态
close() -> None                                                                 # 关闭数据库
backup(target:Connection) -> None                                               # 将数据备份到另一个数据库中

  游标 Cursor 用于执行 SQL 语句,它的常用方法如下:

execute(sql:sqk, parameters:Sequence=()) -> None                                # 执行一条SQL语句,parameters是SQL中的占位符的值
executemany(sql:str, parameters:Sequence) -> None                               # 重复执行SQL语句,parameters是SQL中的占位符的值
executescript(sql_script:str) -> None                                           # 执行多条SQL语句

fetchone() -> Tuple                                                             # 获取数据表中的下一行数据构成的元组或None
fetchmany(size:int=cursor.arraysize) -> List                                    # 获取多行数据,构成的列表,参数size是要获取的行数
fetchall() -> List                                                              # 获取所有剩余的行构成的列表

close() -> None                                                                 # 关闭游标

  新建一个 template.py 文件。

import sqlite3

connection = sqlite3.connect("test.db")                                         # 1.获取连接
cursor = connection.cursor()                                                    # 2.获取游标

cursor.execute("create table if not exists user(name varchar(20), age int)")    # 3.执行SQL语句,创建表

# 4.向表中插入数据,新增用户信息
cursor.execute("insert into user(name,age) values('Sakura',10)")
cursor.execute("insert into user(name,age) values('Mikoto',14)")
cursor.execute("insert into user(name,age) values('Shana',15)")

cursor.execute("select * from user")                                            # 5.执行查询结果

# 6.获取查询结果
result = cursor.fetchone()                                                      # 使用fetchone()获取一条记录
print(result,end="\n\n")

result = cursor.fetchmany(2)                                                    # 使用fetchmany()获取多条记录
print(result,end="\n\n")

result = cursor.fetchall()                                                      # 使用fetchall()获取全部查询结果
print(result,end="\n\n")

# 7.修改用户信息
# 使用问号作为占位符代替具体的数值,然后使用一个元组来替代问号
# 使用占位符的方式可以避免SQL注入的风险
cursor.execute("update user set age = ? where name = ?",(12,"Sakura"))
cursor.execute("select * from user where name = ?",("Sakura",))
result = cursor.fetchall()
print(result,end="\n\n")

# 8.删除用户数据
cursor.execute("delete from user where name = ?",("Shana",))
cursor.execute("select * from user")
result = cursor.fetchall()
print(result,end="\n\n")

cursor.close()                                                                  # 9.关闭游标
connection.close()                                                              # 10.关闭数据库

三、PySide6操作数据库

  PySide6 提供了对常用数据库的驱动,可以从数据库中进行查询、读取、写入、修改、删除数据等操作,同时提供了可视化的控件和数据库数据模型,可以将数据从数据库读取到数据模型中,然后用 Model/View 结构在图形界面中对数据进行操作。

  PySide6 可以驱动常用的关系型数据库,在对数据库进行操作之前需要用 QSqlDatabase 类建立对数据库的连接,然后再用 QSqlQuery 类执行 SQL 命令,实现对数据库的操作。

  我们可以在终端中使用 pip 安装 PySide6 模块。默认是从国外的主站上下载,因此,我们可能会遇到网络不好的情况导致下载失败。我们可以在 pip 指令后通过 -i 指定国内镜像源下载

pip install pyside6 -i https://mirrors.aliyun.com/pypi/simple

  国内常用的 pip 下载源列表:

3.1、数据库连接

  在对数据库进行操作之前,需要先建立对数据库的连接。数据库连接用 QSqlDatabase 类。

  用 QSqlDatabase 创建实例的方法如下所示:

QSqlDatabase()
QSqlDatabase(type:str)

  其中 type数据库的驱动类型,可取的值下所示。如果要建立自定义的驱动类型,可以创建 QSqlDriver 的子类。

"QDB2"                                                                          # IBM DB2数据库
"QIBASE"                                                                        # Borlad InterBase数据库
"QMYSQL"                                                                        # MySQL数据库
"QODBC"                                                                         # 支持ODBC接口的数据库
"QPSQL"                                                                         # PostgreSQL数据库
"QSQLITE"                                                                       # SQLite数据库
"QOCI"                                                                          # Oracle数据库

  QSqlDatabase 类的常用方法如下:

# 实例方法
connectionName() -> str                                                         # 获取连接的名称
driverName() -> str                                                             # 获取驱动类型的名称

setDatabaseName(name:str) -> None                                               # 设置连接的数据库名称
databaseName() -> str                                                           # 获取连接的数据库名称

setHostName(host:str) -> None                                                   # 设置连接的主机名称
hostName() -> str                                                               # 获取连接的主机名称

setPort(port:int) -> None                                                       # 设置连接的端口号
port() -> int                                                                   # 获取连接的端口号

setUserName(username:str) -> None                                               # 设置用户名
userName() -> str                                                               # 获取用户名

setPassword(password:str) -> None                                               # 设置密码
password() -> str                                                               # 获取密码

setConnectOptions(options="") -> None                                           # 设置连接选项
connectOptions() -> str                                                         # 获取连接参数

open() -> bool                                                                  # 打开数据库
open(user:str, password:str) -> bool                                            # 打开数据库
isOpen() -> bool                                                                # 获取数据库是否打开
isOpenError() -> bool                                                           # 获取打开数据库是是否出错
isValid() -> bool                                                               # 获取连接是否有效
close() -> None                                                                 # 关闭连接

transaction() -> bool                                                           # 开启事务
commit() -> bool                                                                # 提交事务
rollback() -> bool                                                              # 回滚事务
lastError() -> QSqlError                                                        # 获取最后的错误信息
record(tablename:str) -> QSqlRecord                                             # 获取含有字段名称的记录

setNumericalPrecisionPolicy(precisionPolicy:QSql.NumericalPrecisionPolicy) -> None  # 设置对数据库进行查询时默认的数值精度
tables(kind:QSql.TableType=QSql.Tables) -> List[str]                            # 根据表格类型参数,获取数据库中的表格名称

# 静态方法
drivers() -> List[str]                                                          # 获取系统支持的驱动类型
isDriverAvailable(name:str) -> bool                                             # 获取是否支持某种类型的驱动

# 添加数据库连接
addDatabase(driver:str, connectionName:str=QLatin1StringView(QSqlDatabase.defaultConnection)) -> QSqlDatabase

# 根据连接名称获取数据库连接
database(connectionName:str=QLatin1StringView(QSqlDatabase.defaultConnection), open:bool=true) -> QSqlDatabase

removeDatabase(connectionName:str) -> None                                      # 删除数据库连接

connectionNames() -> List[str]                                                  # 获取已经添加的连接名称

# 如果connectionNames()返回值中有指定的连接,则返回True
contains(connectionName:str=QLatin1StringView(QSqlDatabase.defaultConnection)) -> bool

  用静态方法 addDatabase(type:str,connectionName:str='qt_sql_default_connection') 添加某种驱动类型的连接,其中参数 type驱动类型。对同一个数据库可以添加多个连接,数据库连接的识别是通过连接名称 connectionName 来区分的,而不是关联的数据库。如果不输入连接名称,则该连接作为默认连接,将使用默认的连接名称 'qt_sql_default_connection'

  在用 open() 方法 打开数据库 前,需要分别用 setDatabaseName(name:str) 方法、setHostName(host:str) 方法、setPassword(password:str) 方法、setPort(port:int) 方法、setUserName(name:str) 方法、setConnectOptions(options:str='') 方法 设置连接的数据库文件名、主机名、密码、端口号、用户名和连接参数,如果用 open() 方法打开数据库后再设置这些参数将不起作用。

  在用 setDatabaseName(name:str) 方法 打开 SQLite 数据库 时,如果数据库不存在则创建新数据库,参数 name 也可取 ':memory:'在内存中临时创建数据库,程序运行结束后删除数据库。对于 ODBC 数据库,参数 name*.dsn 文件或连接字符串;对于 Oracle 数据库,name 参数是 TNS 服务名称

  用 setConnectOptions(options:str='') 方法 设置数据库的参数,不同的驱动类型需要设置的参数也不同,各参数值之间用分号 ; 隔开,例如 SQLite 数据库,可选参数如下:

QSQLITE_BUSY_TIMEOUT
QSQLITE_OPEN_READONLY
QSQLITE_OPEN_URI
QSQLITE_ENABLE_SHARED_CACHE
QSQLITE_ENABLE_REGEXP
QSQLITE_NO_USE_EXTENDED_RESULT_CODES

  用 tables(kind:QSql.TableType=QSql.Tables) 方法 获取数据库中存在的数据表名称列表,其中参数 kindQSql.TableType 类型的枚举值,可以取值如下:

QSql.TableType.Tables                                                           # 对用户可见的所有表
QSql.TableType.SystemTables                                                     # 数据库使用的内部表
QSql.TableType.Views                                                            # 对用户可见的所有视图
QSql.TableType.AllTables                                                        # 以上三种表和视图

  用 setNumericalPrecisionPolicy(precisionPolicy:QSql.NumericalPrecisionPolicy) 方法 设置对数据库进行查询时默认的数值精度,参数 precisionPolicyQSql.NumericalPrecisionPolicy 类型的枚举值,可以取值如下:

QSql.NumericalPrecisionPolicy.LowPrecisionInt32                                 # 32位整数,忽略小数部分
QSql.NumericalPrecisionPolicy.LowPrecisionInt64                                 # 64位整数,忽略小数部分
QSql.NumericalPrecisionPolicy.LowPrecisionDouble                                # 双精度值,默认值
QSql.NumericalPrecisionPolicy.HighPrecision                                     # 保持数据的原有精度

  用 lastError() 方法 获取最后的出错信息 QSqlError 对象,用 QSqlErrortype() 方法可以 获取出错类型,返回值是 QSqlError.ErrorType 的枚举值或 -1(不能确定错误类型),QSqlError.ErrorType 的枚举值如下:

QSqlError.ErrorType.NoError                                                     # 0,没有错误
QSqlError.ErrorType.ConnectionError                                             # 1,数据库连接错误
QSqlError.ErrorType.StatementError                                              # 2,SQL命令语法错误
QSqlError.ErrorType.TransactionError                                            # 3,事务错误
QSqlError.ErrorType.UnknownError                                                # 4,未知错误

3.2、数据库查询

  数据库查询 QSqlQuery 用于执行标准的 SQ L命令,例如 CREATE TABLESELECTINSERTUPDATEDELETE 等,还可执行特定的非标准的 SQL 命令。

  用 QSqlQuery 类创建实例对象的方法如下所示:

QSqlQuery(db:QSqlDatabase)
QSqlQuery(other:QSqlQuery)
QSqlQuery(query:str="", db:QSqlDatabase=Default(QSqldatabase))

  QSqlQuery 类常用方法如下:

prepare(query:str) -> bool                                                      # 准备SQL语句,成功返回True

addBindValue(val:Any, type:QSql.ParamType=QSql.In) -> None                      # 如果prepare(query)中有占位符,则按顺序依次设置占位符的值
bindValue(placeholder:str, val:Any, type:QSql.ParamType=QSql.In) -> None        # 如果prepare(query)中有占位符,则根据占位名称设置占位符的值
bindValue(pos:int, val:Any, type:QSql.ParamType=QSql.In) -> None                # 如果prepare(query)中有占位符,则根据占位位置设置占位符的值

boundValue(placeholder:str) -> Any                                              # 根据占位符名称获取绑定值
boundValue(pos:int) -> Any                                                      # 根据占位位置获取绑定值
boundValues() -> List[Any]                                                      # 获取绑定值列表

exec() -> bool                                                                  # 执行prepare(query)准备的SQL语句
exec(query:str) -> bool                                                         # 执行SQL语句,成功返回True

execBatch(mode:QSqlQuery.BatchExecutionMode=QSqlQuery.BatchExecutionMode.ValuesAsRows) -> bool  # 批处理用prepare()方法准备的命令

finish() -> None                                                                # 完成查询,不再获取数据
clear() -> None                                                                 # 清空结果,释放所有资源,查询处于不活跃状态

executedQuery() -> str                                                          # 返回最后正确执行的SQL语句
lastQuery() -> str                                                              # 返回当前查询使用使用的SQL语句

# 返回查询的当前内部位置,第一个记录位置是0,如果位置无效,则返回值是QSql.BeforeFirstRow(值是-1)或QSql.AfterLastRow(值是-2)
at() -> int

isSelect() -> bool                                                              # 当前SQL语句是SELECT语句时返回True
isValid() -> bool                                                               # 当前查询定位在有效记录时返回True
isActive() -> bool                                                              # 获取查询是否处于活跃状态
isNull(field:int) -> bool                                                       # 将查询处于非活跃状态,查询定位在无效记录或空字段上时返回True
isNull(name:str) -> bool                                                        # 将查询处于非活跃状态,查询定位在无效记录或空字段上时返回True

first() -> bool                                                                 # 将当前查询位置定位在第一个记录
last() -> bool                                                                  # 将当前查询位置定位到最后一个记录
previous() -> bool                                                              # 将当前查询位置定位到前一个记录
next() -> bool                                                                  # 将当前查询位置定位到下一个记录
seek(i:int, relative:bool=false) -> bool                                        # 将当前查询位置定位到指定记录

setForwardOnly(forward:bool) -> None                                            # 当forward取True时,只能用next()和seek()方法来定位结果,此时,seek()参数为正值
isForwardOnly() -> bool                                                         # 获取定位模式

lastError() -> QSqlError                                                        # 返回最近出错信息
lastInsertId() -> Any                                                           # 返回最近插入行的对象ID号
nextResult() -> bool                                                            # 放弃当前查询结果并定位到下一个结果
record() -> QSqlRecord                                                          # 返回查询指向的当前记录

size() -> int                                                                   # 返回查询结果的记录数,无法确定、非SELECT命令或数据库不支持该功能是返回-1、
value(i:int) -> Any                                                             # 根据字段索引,获取当前记录的字段值
value(name:int) -> Any                                                          # 根据字段名称,获取当前记录的字段值

numRowsAffected() -> int                                                        # 获取受影响的行的个数,无法确定或查询处于非活跃状态时返回-1
swap(other:QSqlQuery) -> None                                                   # 与其它查询交换数据

  用 exec(query:str) 方法 直接执行 SQL 命令。也可以用 prepare(query:str)方法 准备要执行的 SQL 命令;用 exec()方法或 execBatch(mode=QSqlQuery.ValuesAsRows) 方法 执行已经准备好的 SQL 命令,其中参数 modeQSqlQuery.ValuesAsRows 类型的枚举值,可以取值如下:

QSqlQuery.BatchExecutionMode.ValuesAsRows                                       # 更新多行,列表中的每个值作为一个值来更新下一行
QSqlQuery.BatchExecutionMode.ValuesAsColumns                                    # 更新一行,列表作为一个值来使用

  在用 prepare(query:str) 方法准备 SQL 命令时,SQL 命令中可以有占位符,占位符可以用问号 "?"(ODBC 格式),也可以用冒号 ":surname"(Oracle 格式)。

  占位符的真实值可以用 addBindValue(val:Any,type:QSql.ParamType=QSql.In) 方法按照 顺序 依次设置,也可用 bindValue(placeholder:str,val:Any,type:QSql.ParamType=QSql.In) 方法根据 占位符的名称 设置,还可以用 bindValue(pos:int,val:Any,type:QSql.ParamType=QSql.In) 方法根据 占位符的位置 设置,其中参数 typeQSql.ParamType 类型的枚举值,可以取值如下:

QSql.ParamTypeFlag.In                                                           # 绑定参数输入到数据库中
QSql.ParamTypeFlag.Out                                                          # 绑定参数从数据库中接收数据
QSql.ParamTypeFlag.InOut                                                        # 既可以将数据输入到数据库中,也可以从数据库中接收数据
QSql.ParamTypeFlag.Binary                                                       # 数据是二进制,需要将“|”与以上三种参数联合使用

  当返回的结果有多个记录时,需要首先定位到所需要的记录上,当 isActive() 方法和 isSelect() 方法的返回值是 True 时,可以用 first()last()previous()next() 方法分别定位到 第一个记录最后一个记录前一个记录下一个记录 上,成功则返回 True;用 seek(index:int,relative:bool=False) 方法可以 定位到指定的记录 上。如果只是想从开始到结束浏览数据,可以设置 setForwardOnly(True),这样可以节省大量的内存。

  用 value(index:int) 方法或 value(name:str) 方法 获取当前记录的字段值。也可用 record() 方法 获取当前的记录对象 QSqlRecordQSqlRecord 是指 数据表(table)或视图(view)中的一行,然后用记录对象的 value(index:int) 方法或 value(name:str) 方法 获取字段的值,用记录对象的 count() 方法 获取字段的数量,用 indexOf(name:str) 方法 获取字段的索引

import sys

from PySide6.QtWidgets import QApplication
from PySide6.QtSql import QSqlDatabase, QSqlQuery

if __name__ == "__main__":
    persons = (("Sakura", 10), ("Mikoto", 14), ("Shana", 15))
    db_name = "./test.db"

    app = QApplication(sys.argv)                                                # 1.创建QApplication对象
  
    db = QSqlDatabase.addDatabase("QSQLITE", "SQLite")                          # 2.添加数据库连接
    db.setDatabaseName(db_name)                                                 # 3.设置连接数据库的名称
    query = QSqlQuery(db)                                                       # 4.创建数据库查询对象 
  
    if db.open():                                                               # 5.打开数据库 
        query.exec("create table if not exists user(name varchar(20), age int)")    # 6.执行SQL语句,创建表

        if db.transaction():                                                    # 7.开启事务
            for person in persons:  
                query.prepare("insert into user(name, age) values(?, ?)")       # 8.准备SQL语句
                query.addBindValue(person[0])                                   # 9.按顺序设置占位符的值
                query.bindValue(1, person[1])                                   # 10.按索引位置设置占位符的值
                query.exec()                                                    # 11.执行SQL语句
            db.commit()                                                         # 12.提交事务

        if query.exec("select * from user"):                                    # 13.查询数据
            while query.next():                                                 # 14.遍历结果集 
                print(query.value("name"), query.value("age"))                  # 15.获取字段的值

        db.close()                                                              # 16.关闭数据库

在使用 PySide6 操作数据库时,必须先创建 QApplication 实例,否则会报 qt.sql.qsqldatabase: QSqlDatabase requires a QCoreApplication

四、数据库模型

  PySide6 提供了对数据库进行可视化操作的 Model/View 结构,通过数据库模型读入在数据库中查询到的数据,并通过视图控件(如 QTableView)显示数据库模型中的数据,通过代理控件在视图控件中对数据进行新增、更新、删除等操作,再通过数据模型把操作后的数据保存到数据库中。PySide6 提供的数据库模型有 QSqlQueryModelQSqlTableModelQSqlRelationalTableModel

数据模型继承关系

4.1、数据库查询模型

  数据库查询模型 QSqlQueryModel 只能从数据库中读取数据,而不能修改数据,可以用视图控件

  用 QSqlQueryModel 创建数据库查询模型对象的方法如下所示。

QSqlQueryModel(paarent:QObject=None)

  QSqlQueryModel 类常用方法如下:

setQuery(query:QSqlQuery) -> None                                               # 设置数据库查询
setQuery(query:str, db:QSqkDatabase=QSqlDatabase()) -> None                     # 设置数据库查询
query() -> QSqlQuery                                                            # 获取数据库查询

record() -> QSqlRecord                                                          # 获取包含字段信息的空记录
record(row:int) -> QSqlRecord                                                   # 获取指定行的记录

clear() -> None                                                                 # 清空查询模型中的数据

# 设置显示数据的视图控件表头某角色的值
setHeaderData(section:int, orientation:Qt.Orientation, value:Any, role:int=Qt.EditRole) -> bool

# 获取显示数据的视图控件表头某种角色的值
headerData(section:int, orientation:Qt.Orientation, role:int=Qt.DisplayRole) -> Any

rowCount(parent:QModelIndex=QModelIndex()) -> int                               # 获取数据表中记录(行的数量)

columnCount(parent:QModelIndex=QModelIndex()) -> int                            # 获取数据表中列的数量

  用 setQuery(query:QSqlQuery) 方法或 setQuery(query:str,db:QSqlDatabase=Default(QSqlDatabase)) 方法 设置数据库查询 QSqlQuery;用 setHeaderData(section:int,orientation:Qt.Orientation,value:Any,role:int=Qt.EditRole) 方法 设置显示数据的视图控件表头某角色的值,在 orientationQt.Orientation.Horizontal,并且 section 取值合适时返回 True,其他情况返回 False,其中 value某种角色的值section列索引

  新建一个 ui.py 文件,用来存放 UI 相关的代码。

from PySide6.QtWidgets import QWidget
from PySide6.QtWidgets import QTableView
from PySide6.QtWidgets import QVBoxLayout

class MyUi:
    def setupUi(self, window:QWidget):
        window.resize(800, 600)                                                 # 1.设置窗口对象大小

        layout = QVBoxLayout(window)                                            # 2.创建一个垂直布局

        self.tableView = QTableView(window)                                     # 3.创建一个表格控件,并添加到布局中
        layout.addWidget(self.tableView)

        self.tableView.setSortingEnabled(True)                                  # 4.设置单击头部时是否可以排序
        self.tableView.setAlternatingRowColors(True)                            # 5.设置表格颜色交错显示

  新建一个 widget.py 文件,用来存放业务逻辑相关的代码。

import sys

from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtSql import QSqlDatabase, QSqlQuery, QSqlQueryModel
from PySide6.QtCore import Qt

from ui import MyUi

class MyWidget(QWidget):
    def __init__(self):
        super().__init__()                                                      # 1.调用父类Qwidget类的__init__()方法

        self.__ui = MyUi()
        self.__ui.setupUi(self)                                                 # 2.初始化页面

        self.use_database()                                                     # 3.使用数据库 
      
        self.__ui.tableView.setModel(self.sqlQueryModel)                        # 4.设置表格控件的模型

    def use_database(self):
        persons = (("Sakura", 10), ("Mikoto", 14), ("Shana", 15))
        db_name = "./test.db"
      
        db = QSqlDatabase.addDatabase("QSQLITE")                                # 1.添加数据库连接
        db.setDatabaseName(db_name)                                             # 2.设置连接数据库的名称
        query = QSqlQuery(db)                                                   # 3.创建数据库查询对象

        self.sqlQueryModel = QSqlQueryModel()                                   # 4.创建数据库查询模型
      
        if db.open():                                                           # 5.打开数据库  
            # 6.执行SQL语句,创建表
            self.sqlQueryModel.setQuery("create table if not exists user(name varchar(20), age int)")

            if db.transaction():                                                # 7.开启事务
                for person in persons:
                    query.prepare("insert into user(name, age) values(?, ?)")   # 8.准备SQL语句
                    query.addBindValue(person[0])                               # 9.按顺序设置占位符的值
                    query.bindValue(1, person[1])                               # 10.按索引位置设置占位符的值
                    query.exec()                                                # 11.执行SQL语句
                db.commit()                                                     # 12.提交事务

        self.sqlQueryModel.setQuery("select * from user")                       # 13.设置查询

        header = self.sqlQueryModel.record()                                    # 14.获取字段信息当作头部信息
        for i in range(header.count()):
            # 15.设置显示数据的视图控件表头某角色的值
            self.sqlQueryModel.setHeaderData(i, Qt.Orientation.Horizontal, header.fieldName(i), Qt.ItemDataRole.DisplayRole)

        db.close()                                                              # 16.关闭数据库

if __name__ == "__main__":
    app = QApplication(sys.argv)                                                # 1.创建一个QApplication类的实例
    window = MyWidget()                                                         # 2.创建一个窗口
    window.show()                                                               # 3.展示窗口
    sys.exit(app.exec())                                                        # 4.进入程序的主循环并通过exit()函数确保主循环安全结束

4.2、数据库表格模型

【1】、QSqlTableModel 数据库表格模型

  数据库表格模型 QSqlTableModel 借助视图控件可以对查询到的数据进行修改、插入、删除和排序等操作,同时将修改后的数据更新到数据库中。

  用 QSqlTableModel 创建数据库表格模型的方法如下所示:

QSqlTableModel(parent:QObject=None, db:QSqlDatabase=Default(QSqlDataBase))

  数据库表格模型 QSqlTableModel 的常用方法按如下所示:

# 实例方法
insertRows(row:int, count:int, parent:QModelIndex=QModelIndex()) -> bool        # 插入多个空行,在OnFieldChange和OnRowChange模式下每次只能插入一行

removeRow(row:int, parent:QModelIndex=QModelIndex()) -> bool                    # 删除指定行
removeRows(row:int, count:int, parent:QModelIndex=QModelIndex()) -> bool        # 删除多行

insertColumns(column:int, count:int, parent:QModelIndex=QModelIndex()) -> bool  # 插入多个空列

removeColumn(column:int, parent:QModelIndex=QModelIndex()) -> bool              # 删除指定列
removeColumns(column:int, count:int, parent:QModelIndex=QModelIndex()) -> bool  # 删除多列

revert() -> None                                                                # 恢复数据模型到初始状态  
submit() -> bool                                                                # 提交数据模型中的所有更改

rowCount(parent:QModelIndex=QModelIndex()) -> int                               # 获取行的数量
columnCount(parent:QModelIndex=QModelIndex()) -> int                            # 获取列的数量

setData(index:QModelIndex, value:Any, role:Qt.ItemDataRole=Qt.EditRole) -> bool # 设置指定索引的数据项的角色值
data(index:QModelIndex, role:Qt.ItemDataRole=Qt.EditRole) -> Any                # 获取指定索引的数据项的角色值

sort(column:int, order:Qt.SortOrder=Qt.AscendingOrder) -> None                  # 直接对结果进行排序

index(row:int, column:int, parent:QModelIndex=QModelIndex()) -> QModelIndex     # 获取子索引
parent(child:QModelIndex)-> QModelIndex                                         # 获取子索引的父索引
sibling(row:int, column:int, idx:QModelIndex) -> QModelIndex                    # 获取指定索引的兄弟索引

clearItemData(index:QModelIndex) -> bool                                        # 根据索引清除数据项中的数据

setEditStrategy(strategy:QSqlTableModel.EditStrategy) -> None                   # 设置修改提交模式
database() -> QSqlDatabase                                                      # 获取关联的数据库连接

deleteRowFromTable(row:int) -> bool                                             # 直接删除数据表中指定的行(记录)

fieldIndex(fieldName:str) -> int                                                # 获取字段的索引,-1表示没有对应的字段

insertRecord(row:int, record:QSqlRecord) -> bool                                # 在指定行位置插入行
insertRowIntoTable(values:QSqlDatabase) -> bool                                 # 直接在数据表中插入行

isDirty() -> bool                                                               # 获取模型中是否有脏数据
isDirty(index:QModelIndex) -> bool                                              # 根据索引获取数据数据是否时脏数据

primaryValues(row:int) -> QSqlRecord                                            # 返回指定行的含有表格字段的记录
revertRow(row:int) -> None                                                      # 复原指定行的更改

setRecord(row:int, record:QSqlRecord) -> bool                                   # 用指定的记录填充指定的行
setTable(tableName:str) -> None                                                 # 设置数据表中的字段名称

setFilter(filter:str) -> None                                                   # 设置SELECT查询语句中WHERE子句部分 
filter() -> str                                                                 # 获取SELECT查询语句中WHERE子句部分

setSort(column:int, order:Qt.SortOrder) -> None                                 # 设置SELECT查询语句中ORDER BY子句部分
orderByClause() -> str                                                          # 获取SELECT查询语句中ORDER BY子句部分

select() -> bool                                                                # 执行SELECT查询语句
selectRow(row:int) -> bool                                                      # 用数据库库中行更新模型中的数据
selectStatement() -> bool                                                       # 获取"SELECT ... WHERE ... ORDER BY..."

# 槽函数
revertAll() -> None                                                             # 复原所有未提交的更改
submitAll() -> bool                                                             # 提交所有更改

  数据库表格模型 QSqlTableModel 的常用信号如下所示:

# 在调用deleteRowFromTable(row:int)方法删除指定行之前发色和信号
beforeDelete(row:int)

# 在调用insertRowIntoTable(values:QSqlRecord)方法插入记录之前发射信号,可以在插入之前修改记录
beforeInsert(record:QSqlRecord)

# 在调用updateRowInTable(row:int, values:QSqlRecord)方法更新指定行之前发射信号
beforeUpdate(row:int, record:QSqlRecord)

# 在调用insertRows(row:int, column:int)方法对新插入的行进行初始化时发射信号
primeInsert(row:int, record:QSqlRecord)

  在视图控件中对数据库表格模型中的数据进行修改提交时,有 3 种模式可供选择:立即模式行模式手动模式。我们可以用 setEditStrategy(strategy:QSqlTableModel.EditStrategy) 方法 设置修改提交模式,参数 strategyQSqlTableModel.EditStrategy 类型的枚举值,可以取的值如下:

# 0,对模型的修改会立即更新到当前的模式
QSqlTableModel.EditStrategy.OnFieldChange

# 1,修改完一行,在选择其它行后把修改应用到数据库中
QSqlTableModel.EditStrategy.OnRowChange

# 2,修改后不会立即更新到数据库中,而是保存在缓存中
# 调用submitALL()方法才会把修改应用到数据库中
# 调用revertAll()方法可撤销修改并恢复原状
QSqlTableModel.EditStrategy.OnManualSubmit

  对数据库的查询可以先用 setTable(tableName:str) 方法、setFilter(filter:str) 方法和 setSort(column:int, order:Qt.SortOrder) 方法分别 设置需要查询的数据表SQL 的 WHERE 从句SORT BY 从句,最后调用 select() 方法,也可以直接用 setQuery(query:QSqlQuery) 方法进行查询。

  用 setSort(column:int,order:Qt.SortOrder) 方法可以 将数据模型中的数据按照某列的值进行排序,参数 orderQt.SortOrder 类型的枚举值,可以取值如下:

Qt.SortOrder.AscendingOrder                                                     # 升序
Qt.SortOrder.DescendingOrder                                                    # 降序

【2】、QSqlField 字段

  记录 QSqlRecord 表示 数据表中的一行数据,一行数据中每个字段有不同的值,可用 QSqlTableModelrecord(row:int) 方法获取 QSqlRecord 对象,以获取数据表中的一行数据。

  用 QSqlRecord 创建记录实例对象的方法如下所示:

QSqlRecord()
QSqlRecord(other:QSqlRecord)

  QSqlRecord 常用的方法如下所示:

append(field:QSqlField) -> None                                                 # 在末尾添加字段
insert(pos:int, field:QSqlField) -> None                                        # 在指定的位置插入字段

remove(pos:int) -> None                                                         # 删除指定位置的字段
replace(pos:int, field:QSqlField) -> None                                       # 替换指定位置的字段

setValue(name:str, val:Any) -> None                                             # 根据字段名称设置字段的值
setValue(i:int, val:Any) -> None                                                # 根据字段索引设置字段的值
value(name:str) -> Any                                                          # 根据字段名称获取字段的值
value(i:int) -> Any                                                             # 根据字段索引获取字段的值

setNull(name:str) -> None                                                       # 根据字段名称设置字段为NULL
setNull(i:int) -> None                                                          # 根据字段索引设置字段为NULL
isNull(name:str) -> bool                                                        # 根据字段名称判断字段是否为NULL
isNull(i:int) -> bool                                                           # 根据字段索引判断字段是否为NULL

clear() -> None                                                                 # 删除所有的字段
isEmpty() -> bool                                                               # 获取是否含有字段

clearValues() -> None                                                           # 删除所有的字段值

contains(name:str) -> bool                                                      # 获取是否包含指定的字段

count() -> int                                                                  # 获取字段的数量

field(name:str) -> QSqlField                                                    # 根据字段名称获取字段
field(i:int) -> QSqlField                                                       # 根据字段索引获取字段
fieldName(i:int) -> str                                                         # 获取字段的名称
indexOf(name:str) -> int                                                        # 获取字段的索引

keyValues(keyFields:QSqlRecord) -> QSqlRecord                                   # 获取与给定的记录具有相同字段名称的记录

setGenerated(name:str, generated:bool) -> None                                  # 根据索引设置字段值是否已经生成
setGenerated(i:int, generated:bool) -> None                                     # 根据索引设置字段值是否已经生成
isGenerated(name:str) -> bool                                                   # 根据索引获取字段是否已经生成
isGenerated(i:int) -> bool                                                      # 根据名称获取字段是否已经生成

  用 append(field:QSqlField) 方法可以 在末尾添加字段。用 insert(pos:int,field:QSqlField) 方法 插入字段;用 remove(pos:int) 方法 移除字段。用 setValue(i:int,val:Any) 方法或 setValue(name:str,val:Any) 方法 根据字段索引或字段名称设置字段的值。用 setGenerated(i:int,generated:bool) 方法或 setGenerated(name:str,generated:bool) 方法 根据索引或名称设置字段值是否已经生成,只有已经生成的字段值才能用 QSqlTableModelupdateRowInTable(row:int, values:QSqlRecord) 方法 更新到数据库 中,默认值是 True

  字段 QSqlField数据表中的列,一个记录由多个字段构成。字段的属性有字段名、字段类型和字段值等。用 QSqlRecordfield(i:int) 方法或 field(name:str) 方法可获得 QSqlField

  用 QSqlField 创建字段的方法如下所示:

QSqlField(fieldName:str="", type:QMetaType:Default(QMetaType), tableName:str="")
QSqlField(other:QSqlField)

  其中 type 用于 定义字段的类型,可取 PySide6 中常见的类。

  QSqlField 类的常用方法如下:

setName(name:str) -> None                                                       # 设置字段的名称
name() -> str                                                                   # 获取字段的名称

setValue(value:Any) -> None                                                     # 获取字段的值,只读时不能设置值
value() -> Any                                                                  # 获取字段的值,只读时不能设置值

setDefaultValue(value:Any) -> None                                              # 设置字段的默认值
defaultValue() -> Any                                                           # 获取字段的默认值

setMetaType(type:QMetaType) -> None                                             # 设置字段的类型

metaType() -> QMetaType                                                         # 获取存储在数据库中的类型

setReadOnly(readOnly:bool) -> None                                              # 设置是否只读,只读时不能修改字段的值
isReadOnly() -> bool                                                            # 获取是否只读

setRequired(required:bool) -> None                                              # 设置字段的值是必须要输入还是可见的

setRequiredStatus(status:QSqlFieldRequiredStatus) -> None                       # 设置可选状态

setGenerated(gen:bool) -> None                                                  # 设置字段的生成状态
isGenerated() -> bool                                                           # 获取字段的生成状态

setLength(fieldLength:int) -> None                                              # 设置字段的长度
length() -> int                                                                 # 获取字段的长度

setPrecision(precision:int) -> None                                             # 设置浮点数的精度
precision() -> int                                                              # 获取浮点数的精度

setTableName(tableName:str) -> None                                             # 设置数据表名称
tableName() -> str                                                              # 获取数据表名称

setAutoValue(autoVal:bool) -> None                                              # 将字段的值标记是由数据库自动生成的
isAutoValue() -> bool                                                           # 获取字段的值是否是由数据库自动生成的

isValid() -> bool                                                               # 获取字段的类型是否有效

clear() -> None                                                                 # 清除字段的值并设置成None
isNull() -> bool                                                                # 获取字段的值是否为None

  用 setName(name:str) 方法 设置字段名称。用 setValue(value:Any) 方法 设置字段的值。用 setDefaultValue(value:Any) 方法 设置字段的默认值。用 setReadOnly(readOnly:bool) 方法 设置是否是只读,在只读状态不能更改字段的值。用 setRequired(required:bool) 方法或 setRequiredStatus(status:QSqlField.RequiredStatus) 方法 设置字段的值是必须要输入的还是可选的,其中参数 statusQSqlField.RequiredStatus 类型的枚举值,可以取值如下:

QSqlField.RequiredStatus.Required
QSqlField.RequiredStatus.Optional
QSqlField.RequiredStatus.Unknown

  新建一个 dialog.py 文件 ,用来存放自定义的对话框控件。

from PySide6.QtWidgets import QWidget, QDialog
from PySide6.QtWidgets import QHBoxLayout, QVBoxLayout, QFormLayout
from PySide6.QtWidgets import QPushButton, QLineEdit, QSpinBox

class MyDialog(QDialog):
    def __init__(self, parent:QWidget | None = None):
        super().__init__(parent)                                                # 1.调用父类Qwidget类的__init__()方法

        self.setupUi()                                                          # 3.初始化对话框页面

    def setupUi(self):
      
        veritical_layout = QVBoxLayout(self)                                    # 2.创建一个垂直布局

        form_layout = QFormLayout()                                             # 3.创建一个表单布局,并添加到垂直布局中
        veritical_layout.addLayout(form_layout)

        self.name_lineEdit = QLineEdit()                                        # 4.创建一个文本输入框,并添加到表单布局中
        form_layout.addRow("姓名:", self.name_lineEdit)

        self.age_spinBox = QSpinBox()                                           # 6.创建一个数字输入框,并添加到表单布局中
        self.age_spinBox.setRange(0, 300)
        form_layout.addRow("年龄:", self.age_spinBox)

        horizontal = QHBoxLayout()                                              # 7.创建一个水平布局,并添加到垂直布局中
        veritical_layout.addLayout(horizontal)

        self.ok_button = QPushButton("确定")                                    # 8.创建一个按钮,并添加到水平布局中
        horizontal.addWidget(self.ok_button)

        self.cancel_button = QPushButton("取消")
        horizontal.addWidget(self.cancel_button)

        self.clear_button = QPushButton("清空")
        horizontal.addWidget(self.clear_button)

  修改 ui.py 文件的内容。

from PySide6.QtWidgets import QWidget
from PySide6.QtWidgets import QTableView, QPushButton, QLineEdit
from PySide6.QtWidgets import QVBoxLayout, QHBoxLayout

class MyUi:
    def setupUi(self, window:QWidget):
        window.resize(800, 600)                                                 # 1.设置窗口对象大小

        vertical_layout = QVBoxLayout(window)                                   # 2.创建一个垂直布局

        self.tableView = QTableView(window)                                     # 3.创建一个表格控件,并添加到布局中
        vertical_layout.addWidget(self.tableView)

        self.tableView.setSortingEnabled(True)                                  # 4.设置单击头部时是否可以排序
        self.tableView.setAlternatingRowColors(True)                            # 5.设置表格颜色交错显示

        horizontal_layout = QHBoxLayout()                                       # 6.创建水平布局,并添加到垂直布局
        vertical_layout.addLayout(horizontal_layout)

        self.quert_button = QPushButton("查询")                                 # 7.创建查询按钮,并添加到水平布局中
        horizontal_layout.addWidget(self.quert_button)

        self.query_lineEidt = QLineEdit()
        self.query_lineEidt.setPlaceholderText("请输入查询条件")
        horizontal_layout.addWidget(self.query_lineEidt)

        self.add_button = QPushButton("添加")
        horizontal_layout.addWidget(self.add_button)

        self.delete_button = QPushButton("删除")
        horizontal_layout.addWidget(self.delete_button)

  修改 widget.py 文件的内容。

import sys

from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtSql import QSqlDatabase, QSqlRecord, QSqlTableModel

from ui import MyUi
from dialog import MyDialog

class MyWidget(QWidget):
    def __init__(self):
        super().__init__()                                                      # 1.调用父类Qwidget类的__init__()方法

        self.__ui = MyUi()
        self.__ui.setupUi(self)                                                 # 2.初始化页面

        self.dialog = MyDialog(self)                                            # 3.初始化对话框
      
        self.use_database()                                                     # 4.使用数据库 
      
        self.__ui.tableView.setModel(self.sqlTableModel)                        # 5.设置表格控件的模型

        self.__ui.quert_button.clicked.connect(self.query_button_clicked)
        self.__ui.add_button.clicked.connect(self.add_button_clicked)
        self.__ui.delete_button.clicked.connect(self.delete_button_clicked)

        self.dialog.ok_button.clicked.connect(self.dialog_ok_button_clicked)
        self.dialog.cancel_button.clicked.connect(self.dialog_cancel_button_clicked)
        self.dialog.clear_button.clicked.connect(self.dialog_clear_button_clicked)

    def use_database(self):
        persons = [{"name": "Sakura", "age": 10}, {"name": "Mikoto", "age": 14}, {"name": "Shana", "age": 10}]
        db_name = "./test.db"

        db = QSqlDatabase.addDatabase("QSQLITE")                                # 1.添加数据库连接
        db.setDatabaseName(db_name)                                             # 2.设置连接数据库的名称
        self.sqlTableModel = QSqlTableModel(self, db)                           # 3.创建数据库表格模型

        if db.open():                                                           # 4.打开数据库  
            # 5.执行SQL语句,创建表
            self.sqlTableModel.setQuery("CREATE TABLE IF NOT EXISTS person(id INTEGER PRIMARY KEY AUTOINCREMENT, name varchar(20), age int)")

            self.sqlTableModel.setTable("person")                               # 6.设置数据表中字段的名称

            if db.transaction():                                                # 7.开启事务
                for i, person in enumerate(persons):
                    record = QSqlRecord(self.sqlTableModel.record())            # 8.创建QSqlRecord对象,表示数据表中的一行数据
                    for key, value in person.items():
                        record.setValue(key, value)                             # 9.根据字段名称设置字段的值
                    self.sqlTableModel.insertRecord(i, record)                  # 10.在指定位置插入行
                db.commit()                                                     # 11.提交事务

        self.sqlTableModel.select()                                             # 12.查询数据

    def query_button_clicked(self):
        query_condition = self.__ui.query_lineEidt.text()                       # 1.获取查询条件

        if query_condition:
            self.sqlTableModel.setFilter(query_condition)                       # 2.设置查询条件
        else:
            self.sqlTableModel.setFilter("")

        self.sqlTableModel.select()                                             # 3.执行查询

    def add_button_clicked(self):
        self.dialog.open()                                                      # 1.打开对话框

    def delete_button_clicked(self):
        select_row_index_list = self.__ui.tableView.selectedIndexes()           # 1.获取选中的多行的索引
        select_rows = [index.row() for index in select_row_index_list]          # 2.获取行索引
        select_rows.sort(key=int, reverse=True)                                 # 3.对行索引进行降序排序

        for row in select_rows:
            self.sqlTableModel.removeRow(row)                                   # 4.删除行
            self.sqlTableModel.submit()                                         # 5.提交修改

        self.sqlTableModel.select()                                             # 6.重新查询,刷新表格

    def dialog_ok_button_clicked(self):
        # 1.获取输入控件的数据
        name = self.dialog.name_lineEdit.text()
        age = self.dialog.age_spinBox.value()

        if name:
            self.dialog_clear_button_clicked()                                  # 2.清空对话框的输入数据

            count = self.sqlTableModel.rowCount()                               # 3.获取数据表的行数
            self.sqlTableModel.insertRow(count)                                 # 4.在数据库表格模型中插入一行数据

            self.sqlTableModel.setData(self.sqlTableModel.index(count, 0), name)    # 5.设置数据库表格模型中指定行的数据
            self.sqlTableModel.setData(self.sqlTableModel.index(count, 1), age)

            self.sqlTableModel.submit()                                         # 6.提交数据库表格模型中的数据

            self.dialog.accept()                                                # 7.隐藏对话框

    def dialog_cancel_button_clicked(self):
        self.dialog_clear_button_clicked()                                      # 1.清空对话框的输入数据
        self.dialog.reject()                                                    # 2.隐藏对话框

    def dialog_clear_button_clicked(self):
        self.dialog.name_lineEdit.clear()
        self.dialog.age_spinBox.setValue(0)

if __name__ == "__main__":
    app = QApplication(sys.argv)                                                # 1.创建一个QApplication类的实例
    window = MyWidget()                                                         # 2.创建一个窗口
    window.show()                                                               # 3.展示窗口
    sys.exit(app.exec())                                                        # 4.进入程序的主循环并通过exit()函数确保主循环安全结束

这里,创建表的时候要增加一个主键字段,不要然后面删除行的时候,会把数据相同的行全部删除。

在 SQLite 中,创建带自增主键的表时字段的格式为 主键字段名 INTEGER PRIMARY KEY AUTOINICREMENT(不区分大小写),否则会创建表失败或者主键为空,插入数据时没有自增。

4.3、关系表格模型

  数据库关系表格模型 QSqlRelationalTableModel 继承自 QSqlTableModel,除具有 QSqlTableModel 的方法外,它还提供了外键功能。关系表格模型 QSqlRelationalTableModel 实现了 SQL 的 SELECT 命令中的 INNER JOINLEFT JOIN 功能。

  用 QSqlRelationalTableModel 类创建关系表格模型的方法如下所示。

QSqlRelationalTableModel(parent:QObject=None, db:QSqlDatabase=Default(QSqlDatabase))

  用 QSqlRelationalTableModelsetRelation(column:int,relation:QSqlRelation) 方法定义 QSqlRelationalTableModel 当前数据表格(如 table1)的外键和映射关系,其中参数 columntable1 的字段编号,用于确定 table1 中当作外键的字段 field1relation 参数是 QSqlRelation 的实例对象,用于确定另外一个数据表格(如 table2)和对应的字段 field2

  QSqlRelation 实例对象的创建方法是 QSqlRelation(tableName:str,indexCol:str,displayCol:str),其中 tableName 用于 确定第 2 个数据表格 table2indexCol 用于 指定 table2 的字段 field2displayColtable2 中用于显示在 table1field1 位置处的字段 field3,用 field3 的值显示在 field1 位置处,field1 的值不显示。

  另外用 QSqlRelationalTableModelsetJoinMode(joinMode:QSqlRelationalTableModel.JoinMode) 方法可 设置两个数据表格的数据映射模式,即使 table1table2 没有匹配的数据,也列出 table1 中的数据。参数 joinModeQSqlRelationalTableModel.JoinMode 类型的枚举值,可以取值如下:

QSqlRelationalTableModel.InnerJoin                                              # 内连接,值为0,只列出table1和table2中匹配的数据
QSqlRelationalTableModel.LeftJoin                                               # 左连接,值为1
posted @ 2025-01-18 21:41  星光映梦  阅读(164)  评论(0)    收藏  举报