You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何仅通过QTableWidget中变更的单元格内容更新SQL数据库

实现QTableWidget增量更新到MySQL数据库的方案

核心实现思路

你当前的代码已经将从数据库加载的原有数据行设为不可编辑状态,只需要区分「数据库原有行」和「用户新增行」即可实现增量更新;如果需要支持修改原有数据,只要放开编辑限制并增加修改标记即可,全程不需要全量覆盖数据库内容。
具体实现逻辑如下:

  • 加载数据库数据时,给每一行绑定对应的数据库主键ID、给每个单元格绑定原始值作为标记
  • 新增保存按钮,触发增量更新逻辑
  • 保存时分别处理两类数据:
    • 有主键标记的行:对比单元格当前值与原始值,仅更新发生修改的字段
    • 无主键标记的行:只要有有效内容就直接插入数据库
  • 增加事务处理,保证数据一致性

完整修改后的代码

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QTableWidgetItem
from PyQt5.QtCore import Qt
import mysql.connector

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName("gridLayout")
        self.widget = QtWidgets.QWidget(self.centralwidget)
        self.widget.setObjectName("widget")
        self.gridLayout_2 = QtWidgets.QGridLayout(self.widget)
        self.gridLayout_2.setObjectName("gridLayout_2")
        self.tableWidget = QtWidgets.QTableWidget(self.widget)
        self.tableWidget.setRowCount(10)
        self.tableWidget.setColumnCount(6) # 可根据实际表字段数调整,默认第一列是主键id
        self.tableWidget.setObjectName("tableWidget")
        self.gridLayout_2.addWidget(self.tableWidget, 0, 0, 1, 1)
        self.pushButton_load = QtWidgets.QPushButton(self.widget)
        self.pushButton_load.setObjectName("pushButton_load")
        self.gridLayout_2.addWidget(self.pushButton_load, 1, 0, 1, 1)
        # 新增保存按钮
        self.pushButton_save = QtWidgets.QPushButton(self.widget)
        self.pushButton_save.setObjectName("pushButton_save")
        self.gridLayout_2.addWidget(self.pushButton_save, 2, 0, 1, 1)
        self.gridLayout.addWidget(self.widget, 0, 0, 1, 1)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.pushButton_load.clicked.connect(self.load_table)
        self.pushButton_save.clicked.connect(self.save_changes)
        
        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton_load.setText(_translate("MainWindow", "加载数据"))
        self.pushButton_save.setText(_translate("MainWindow", "保存修改"))

    # 数据库连接复用,避免重复连接
    def get_db_conn(self):
        return mysql.connector.connect(
            host="localhost",
            user="root",
            password="你的数据库密码",
            database="你的数据库名"
        )

    def load_table(self):
        mydb = self.get_db_conn()
        mycursor = mydb.cursor()
        # 替换为实际表名,不要用关键字table作为表名
        mycursor.execute("select * from 你的实际表名")
        result = mycursor.fetchall()
        # 获取表字段名,用于后续生成SQL语句,和实际表字段一一对应
        self.columns = [i[0] for i in mycursor.description]
        mycursor.close()
        mydb.close()
        
        self.tableWidget.setRowCount(0)
        for row_number, row_data in enumerate(result):
            self.tableWidget.insertRow(row_number)
            # 给每行绑定数据库主键ID,默认第一列是主键
            self.tableWidget.setRowData(row_number, Qt.UserRole, row_data[0])
            for column_number, data in enumerate(row_data):
                item = QTableWidgetItem(str(data) if data is not None else "")
                # 给每个单元格绑定原始值,用于后续判断是否修改
                item.setData(Qt.UserRole, str(data) if data is not None else "")
                # 如果不需要修改原有数据保留此行锁死编辑,需要修改直接注释即可
                item.setFlags(item.flags() ^ Qt.ItemIsEditable)
                self.tableWidget.setItem(row_number, column_number, item)
        # 加载完原有数据后新增5行空行供用户填写,可按需调整数量
        for i in range(5):
            self.tableWidget.insertRow(self.tableWidget.rowCount())

    def save_changes(self):
        mydb = self.get_db_conn()
        mycursor = mydb.cursor()
        try:
            row_count = self.tableWidget.rowCount()
            col_count = self.tableWidget.columnCount()
            for row in range(row_count):
                # 获取行绑定的主键ID,有值为原有数据行,无值为新增行
                pk = self.tableWidget.rowData(row, Qt.UserRole)
                if pk is not None:
                    # 处理修改行:对比每个单元格当前值和原始值
                    update_fields = []
                    update_values = []
                    for col in range(col_count):
                        item = self.tableWidget.item(row, col)
                        if not item:
                            continue
                        current_val = item.text().strip()
                        origin_val = item.data(Qt.UserRole).strip() if item.data(Qt.UserRole) else ""
                        if current_val != origin_val:
                            update_fields.append(f"{self.columns[col]} = %s")
                            update_values.append(current_val if current_val else None)
                    # 有修改字段才执行更新
                    if update_fields:
                        update_values.append(pk)
                        sql = f"UPDATE 你的实际表名 SET {','.join(update_fields)} WHERE {self.columns[0]} = %s"
                        mycursor.execute(sql, tuple(update_values))
                else:
                    # 处理新增行:判断行是否有有效内容
                    row_data = []
                    has_content = False
                    for col in range(col_count):
                        # 主键为自增的话跳过第一列
                        if col == 0:
                            continue
                        item = self.tableWidget.item(row, col)
                        val = item.text().strip() if item else ""
                        if val:
                            has_content = True
                        row_data.append(val if val else None)
                    if has_content:
                        # 生成插入语句,跳过自增主键列
                        insert_cols = self.columns[1:]
                        placeholders = ",".join(["%s"] * len(insert_cols))
                        sql = f"INSERT INTO 你的实际表名 ({','.join(insert_cols)}) VALUES ({placeholders})"
                        mycursor.execute(sql, tuple(row_data))
            # 提交事务
            mydb.commit()
            self.statusbar.showMessage("保存成功", 3000)
            # 保存完成后重新加载数据刷新标记
            self.load_table()
        except Exception as e:
            # 出错回滚
            mydb.rollback()
            self.statusbar.showMessage(f"保存失败:{str(e)}", 5000)
        finally:
            mycursor.close()
            mydb.close()

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

注意事项

  • 代码中的你的实际表名、数据库账号密码需要替换为自己的实际配置
  • 默认假设表第一列是自增主键,如果主键规则不同,需要自行调整主键绑定和SQL语句逻辑
  • 如果需要支持修改原有数据,直接注释掉item.setFlags(item.flags() ^ Qt.ItemIsEditable)这行即可

内容的提问来源于stack exchange,提问作者E. Artemis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 00:48:03