一、引言
在數字化轉型浪潮中,超過76%的基層業務人員仍被困在"SQL恐懼癥"的泥潭里——他們精通業務邏輯卻受限于技術門檻,面對海量數據時只能反復請求IT部門協助。本項目通過Python+PyQt來構建基于Excel風格的查詢系統,從而打破這種低效循環:PyQt5提供直觀界面可視化組件,pandas庫實現"無SQL"的數據操作轉換,對用戶上傳的任意電子表格Excel文件均可完成復雜數據庫查詢。
二、GUI界面設計
使用PyQt5進行界面的搭建,最終界面如下:
1.第一步:上傳任意Excel文件
直接點擊上傳即可。
這里可選擇上傳任意填寫好的Excel文件,但需要注意:
①Excel文件是純數據文件,不能包含表頭、標題行、合并單元格等特殊格式。
②Excel中的數據量不受限,但要求第一列必須為查找的主鍵值(主鍵值是指數據庫表中主鍵字段(或字段組合)所存儲的具體數值或字符串,用于唯一標識表中的每一行記錄,確保數據可唯一識別和訪問),其余列可填寫對應數據。
以成績表作為演示示例:如若想查詢全校所有人的各科成績信息,則第一列應為該學生的學號(主鍵),其余各列可放置該學生的全部科目成績。
2.第二步:選擇查詢字段
在第一步上傳任意Excel成功后,會自動根據上傳文件更新“請選擇主鍵字段”和“請選擇待查詢字段”內的選項,并且在“請選擇主鍵字段”這一選項下方出現輸入框,方便用戶輸入主鍵內容。對于“請選擇主鍵字段”這一選項,在第一步中已明確規定:Excel中第一列為主鍵字段,所以這里僅額外增加一個選項(以成績表為例,這里自動增加“學號”字段);而對于“請選擇待查詢字段”這一選項,會將Excel中除第一列以外的所有列名均作為新增選項(以成績表為例,這里自動增加“語文”、“數學”、“英語”等其他所有字段),效果如下:
3.第三步:輸入主鍵值并查詢
選擇好主鍵字段及待查詢字段,并輸入待查詢主鍵值后,點擊“開始查詢”即可。
最終查詢效果如下:
可以看到,結果被分為兩個區域:左邊表示查詢結果(即學生姓名),右邊表示與查詢結果相關的其他所有Excel數據(即除姓名和學號之外,該生的所有成績信息)。
4.異常提示
若未進行相應的操作,跳過某個步驟或直接點擊“開始查詢”,則會出現以下的異常提示。
5.界面設計jiemian.py
最后附上通過pyuic5產生的GUI界面代碼jiemian.py:
# -*- coding: utf-8 -*-# Form implementation generated from reading ui file 'jiemian.ui'
#
# Created by: PyQt5 UI code generator 5.15.11
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.from PyQt5 import QtCore, QtGui, QtWidgetsclass Ui_Form(object):def setupUi(self, Form):Form.setObjectName("Form")Form.setEnabled(True)Form.resize(600, 500)Form.setMinimumSize(QtCore.QSize(600, 500))Form.setMaximumSize(QtCore.QSize(600, 500))icon = QtGui.QIcon()icon.addPixmap(QtGui.QPixmap(":/image1.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)Form.setWindowIcon(icon)self.label_5 = QtWidgets.QLabel(Form)self.label_5.setGeometry(QtCore.QRect(390, 50, 141, 31))font = QtGui.QFont()font.setFamily("Adobe Arabic")font.setPointSize(16)self.label_5.setFont(font)self.label_5.setObjectName("label_5")self.label_8 = QtWidgets.QLabel(Form)self.label_8.setGeometry(QtCore.QRect(220, 20, 231, 31))font = QtGui.QFont()font.setFamily("Adobe Arabic")font.setPointSize(18)font.setBold(False)font.setWeight(50)self.label_8.setFont(font)self.label_8.setObjectName("label_8")self.label_10 = QtWidgets.QLabel(Form)self.label_10.setGeometry(QtCore.QRect(470, 40, 71, 51))self.label_10.setText("")self.label_10.setPixmap(QtGui.QPixmap(":/image1.png"))self.label_10.setObjectName("label_10")self.label = QtWidgets.QLabel(Form)self.label.setGeometry(QtCore.QRect(80, 90, 161, 21))font = QtGui.QFont()font.setFamily("Adobe Arabic")font.setPointSize(12)self.label.setFont(font)self.label.setObjectName("label")self.pushButton = QtWidgets.QPushButton(Form)self.pushButton.setGeometry(QtCore.QRect(120, 120, 161, 23))self.pushButton.setObjectName("pushButton")self.label_2 = QtWidgets.QLabel(Form)self.label_2.setGeometry(QtCore.QRect(80, 170, 161, 21))font = QtGui.QFont()font.setFamily("Adobe Arabic")font.setPointSize(12)self.label_2.setFont(font)self.label_2.setObjectName("label_2")self.comboBox = QtWidgets.QComboBox(Form)self.comboBox.setGeometry(QtCore.QRect(120, 200, 161, 22))self.comboBox.setObjectName("comboBox")self.comboBox.addItem("")self.comboBox_2 = QtWidgets.QComboBox(Form)self.comboBox_2.setGeometry(QtCore.QRect(330, 200, 161, 22))self.comboBox_2.setObjectName("comboBox_2")self.comboBox_2.addItem("")self.label_3 = QtWidgets.QLabel(Form)self.label_3.setGeometry(QtCore.QRect(80, 300, 221, 151))font = QtGui.QFont()font.setFamily("Adobe Arabic")font.setPointSize(28)self.label_3.setFont(font)self.label_3.setFrameShape(QtWidgets.QFrame.StyledPanel)self.label_3.setText("")self.label_3.setAlignment(QtCore.Qt.AlignCenter)self.label_3.setObjectName("label_3")self.pushButton_3 = QtWidgets.QPushButton(Form)self.pushButton_3.setGeometry(QtCore.QRect(80, 470, 471, 23))self.pushButton_3.setObjectName("pushButton_3")self.lineEdit = QtWidgets.QLineEdit(Form)self.lineEdit.setGeometry(QtCore.QRect(120, 250, 161, 20))self.lineEdit.setObjectName("lineEdit")self.textEdit = QtWidgets.QTextEdit(Form)self.textEdit.setGeometry(QtCore.QRect(320, 300, 231, 151))self.textEdit.setObjectName("textEdit")self.label_4 = QtWidgets.QLabel(Form)self.label_4.setGeometry(QtCore.QRect(80, 280, 161, 16))self.label_4.setObjectName("label_4")self.label_6 = QtWidgets.QLabel(Form)self.label_6.setGeometry(QtCore.QRect(320, 280, 161, 16))self.label_6.setObjectName("label_6")self.label_7 = QtWidgets.QLabel(Form)self.label_7.setGeometry(QtCore.QRect(120, 230, 161, 16))self.label_7.setObjectName("label_7")self.retranslateUi(Form)QtCore.QMetaObject.connectSlotsByName(Form)def retranslateUi(self, Form):_translate = QtCore.QCoreApplication.translateForm.setWindowTitle(_translate("Form", "Kebiao_query"))self.label_5.setText(_translate("Form", "Designed By"))self.label_8.setText(_translate("Form", "數據庫查詢系統"))self.label.setText(_translate("Form", "一、上傳數據文件"))self.pushButton.setText(_translate("Form", "點擊上傳"))self.label_2.setText(_translate("Form", "二、選擇查詢字段"))self.comboBox.setItemText(0, _translate("Form", "請選擇主鍵字段"))self.comboBox_2.setItemText(0, _translate("Form", "請選擇待查詢字段"))self.pushButton_3.setText(_translate("Form", "開始查詢"))self.label_4.setText(_translate("Form", "查詢結果如下:"))self.label_6.setText(_translate("Form", "相關查詢結果如下:"))self.label_7.setText(_translate("Form", "請輸入主鍵值:"))
import ziyuan_rc
三、主要程序詳解
1.導入所需模塊
import sys
from jiemian import *
from PyQt5.QtWidgets import QApplication, QWidget
import pandas as pd
# 保持窗口大小和qtdesigner中的一致
from PyQt5 import QtCore
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
不懂為啥需要導入Qtcore的,請看一鍵曝光:Python+PyQt實現的文件目錄透視鏡。
2.初始化設置
def __init__(self):super(QWidget, self).__init__()self.setupUi(self)self.pushButton.clicked.connect(self.shangchuan_wenjian)self.pushButton_3.clicked.connect(self.kaishi)self.lineEdit.setVisible(False)self.label_4.setVisible(False)self.label_6.setVisible(False)self.label_7.setVisible(False)self.shangchuan_flag = False
主要將pushbutton綁定于對應的信號函數,并對一些lineedit和label設置visible效果,目的是只有觸發某些動作或函數時,才將其顯示狀態更改為True,否則一直不顯示(即False狀態);shangchuan_flag作為是否上傳任意Excel表的標志位。
3.上傳文件并更新選項
def shangchuan_wenjian(self):self.wenjian_df = shangchuan()if not self.wenjian_df.empty:self.shangchuan_flag = Trueself.lineEdit.setVisible(True)self.label_7.setVisible(True)self.columns = self.wenjian_df.columns.tolist()self.comboBox.clear()self.comboBox.addItem("請選擇主鍵字段")self.comboBox.addItem(self.columns[0])self.comboBox_2.clear()self.comboBox_2.addItem("請選擇待查詢字段")self.comboBox_2.addItems(self.columns[1:])
調用自定義函數shangchuan來實現彈窗效果;當文件內容不為空時,設置上傳標志位shangchuan_flag為True,并顯示lineedit(主鍵值輸入框)和label_7(“請輸入主鍵值”);讀取所有文件列名并存儲在列表columns中,將列表columns中的第一個值(即第一列的列名)添加至combobox中,其余值添加至combobox_2中。但需要注意:每次添加item(s)前,需要將其原有選項清空clear,否則容易造成選項堆疊重復。
4.查詢信息并顯示
def kaishi(self):if self.shangchuan_flag==True:if self.comboBox.currentIndex()!=0:if self.comboBox_2.currentIndex()!=0:lieziduan = self.comboBox_2.currentText()liesuoyin = self.wenjian_df.columns.get_loc(lieziduan)# 根據行字段確定行索引hangsuoyin = -1for i in range(0, len(self.wenjian_df)):if str(self.wenjian_df.iloc[i,0]) == self.lineEdit.text():hangsuoyin = ibreakchaxunzhi = self.wenjian_df.iloc[hangsuoyin, liesuoyin]# 匹配剩余未查詢值ewai_lst = []for column_name in self.columns:new_column_index = self.wenjian_df.columns.get_loc(column_name)if (new_column_index != liesuoyin) and (new_column_index != 0):ewaizhi = self.wenjian_df.iloc[hangsuoyin, new_column_index]ewai_lst.append(column_name+":"+str(ewaizhi))if pd.isna(chaxunzhi) or hangsuoyin==-1:self.label_4.setVisible(True)self.label_6.setVisible(True)self.label_3.setText("未找到")self.textEdit.setText("未找到")else:self.label_4.setVisible(True)self.label_6.setVisible(True)self.label_3.setText(str(chaxunzhi))self.textEdit.setText("\n".join(ewai_lst))else:QtWidgets.QMessageBox.critical(self, "提示", "請選擇主鍵字段!")else:QtWidgets.QMessageBox.critical(self, "提示", "請選擇待查詢字段!")else:QtWidgets.QMessageBox.critical(self, "提示", "請檢查操作步驟或上傳文件!")
以shangchaun_flag作為標志位,檢查是否上傳模板文件;依次判斷是否選擇每個combobox里的對應選項;若均滿足,則根據combobox_2的內容定位列字段名稱,再根據列字段名稱反推列索引;根據lineedit內容通過循環查詢對應的值,找到后即可退出循環,避免運算復雜度,最終得到行索引;最后通過得到的行索引和列索引確定查詢值,并通過label_3顯示;剩余未查詢值,思路方法也是同理,最終顯示在textedit中。若遇到查詢不到的情況時,顯示的內容均設置為“未找到”。當然,哪個combobox有問題,則顯示不同的對應提示。
5.自定義函數
def shangchuan():filepath, _ = QtWidgets.QFileDialog.getOpenFileName(None, "請選擇文件", "", "XLSX工作表 (*.xlsx)") # 獲取文件路徑if filepath:# 獲取原始dfyuanshi_df = pd.read_excel(filepath)if not yuanshi_df.empty:QtWidgets.QMessageBox.information(None, "成功", "上傳成功!")return yuanshi_dfelse:QtWidgets.QMessageBox.critical(None, "提示", "請檢查操作步驟或上傳文件!")else:QtWidgets.QMessageBox.critical(None, "提示", "請選擇XLSX工作表類型!")return pd.DataFrame()
一旦需要上傳文件時,直接調用此函數即可。
四、總程序代碼Kebiao_query.py
import sys
from jiemian import *
from PyQt5.QtWidgets import QApplication, QWidget
import pandas as pd
# 保持窗口大小和qtdesigner中的一致
from PyQt5 import QtCore
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)class mainwindow(QWidget, Ui_Form):def __init__(self):super(QWidget, self).__init__()self.setupUi(self)self.pushButton.clicked.connect(self.shangchuan_wenjian)self.pushButton_3.clicked.connect(self.kaishi)self.lineEdit.setVisible(False)self.label_4.setVisible(False)self.label_6.setVisible(False)self.label_7.setVisible(False)self.shangchuan_flag = Falsedef shangchuan_wenjian(self):self.wenjian_df = shangchuan()if not self.wenjian_df.empty:self.shangchuan_flag = Trueself.lineEdit.setVisible(True)self.label_7.setVisible(True)self.columns = self.wenjian_df.columns.tolist()self.comboBox.clear()self.comboBox.addItem("請選擇主鍵字段")self.comboBox.addItem(self.columns[0])self.comboBox_2.clear()self.comboBox_2.addItem("請選擇待查詢字段")self.comboBox_2.addItems(self.columns[1:])def kaishi(self):if self.shangchuan_flag==True:if self.comboBox.currentIndex()!=0:if self.comboBox_2.currentIndex()!=0:lieziduan = self.comboBox_2.currentText()liesuoyin = self.wenjian_df.columns.get_loc(lieziduan)# 根據行字段確定行索引hangsuoyin = -1for i in range(0, len(self.wenjian_df)):if str(self.wenjian_df.iloc[i,0]) == self.lineEdit.text():hangsuoyin = ibreakchaxunzhi = self.wenjian_df.iloc[hangsuoyin, liesuoyin]# 匹配剩余未查詢值ewai_lst = []for column_name in self.columns:new_column_index = self.wenjian_df.columns.get_loc(column_name)if (new_column_index != liesuoyin) and (new_column_index != 0):ewaizhi = self.wenjian_df.iloc[hangsuoyin, new_column_index]ewai_lst.append(column_name+":"+str(ewaizhi))if pd.isna(chaxunzhi) or hangsuoyin==-1:self.label_4.setVisible(True)self.label_6.setVisible(True)self.label_3.setText("未找到")self.textEdit.setText("未找到")else:self.label_4.setVisible(True)self.label_6.setVisible(True)self.label_3.setText(str(chaxunzhi))self.textEdit.setText("\n".join(ewai_lst))else:QtWidgets.QMessageBox.critical(self, "提示", "請選擇主鍵字段!")else:QtWidgets.QMessageBox.critical(self, "提示", "請選擇待查詢字段!")else:QtWidgets.QMessageBox.critical(self, "提示", "請檢查操作步驟或上傳文件!")def shangchuan():filepath, _ = QtWidgets.QFileDialog.getOpenFileName(None, "請選擇文件", "", "XLSX工作表 (*.xlsx)") # 獲取文件路徑if filepath:# 獲取原始dfyuanshi_df = pd.read_excel(filepath)if not yuanshi_df.empty:QtWidgets.QMessageBox.information(None, "成功", "上傳成功!")return yuanshi_dfelse:QtWidgets.QMessageBox.critical(None, "提示", "請檢查操作步驟或上傳文件!")else:QtWidgets.QMessageBox.critical(None, "提示", "請選擇XLSX工作表類型!")return pd.DataFrame()if __name__ == '__main__':app = QApplication(sys.argv)w = mainwindow()w.show()sys.exit(app.exec_())
僅以成績表作為示例,上傳其他任意Excel均可!
歡迎留言/私信溝通交流!