基于Python和tkinter开发一个SQLite数据库可视化小工具

 更新时间:2026年03月10日 08:42:52   作者:学习&实践爱好者  
这篇文章主要为大家详细介绍了一个基于Python tkinter开发的SQLite数据库可视化工具,该工具具有简洁的GUI界面,支持查看数据库表结构、浏览数据内容并能将数据导出为CSV文件,感兴趣的小伙伴可以了解下

基于 Python tkinter 开发的SQLite数据库可视化小工具

一个基于 Python tkinter 开发的桌面 GUI 应用程序,特别适合需要快速查看 SQLite 数据库内容的场景——用于可视化查看 SQLite 数据库的结构和数据,并能将表数据导出为CSV文件。

程序采用防御式编程,处理了特殊表名、NULL值等边界情况,并优化了列名获取方式确保数据准确性。该工具适用于需要快速查看和分析SQLite数据库内容的开发场景。

运行界面界面截图:

运行流程图:

源码(参考自网路,进行了适当修改)如下:

# 修复列出的表不全以及表内容(列)不完整问题
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import sqlite3
import os
from datetime import datetime
 
class SQLiteViewer:
    def __init__(self, root):
        self.root = root
        self.root.title("SQLite可视化工具 V2.01")
        self.root.geometry("1100x750")
        self.root.minsize(800, 600)
         
        # 数据库连接状态
        self.db_connection = None
        self.db_path = None
         
        # 初始化UI
        self.setup_ui()
         
    def setup_ui(self):
        """设置界面布局"""
        # 顶部控制面板
        control_frame = ttk.Frame(self.root, padding="10")
        control_frame.pack(fill=tk.X, anchor=tk.W)
         
        # 打开数据库按钮
        self.open_btn = ttk.Button(control_frame, text="选择SQLite数据库", command=self.open_database)
        self.open_btn.pack(side=tk.LEFT, padx=5)
         
        # 数据库路径显示
        self.db_label = ttk.Label(control_frame, text="未选择数据库", foreground="gray")
        self.db_label.pack(side=tk.LEFT, padx=10, fill=tk.X, expand=True)
         
        # 导出按钮(默认禁用)
        self.export_btn = ttk.Button(control_frame, text="导出数据", command=self.export_data, state=tk.DISABLED)
        self.export_btn.pack(side=tk.RIGHT, padx=5)
         
        # 中间选择区域
        select_frame = ttk.Frame(self.root, padding="10")
        select_frame.pack(fill=tk.X, anchor=tk.W)
         
        ttk.Label(select_frame, text="数据表列表:").pack(side=tk.LEFT, padx=5)
         
        # 表选择下拉框
        self.table_var = tk.StringVar()
        self.table_combobox = ttk.Combobox(select_frame, textvariable=self.table_var, state="readonly", width=30)
        self.table_combobox.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
        self.table_combobox.bind("<<ComboboxSelected>>", self.on_table_selected)
         
        # 底部显示区域(分为结构和数据两个标签页)
        tab_control = ttk.Notebook(self.root)
        tab_control.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
         
        # 表结构标签页
        self.structure_tab = ttk.Frame(tab_control)
        tab_control.add(self.structure_tab, text="表结构")
         
        # 数据标签页
        self.data_tab = ttk.Frame(tab_control)
        tab_control.add(self.data_tab, text="表数据")
         
        # 初始化表结构显示区域
        self.init_structure_display()
         
        # 初始化数据显示区域
        self.init_data_display()
         
    def init_structure_display(self):
        """初始化表结构显示区域"""
        # 创建滚动文本框显示表结构
        self.structure_text = scrolledtext.ScrolledText(self.structure_tab, wrap=tk.WORD, font=("Consolas", 10))
        self.structure_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        self.structure_text.insert(tk.END, "请先选择数据库文件,然后选择数据表查看结构...")
        self.structure_text.config(state=tk.DISABLED)
         
    def init_data_display(self):
        """初始化数据显示区域"""
        # 创建表格
        self.data_tree = ttk.Treeview(self.data_tab, show="headings")
        self.data_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)
         
        # 添加滚动条
        scrollbar = ttk.Scrollbar(self.data_tab, orient=tk.VERTICAL, command=self.data_tree.yview)
        self.data_tree.configure(yscrollcommand=scrollbar.set)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y, padx=(0,5), pady=5)
         
        # 初始提示
        self.data_tree["columns"] = ("tip",)
        self.data_tree.heading("tip", text="提示信息")
        self.data_tree.insert("", tk.END, values=("请先选择数据库文件和数据表查看数据...",))
         
    def open_database(self):
        """打开SQLite数据库文件"""
        file_path = filedialog.askopenfilename(
            title="选择SQLite数据库文件",
            filetypes=[("SQLite Files", "*.db *.sqlite *.sqlite3"), ("All Files", "*.*")]
        )
         
        if not file_path:
            return
         
        try:
            # 关闭之前的连接
            if self.db_connection:
                self.db_connection.close()
             
            # 建立新连接
            self.db_connection = sqlite3.connect(file_path)
            self.db_path = file_path
             
            # 更新界面显示
            self.db_label.config(text=f"当前数据库:{os.path.basename(file_path)}", foreground="black")
            self.export_btn.config(state=tk.NORMAL)
             
            # 获取所有表名并更新下拉框
            tables = self.get_all_tables()
            self.table_combobox["values"] = tables
             
            # 清空之前的显示
            self.clear_structure_display()
            self.clear_data_display()
             
            if tables:
                messagebox.showinfo("成功", f"成功连接数据库!共发现 {len(tables)} 个数据表")
            else:
                messagebox.showwarning("警告", "数据库连接成功,但未发现任何数据表")
                 
        except sqlite3.Error as e:
            messagebox.showerror("错误", f"数据库连接失败:{str(e)}")
            self.db_connection = None
            self.db_path = None
            self.db_label.config(text="连接失败", foreground="red")
            self.export_btn.config(state=tk.DISABLED)
     
    def get_all_tables(self):
        """获取数据库中所有表名和视图名"""
        if not self.db_connection:
            return []
         
        cursor = self.db_connection.cursor()
        try:
            # 兼容性最好、最稳妥的查询方式:同时获取表和视图
            cursor.execute("""
                SELECT name FROM sqlite_master 
                WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%'
                ORDER BY name
            """)
            tables = [row[0] for row in cursor.fetchall()]
        except sqlite3.Error as e:
            print(f"获取表列表失败: {e}")
            tables = []
        finally:
            cursor.close()
            
        return tables

     
    def on_table_selected(self, event):
        """当选择不同表时的处理"""
        selected_table = self.table_var.get()
        if not selected_table or not self.db_connection:
            return
         
        # 显示表结构
        self.show_table_structure(selected_table)
         
        # 显示表数据
        self.show_table_data(selected_table)
     
    def show_table_structure(self, table_name):
        """显示表结构信息"""
        cursor = self.db_connection.cursor()
         
        # 核心防错:必须处理表名中的双引号并用双引号包裹,否则"Order Details"这种带空格的表会报错
        safe_table = table_name.replace('"', '""')
         
        # 获取表结构
        cursor.execute(f'PRAGMA table_info("{safe_table}")')
        structure = cursor.fetchall()
         
        # 获取表的创建语句 (使用 ? 占位符最安全)
        cursor.execute("""
            SELECT sql FROM sqlite_master 
            WHERE name=? AND type IN ('table', 'view')
        """, (table_name,))
        create_sql = cursor.fetchone()
        create_sql = create_sql[0] if create_sql and create_sql[0] else "未找到创建语句(可能是视图或系统表)"
         
        cursor.close()
         
        # 更新显示
        self.clear_structure_display()
        self.structure_text.config(state=tk.NORMAL)
         
        # 写入表基本信息
        self.structure_text.insert(tk.END, f"=== 结构:{table_name} ===\n\n")
        self.structure_text.insert(tk.END, f"创建语句:\n{create_sql}\n\n")
        self.structure_text.insert(tk.END, "字段信息:\n")
        self.structure_text.insert(tk.END, f"{'序号':<5} {'字段名':<20} {'类型':<15} {'是否主键':<10} {'是否允许空':<10} {'默认值':<15}\n")
        self.structure_text.insert(tk.END, "-" * 80 + "\n")
         
        for field in structure:
            cid, name, type_, notnull, dflt_value, pk = field
            is_pk = "是" if pk else "否"
            is_nullable = "否" if notnull else "是"
            default = dflt_value if dflt_value is not None else "无"
             
            self.structure_text.insert(tk.END, f"{cid:<5} {name:<20} {type_:<15} {is_pk:<10} {is_nullable:<10} {default:<15}\n")
         
        self.structure_text.config(state=tk.DISABLED)

     
    def show_table_data(self, table_name):
        """显示表数据"""
        cursor = self.db_connection.cursor()
        safe_table = table_name.replace('"', '""')
         
        try:
            # 获取所有数据 (使用安全表名包裹)
            cursor.execute(f'SELECT * FROM "{safe_table}"')
            data = cursor.fetchall()
             
            # 【核心修复】必须使用 cursor.description 获取实际的列名,
            # 绝对不要用 PRAGMA 去匹配,以防止列数量错位或视图引发错误
            if cursor.description:
                columns = [desc[0] for desc in cursor.description]
            else:
                columns = []
             
            cursor.close()
             
            # 清空之前的数据
            self.clear_data_display()
             
            # 设置表格列
            if columns:
                self.data_tree["columns"] = columns
                for col in columns:
                    self.data_tree.heading(col, text=col)
                    self.data_tree.column(col, width=120, anchor=tk.CENTER)
            else:
                # 极端情况:表存在但连列名都没有定义
                self.data_tree["columns"] = ("tip",)
                self.data_tree.heading("tip", text="提示")
             
            # 插入数据
            if data:
                for i, row in enumerate(data):
                    # 处理None值
                    formatted_row = [str(val) if val is not None else "NULL" for val in row]
                    self.data_tree.insert("", tk.END, iid=i, values=formatted_row)
            else:
                # 没有数据时显示提示
                col_tuple = ("该表暂无数据",) + ("",) * (len(columns) - 1) if columns else ("该表没有定义列",)
                self.data_tree.insert("", tk.END, values=col_tuple)
                 
        except sqlite3.Error as e:
            self.clear_data_display()
            self.data_tree["columns"] = ("error",)
            self.data_tree.heading("error", text="错误信息")
            self.data_tree.column("error", anchor=tk.W)
            self.data_tree.insert("", tk.END, values=(f"查询失败:{str(e)}",))

     
    def clear_structure_display(self):
        """清空表结构显示"""
        self.structure_text.config(state=tk.NORMAL)
        self.structure_text.delete(1.0, tk.END)
        self.structure_text.config(state=tk.DISABLED)
     
    def clear_data_display(self):
        """清空数据表格"""
        # 删除所有列
        for col in self.data_tree["columns"]:
            self.data_tree.heading(col, text="")
        self.data_tree["columns"] = ()
         
        # 删除所有行
        for item in self.data_tree.get_children():
            self.data_tree.delete(item)
     
    def export_data(self):
        """导出当前表的数据"""
        selected_table = self.table_var.get()
        if not selected_table or not self.db_connection:
            messagebox.showwarning("警告", "请先选择数据库和数据表")
            return
         
        # 选择保存路径
        file_path = filedialog.asksaveasfilename(
            title="导出数据",
            defaultextension=".csv",
            filetypes=[("CSV文件", "*.csv"), ("文本文件", "*.txt"), ("所有文件", "*.*")]
        )
         
        if not file_path:
            return
         
        cursor = self.db_connection.cursor()
        safe_table = selected_table.replace('"', '""')
         
        try:
            # 统一修改使用游标去获取列名确保绝对匹配
            cursor.execute(f'SELECT * FROM "{safe_table}"')
            data = cursor.fetchall()
            columns = [desc[0] for desc in cursor.description] if cursor.description else []
             
            cursor.close()
             
            # 写入文件
            with open(file_path, "w", encoding="utf-8-sig", newline="") as f:
                # 写入表头
                if columns:
                    f.write(",".join(columns) + "\n")
                 
                # 写入数据
                if data:
                    for row in data:
                        formatted_row = []
                        for val in row:
                            if val is None:
                                formatted_val = "NULL"
                            elif isinstance(val, str):
                                if "," in val or "\n" in val or '"' in val:
                                    formatted_val = '"' + val.replace('"', '""') + '"'
                                else:
                                    formatted_val = val
                            else:
                                formatted_val = str(val)
                            formatted_row.append(formatted_val)
                         
                        f.write(",".join(formatted_row) + "\n")
             
            messagebox.showinfo("成功", f"数据已成功导出到:\n{file_path}")
             
        except Exception as e:
            messagebox.showerror("错误", f"导出数据失败:{str(e)}")

     
    def __del__(self):
        """程序退出时关闭数据库连接"""
        if self.db_connection:
            self.db_connection.close()
 
if __name__ == "__main__":
    root = tk.Tk()
    app = SQLiteViewer(root)
    root.mainloop()

到此这篇关于基于Python和tkinter开发一个SQLite数据库可视化小工具的文章就介绍到这了,更多相关Python SQLite数据库可视化内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

最新评论