如何仅通过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
相关产品推荐
相关产品推荐

