delete records in table A not in table B

本文介绍三种高效查询A表中但不在B表中的记录方法,包括使用not in、left join及子查询的方式,并对比其执行效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转)A、B两表,找出ID字段中,存在A表,但是不存在B表的数据。A表总共13w数据,去重后大约3W条数据,B表有2W条数据,且B表的ID字段有索引。

方法一

  使用 not in ,容易理解,效率低  ~执行时间为:1.395秒~

1 select distinct A.ID from  A where A.ID not in (select ID from B)

方法二

  使用 left join...on... , "B.ID isnull" 表示左连接之后在B.ID 字段为 null的记录  ~执行时间:0.739秒~

1 select A.ID from A left join B on A.ID=B.ID where B.ID is null

  图解

方法三

  逻辑相对复杂,但是速度最快  ~执行时间: 0.570秒~(感觉这种方式挺好)之前A/B表位置写反了,很明显可以看出为问题所在

,在碰到问题可以分一下

1  select * from  A

2 where (select count(1) as num from B where A.ID = B.ID) = 0

转载于:https://www.cnblogs.com/kakaisgood/p/9168569.html

修改上面程序,保存修改只改内存里的不要改源文件的内容;搜索后,修改搜索到的内容表格,点击保存修改,点击清空搜索能够显示表格所有内容;搜索后,修改搜索到的内容表格,点击保存修改,再点击搜索,可以搜索到相关内容,点击清空搜索能够显示表格所有内容。import sys import xml.etree.ElementTree as ET from PyQt5.QtWidgets import (QApplication, QMainWindow, QTableWidget, QTableWidgetItem, QPushButton, QFileDialog, QLineEdit, QLabel, QMessageBox, QHBoxLayout, QVBoxLayout, QWidget, QHeaderView, QAbstractItemView, QComboBox, QSizePolicy, QStyledItemDelegate) from PyQt5.QtCore import Qt, QSize from PyQt5.QtGui import QBrush, QColor, QFont import os class ComboBoxDelegate(QStyledItemDelegate): """自定义委托,用于在表格单元格中显示可编辑的下拉框""" def __init__(self, parent=None, options=None): super().__init__(parent) self.options = options or [] def createEditor(self, parent, option, index): editor = QComboBox(parent) editor.setEditable(True) editor.addItems(self.options) return editor def setEditorData(self, editor, index): value = index.data(Qt.DisplayRole) if value: editor.setCurrentText(value) def setModelData(self, editor, model, index): model.setData(index, editor.currentText(), Qt.EditRole) class XMLDataManager(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("RawDataConfig编辑器") self.setGeometry(100, 100, 1400, 800) # 初始化变量 self.current_file = None self.table_data = [] self.original_table_data = [] self.row_counter = 1 self.type_options = set() # 存储type列的所有选项 self.data_type_id_options = set() # 存储data_type_id列的所有选项 # 定义列名列表 self.column_names = [ "path", "type", "variable", "id", "data_type_id", "subsystem_id", "parts", "parts_id", "attribute", "attribute_id", "mapping" ] # 定义必填项列(除了mapping列) self.required_columns = [col for col in self.column_names if col != "mapping"] # 创建UI self.init_ui() def init_ui(self): # 创建中央部件 central_widget = QWidget() self.setCentralWidget(central_widget) # 创建按钮 self.load_btn = QPushButton("加载文件") self.add_btn = QPushButton("添加记录") self.delete_btn = QPushButton("删除记录") self.save_btn = QPushButton("保存修改") self.export_btn = QPushButton("导出文件") self.search_btn = QPushButton("搜索") self.clear_btn = QPushButton("清空搜索") # 设置按钮样式 button_style = """ QPushButton { background-color: rgb(65, 105, 225); color: white; border: none; padding: 8px 16px; text-align: center; font-size: 14px; margin: 4px 2px; border-radius: 4px; } QPushButton:hover { background-color: rgb(55, 95, 215); } QPushButton:pressed { background-color: rgb(45, 85, 205); } """ for btn in [self.load_btn, self.export_btn, self.add_btn, self.delete_btn, self.save_btn, self.search_btn, self.clear_btn]: btn.setStyleSheet(button_style) # 创建搜索框 self.search_box = QLineEdit() self.search_box.setPlaceholderText("输入搜索内容...") self.search_box.setMinimumWidth(300) self.search_box.setStyleSheet(""" QLineEdit { padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; } """) # 创建列选择下拉框 self.column_combo = QComboBox() self.column_combo.setMinimumWidth(120) self.column_combo.addItem("所有列") self.column_combo.addItems(self.column_names) self.column_combo.setCurrentIndex(0) self.column_combo.setStyleSheet(""" QComboBox { padding: 6px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; } """) # 创建表格 - 增加序号列 self.table = QTableWidget() self.table.setColumnCount(len(self.column_names) + 1) # 增加序号列 headers = ["序号"] + self.column_names # 添加序号列头 self.table.setHorizontalHeaderLabels(headers) self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive) self.table.horizontalHeader().setStretchLastSection(True) self.table.setSelectionBehavior(QTableWidget.SelectRows) self.table.setSelectionMode(QAbstractItemView.ExtendedSelection) # 连接单元格更改信号(用于必填项背景色提示) self.table.itemChanged.connect(self.update_cell_background) # 设置表格样式 self.table.setStyleSheet(""" QTableWidget { gridline-color: #ddd; font-size: 12px; } QHeaderView::section { background-color: #f2f2f2; padding: 4px; border: 1px solid #ddd; font-weight: bold; } QTableCornerButton::section { background-color: #f2f2f2; border: 1px solid #ddd; } """) # 设置序号列样式 self.table.setColumnWidth(0, 60) self.table.verticalHeader().setVisible(False) # 设置列宽策略 self.table.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) # 布局设置 - 调整按钮顺序 top_layout = QHBoxLayout() top_layout.addWidget(self.load_btn) top_layout.addWidget(self.add_btn) top_layout.addWidget(self.delete_btn) top_layout.addWidget(self.save_btn) # 保存修改按钮 top_layout.addWidget(self.export_btn) # 导出XML文件按钮放在保存按钮后面 top_layout.addStretch() search_col_label = QLabel("搜索列:") search_col_label.setStyleSheet("font-size: 14px;") # 设置字号为14像素 top_layout.addWidget(search_col_label) top_layout.addWidget(self.column_combo) search_col_label1 = QLabel("搜索内容:") search_col_label1.setStyleSheet("font-size: 14px;") # 设置字号为14像素 top_layout.addWidget(search_col_label1) top_layout.addWidget(self.search_box) top_layout.addWidget(self.search_btn) top_layout.addWidget(self.clear_btn) main_layout = QVBoxLayout() main_layout.addLayout(top_layout) main_layout.addWidget(self.table) central_widget.setLayout(main_layout) # 连接信号 self.load_btn.clicked.connect(self.load_xml) self.export_btn.clicked.connect(self.export_xml) self.add_btn.clicked.connect(self.add_record) self.delete_btn.clicked.connect(self.delete_record) self.save_btn.clicked.connect(self.save_changes) self.search_btn.clicked.connect(self.search_records) self.clear_btn.clicked.connect(self.clear_search) # 状态栏 self.statusBar = self.statusBar() self.statusBar.setStyleSheet("background-color: #f0f0f0; padding: 4px;") def update_cell_background(self, item): """更新单元格背景色(仅用于视觉提示,不弹出警告)""" col_idx = item.column() - 1 # 减去序号列 if col_idx < 0 or col_idx >= len(self.column_names): return col_name = self.column_names[col_idx] # 更新必填项背景色 if col_name in self.required_columns: if not item.text().strip(): item.setBackground(QColor(255, 200, 200)) # 浅红色背景 else: item.setBackground(QColor(255, 255, 255)) # 白色背景 def load_xml(self): file_path, _ = QFileDialog.getOpenFileName( self, "选择XML文件", "", "XML文件 (*.xml)" ) if not file_path: return try: tree = ET.parse(file_path) root = tree.getroot() self.current_file = file_path file_name = os.path.basename(file_path) self.setWindowTitle(f"RawDataConfig编辑器 - {file_name}") # 清空表格 self.table.setRowCount(0) self.table_data = [] self.original_table_data = [] self.row_counter = 1 self.type_options = set() self.data_type_id_options = set() # 解析XML并填充表格 for record in root.findall('Record'): row_data = {} for col_name in self.column_names: value = record.get(col_name, '') row_data[col_name] = value # 收集type和data_type_id的值作为下拉选项 if col_name == 'type' and value: self.type_options.add(value) elif col_name == 'data_type_id' and value: self.data_type_id_options.add(value) self.table_data.append(row_data) # 保存原始数据副本 self.original_table_data = self.table_data.copy() self.populate_table() self.adjust_column_widths() self.statusBar.showMessage(f"成功加载文件: {file_path}", 3000) except Exception as e: QMessageBox.critical(self, "错误", f"加载XML文件失败:\n{str(e)}") def populate_table(self): # 暂时断开itemChanged信号 if hasattr(self, 'table') and self.table: try: self.table.itemChanged.disconnect(self.update_cell_background) except: pass self.table.setRowCount(len(self.table_data)) # 为type和data_type_id列设置下拉框委托 type_col_index = self.column_names.index('type') + 1 data_type_id_col_index = self.column_names.index('data_type_id') + 1 # 创建委托并设置选项 type_delegate = ComboBoxDelegate(self.table, sorted(self.type_options)) data_type_id_delegate = ComboBoxDelegate(self.table, sorted(self.data_type_id_options)) self.table.setItemDelegateForColumn(type_col_index, type_delegate) self.table.setItemDelegateForColumn(data_type_id_col_index, data_type_id_delegate) for row_idx, record in enumerate(self.table_data): # 添加序号列 seq_item = QTableWidgetItem(str(self.row_counter + row_idx)) seq_item.setTextAlignment(Qt.AlignCenter) seq_item.setFlags(seq_item.flags() & ~Qt.ItemIsEditable) self.table.setItem(row_idx, 0, seq_item) # 添加数据列 for col_idx, col_name in enumerate(self.column_names): value = record.get(col_name, '') item = QTableWidgetItem(value) # 设置下拉框列可编辑 if col_name in ['type', 'data_type_id']: item.setFlags(item.flags() | Qt.ItemIsEditable) # 设置必填项背景色 if col_name in self.required_columns and not value.strip(): item.setBackground(QColor(255, 200, 200)) # 浅红色背景 self.table.setItem(row_idx, col_idx + 1, item) # 重新连接itemChanged信号 if hasattr(self, 'table') and self.table: self.table.itemChanged.connect(self.update_cell_background) def adjust_column_widths(self): self.table.setColumnWidth(0, 60) for col in range(1, self.table.columnCount()): self.table.resizeColumnToContents(col) if self.table.columnWidth(col) < 100: self.table.setColumnWidth(col, 100) def export_xml(self): if not self.table_data: QMessageBox.warning(self, "警告", "没有数据可导出") return file_path, _ = QFileDialog.getSaveFileName( self, "导出XML文件", "", "XML文件 (*.xml)" ) if not file_path: return try: with open(file_path, 'wb') as f: f.write(b'<?xml version="1.0" ?>\n') f.write(b'<DataRecords>\n') for record in self.table_data: attrs = ' '.join([f'{k}="{v}"' for k, v in record.items()]) f.write(f' <Record {attrs}/>\n'.encode('utf-8')) f.write(b'</DataRecords>') self.statusBar.showMessage(f"成功导出到: {file_path}", 3000) except Exception as e: QMessageBox.critical(self, "错误", f"导出XML文件失败:\n{str(e)}") # ... 前面的代码保持不变 ... def add_record(self): # 获取选中的行 selected_rows = set(index.row() for index in self.table.selectedIndexes()) insert_row = self.table.rowCount() # 默认插入到末尾 if selected_rows: # 取第一个选中的行作为参考行 insert_row = min(selected_rows) + 1 # 插入到选中行之后 # 在表格中插入新行 self.table.insertRow(insert_row) # 创建新记录 new_record = {col_name: "" for col_name in self.column_names} # 在table_data中插入新记录 self.table_data.insert(insert_row, new_record) # 添加序号列 seq_item = QTableWidgetItem(str(self.row_counter + insert_row)) seq_item.setTextAlignment(Qt.AlignCenter) seq_item.setFlags(seq_item.flags() & ~Qt.ItemIsEditable) self.table.setItem(insert_row, 0, seq_item) # 为新行创建数据单元格 for col_idx, col_name in enumerate(self.column_names): item = QTableWidgetItem("") # 设置下拉框列可编辑 if col_name in ['type', 'data_type_id']: item.setFlags(item.flags() | Qt.ItemIsEditable) # 设置必填项背景色(浅红色提示) if col_name in self.required_columns: item.setBackground(QColor(255, 200, 200)) self.table.setItem(insert_row, col_idx + 1, item) # 更新所有行的序号 self.update_sequence_numbers() # 滚动到新行并自动选中 self.table.scrollToItem(self.table.item(insert_row, 0)) self.table.selectRow(insert_row) self.statusBar.showMessage("已添加新记录", 2000) # ... 后面的代码保持不变 ... def add_record(self): # 获取选中的行 selected_rows = set(index.row() for index in self.table.selectedIndexes()) insert_row = self.table.rowCount() # 默认插入到末尾 if selected_rows: # 取第一个选中的行作为参考行 insert_row = min(selected_rows) + 1 # 插入到选中行之后 # 在表格中插入新行 self.table.insertRow(insert_row) # 创建新记录 new_record = {col_name: "" for col_name in self.column_names} # 在table_data中插入新记录 self.table_data.insert(insert_row, new_record) # 添加序号列 seq_item = QTableWidgetItem(str(self.row_counter + insert_row)) seq_item.setTextAlignment(Qt.AlignCenter) seq_item.setFlags(seq_item.flags() & ~Qt.ItemIsEditable) self.table.setItem(insert_row, 0, seq_item) # 为新行创建数据单元格 for col_idx, col_name in enumerate(self.column_names): item = QTableWidgetItem("") # 设置下拉框列可编辑 if col_name in ['type', 'data_type_id']: item.setFlags(item.flags() | Qt.ItemIsEditable) # 设置必填项背景色(浅红色提示) if col_name in self.required_columns: item.setBackground(QColor(255, 200, 200)) self.table.setItem(insert_row, col_idx + 1, item) # 更新所有行的序号 self.update_sequence_numbers() # 滚动到新行并自动选中 self.table.scrollToItem(self.table.item(insert_row, 0)) self.table.selectRow(insert_row) self.statusBar.showMessage("已添加新记录", 2000) def delete_record(self): selected_rows = set(index.row() for index in self.table.selectedIndexes()) if not selected_rows: QMessageBox.warning(self, "警告", "请先选择要删除的记录") return reply = QMessageBox.question(self, "确认删除", f"确定要删除选中的 {len(selected_rows)} 条记录吗?", QMessageBox.Yes | QMessageBox.No) if reply != QMessageBox.Yes: return for row in sorted(selected_rows, reverse=True): self.table.removeRow(row) if row < len(self.table_data): del self.table_data[row] self.update_sequence_numbers() self.statusBar.showMessage(f"已删除 {len(selected_rows)} 条记录", 3000) def update_sequence_numbers(self): for row_idx in range(self.table.rowCount()): seq_item = QTableWidgetItem(str(self.row_counter + row_idx)) seq_item.setTextAlignment(Qt.AlignCenter) seq_item.setFlags(seq_item.flags() & ~Qt.ItemIsEditable) self.table.setItem(row_idx, 0, seq_item) def save_changes(self): if not self.current_file: QMessageBox.warning(self, "警告", "请先加载XML文件") return # 检查是否有空必填项 empty_fields = [] for row_idx in range(self.table.rowCount()): for col_idx, col_name in enumerate(self.column_names): if col_name in self.required_columns: item = self.table.item(row_idx, col_idx + 1) if item is None or not item.text().strip(): seq_item = self.table.item(row_idx, 0) row_num = seq_item.text() if seq_item else str(row_idx + 1) empty_fields.append(f"行 {row_num} 的 '{col_name}' 列") if empty_fields: error_msg = "以下必填项为空,请填写完整后保存:\n" error_msg += "\n".join(empty_fields[:10]) # 最多显示10个错误 if len(empty_fields) > 10: error_msg += f"\n...(共{len(empty_fields)}个错误)" QMessageBox.warning(self, "保存失败", error_msg) return try: # 更新内存中的数据 for row_idx in range(self.table.rowCount()): if row_idx >= len(self.table_data): self.table_data.append({col_name: "" for col_name in self.column_names}) for col_idx, col_name in enumerate(self.column_names): item = self.table.item(row_idx, col_idx + 1) if item: self.table_data[row_idx][col_name] = item.text() # 更新下拉框选项 if col_name == 'type' and item.text(): self.type_options.add(item.text()) elif col_name == 'data_type_id' and item.text(): self.data_type_id_options.add(item.text()) else: self.table_data[row_idx][col_name] = "" # 保存到原文件 with open(self.current_file, 'wb') as f: f.write(b'<?xml version="1.0" ?>\n') f.write(b'<DataRecords>\n') for record in self.table_data: attrs = ' '.join([f'{k}="{v}"' for k, v in record.items()]) f.write(f' <Record {attrs}/>\n'.encode('utf-8')) f.write(b'</DataRecords>') # 更新原始数据和序号计数器 self.original_table_data = self.table_data.copy() # self.row_counter = self.table.rowCount() + 1 # 更新下拉框委托 self.populate_table() self.statusBar.showMessage("修改已保存", 2000) except Exception as e: QMessageBox.critical(self, "错误", f"保存失败: {str(e)}") def search_records(self): search_text = self.search_box.text().strip().lower() selected_column = self.column_combo.currentText() if not search_text: self.clear_search() return if not self.original_table_data: self.original_table_data = self.table_data.copy() matched_rows = [] matched_indices = [] for row_idx, record in enumerate(self.original_table_data): match_found = False if selected_column == "所有列": for col_name in self.column_names: value = record.get(col_name, '').lower() if search_text in value: match_found = True break else: value = record.get(selected_column, '').lower() if search_text in value: match_found = True if match_found: matched_rows.append(record) matched_indices.append(row_idx + 1) self.table_data = matched_rows self.populate_table_with_original_indices(matched_indices) self.adjust_column_widths() if matched_rows: for row in range(self.table.rowCount()): for col in range(1, self.table.columnCount()): item = self.table.item(row, col) if item: col_name = self.column_names[col - 1] value = item.text().lower() if (selected_column == "所有列" and search_text in value) or \ (col_name == selected_column and search_text in value): item.setBackground(QBrush(QColor(255, 255, 0))) item.setFont(QFont("Arial", 10, QFont.Bold)) else: # 恢复必填项背景色 if col_name in self.required_columns and not item.text().strip(): item.setBackground(QColor(255, 200, 200)) else: item.setBackground(QBrush(Qt.white)) item.setFont(QFont("Arial", 9)) self.statusBar.showMessage(f"找到 {len(matched_rows)} 条匹配记录", 3000) else: QMessageBox.information(self, "搜索结果", "未找到匹配记录") self.statusBar.showMessage("未找到匹配记录", 2000) def populate_table_with_original_indices(self, indices): # 暂时断开itemChanged信号 if hasattr(self, 'table') and self.table: try: self.table.itemChanged.disconnect(self.update_cell_background) except: pass self.table.setRowCount(len(self.table_data)) # 为type和data_type_id列设置下拉框委托 type_col_index = self.column_names.index('type') + 1 data_type_id_col_index = self.column_names.index('data_type_id') + 1 type_delegate = ComboBoxDelegate(self.table, sorted(self.type_options)) data_type_id_delegate = ComboBoxDelegate(self.table, sorted(self.data_type_id_options)) self.table.setItemDelegateForColumn(type_col_index, type_delegate) self.table.setItemDelegateForColumn(data_type_id_col_index, data_type_id_delegate) for row_idx, record in enumerate(self.table_data): seq_item = QTableWidgetItem(str(indices[row_idx])) seq_item.setTextAlignment(Qt.AlignCenter) seq_item.setFlags(seq_item.flags() & ~Qt.ItemIsEditable) self.table.setItem(row_idx, 0, seq_item) for col_idx, col_name in enumerate(self.column_names): value = record.get(col_name, '') item = QTableWidgetItem(value) if col_name in ['type', 'data_type_id']: item.setFlags(item.flags() | Qt.ItemIsEditable) # 设置必填项背景色 if col_name in self.required_columns and not value.strip(): item.setBackground(QColor(255, 200, 200)) self.table.setItem(row_idx, col_idx + 1, item) # 重新连接itemChanged信号 if hasattr(self, 'table') and self.table: self.table.itemChanged.connect(self.update_cell_background) def clear_search(self): if self.original_table_data: self.table_data = self.original_table_data.copy() self.populate_table() self.adjust_column_widths() self.search_box.clear() self.statusBar.showMessage("搜索已清除", 2000) if __name__ == "__main__": app = QApplication(sys.argv) window = XMLDataManager() window.show() sys.exit(app.exec_()) 显示修改后的完整程序
08-21
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值