2025年10月17日 星期五

SQLite 資料庫 -5 一對多關聯

SQLite 資料庫 -5 一對多關聯 


「一對多關聯」圖片(老師 $\rightarrow$ 課程),將 teacher2 (老師資料表) 和 course2 (課程資料表) 的管理功能整合到一個單一的 Tkinter 應用程式中。

這個程式將使用 Notebook (分頁) 來分別管理老師和課程資料,並確保課程資料的「老師編號」有外來鍵約束。


操作步驟:

  • 1. 建立資料庫: 點擊頂部的「1. 建立資料庫」按鈕,會創建 MySchoolDB.db 檔案,以及 teacher2course2 兩個資料表。

  • 切換分頁: 使用上方的「老師資料 (teacher2)」和「課程資料 (course2)」分頁進行不同資料表的管理。

  • 老師資料 (Teacher2) 操作: 在第一個分頁中,可以新增、更正、刪除老師資料。

  • 課程資料 (Course2) 操作: 在第二個分頁中,可以新增、更正、刪除課程資料。在新增或更正時,老師編號 必須填入一個在 老師資料 分頁中存在的編號,否則會新增失敗。

  • 6. 刪除資料庫: 點擊頂部的「6. 刪除資料庫」會刪除整個 .db 檔案。




import tkinter as tk

from tkinter import messagebox, ttk

import sqlite3

import os


DB_NAME = "MySchoolDB.db"


# --- 資料庫操作類別 ---

class DatabaseManager:

    """管理 MySchoolDB.db 中的 teacher2 和 course2 資料表的 CRUD 操作。"""


    def __init__(self, db_name=DB_NAME):

        self.db_name = db_name


    def connect(self):

        """建立資料庫連線並啟用外來鍵約束"""

        try:

            conn = sqlite3.connect(self.db_name)

            # 必須啟用外來鍵約束

            conn.execute("PRAGMA foreign_keys = ON;")

            return conn

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"無法連線到資料庫: {e}")

            return None


    # --- 1. 建立資料庫 ---

    def create_tables(self):

        """建立 teacher2 和 course2 兩個資料表"""

        conn = self.connect()

        if not conn: return False


        try:

            cursor = conn.cursor()

            

            # 1. 建立 teacher2 (老師資料表 - 主表)

            # 欄位: *老師編號 (主鍵), 老師姓名, 研究領域

            cursor.execute('''

                CREATE TABLE IF NOT EXISTS teacher2 (

                    老師編號 TEXT PRIMARY KEY,

                    老師姓名 TEXT NOT NULL,

                    研究領域 TEXT

                )

            ''')

            

            # 2. 建立 course2 (課程資料表 - 從表, 包含外來鍵)

            # 欄位: *課程代號 (主鍵), 課程名稱, 學分數, #老師編號 (外來鍵)

            cursor.execute('''

                CREATE TABLE IF NOT EXISTS course2 (

                    課程代號 TEXT PRIMARY KEY,

                    課程名稱 TEXT NOT NULL,

                    學分數 INTEGER,

                    老師編號 TEXT,

                    FOREIGN KEY (老師編號) REFERENCES teacher2(老師編號) ON DELETE SET NULL ON UPDATE CASCADE

                )

            ''')

            

            conn.commit()

            return True

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"建立資料表失敗: {e}")

            return False

        finally:

            if conn: conn.close()


    # --- 2/3/4/5. 老師資料 (teacher2) CRUD 操作 ---

    def execute_teacher_crud(self, query, params=None, action="select"):

        """執行老師資料表的 CRUD 操作"""

        conn = self.connect()

        if not conn: return False if action != "select" else []

        

        try:

            cursor = conn.cursor()

            if params:

                cursor.execute(query, params)

            else:

                cursor.execute(query)

                

            if action == "select":

                return cursor.fetchall()

            

            conn.commit()

            return cursor.rowcount

        except sqlite3.IntegrityError as e:

            if "UNIQUE constraint failed" in str(e):

                 messagebox.showwarning("操作失敗", "該老師編號已存在!")

            else:

                 messagebox.showerror("資料庫錯誤", f"{action} 操作失敗: {e}")

            conn.rollback()

            return False

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"{action} 操作失敗: {e}")

            conn.rollback()

            return False

        finally:

            if conn: conn.close()


    # --- 2/3/4/5. 課程資料 (course2) CRUD 操作 ---

    def execute_course_crud(self, query, params=None, action="select"):

        """執行課程資料表的 CRUD 操作"""

        conn = self.connect()

        if not conn: return False if action != "select" else []

        

        try:

            cursor = conn.cursor()

            if params:

                cursor.execute(query, params)

            else:

                cursor.execute(query)

                

            if action == "select":

                # 為了顯示完整的資訊,使用 LEFT JOIN 連接 teacher2 表

                return cursor.fetchall() 

            

            conn.commit()

            return cursor.rowcount

        except sqlite3.IntegrityError as e:

            if "UNIQUE constraint failed" in str(e):

                 messagebox.showwarning("操作失敗", "該課程代號已存在!")

            elif "foreign key constraint failed" in str(e):

                 messagebox.showwarning("操作失敗", "老師編號不存在,請先新增老師資料!")

            else:

                 messagebox.showerror("資料庫錯誤", f"{action} 操作失敗: {e}")

            conn.rollback()

            return False

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"{action} 操作失敗: {e}")

            conn.rollback()

            return False

        finally:

            if conn: conn.close()

            

    # --- 6. 刪除資料庫 ---

    def delete_database_file(self):

        """刪除資料庫檔案"""

        if os.path.exists(self.db_name):

            try:

                # 確保檔案沒有被鎖定

                os.remove(self.db_name)

                return True

            except OSError as e:

                messagebox.showerror("刪除失敗", f"無法刪除資料庫檔案 {self.db_name}: {e}\n請確認是否有其他程式正在使用此檔案。")

                return False

        else:

            messagebox.showwarning("刪除失敗", f"資料庫檔案 {self.db_name} 不存在。")

            return False


# --- Tkinter GUI 應用程式類別 ---

class IntegratedManager(tk.Tk):

    def __init__(self):

        super().__init__()

        self.title("學校資料管理系統 (整合版)")

        self.db = DatabaseManager()

        self.geometry("850x550")

        

        self.create_main_widgets()

        self.load_teacher_data() # 啟動時載入老師資料

        self.load_course_data()  # 啟動時載入課程資料


    def create_main_widgets(self):

        # 頂部功能按鈕

        button_frame = tk.Frame(self, padx=10, pady=5)

        button_frame.pack(fill='x')


        buttons = [

            ("1. 建立資料庫", self.create_db_command),

            ("6. 刪除資料庫", self.delete_db_command),

        ]


        for i, (text, command) in enumerate(buttons):

            btn = tk.Button(button_frame, text=text, command=command, width=15)

            btn.grid(row=0, column=i, padx=5, pady=5)

        

        # Notebook 分頁

        self.notebook = ttk.Notebook(self)

        self.notebook.pack(pady=10, padx=10, expand=True, fill="both")


        # 老師資料分頁

        self.teacher_frame = ttk.Frame(self.notebook)

        self.notebook.add(self.teacher_frame, text=' 老師資料 (teacher2) ')

        self.create_teacher_widgets(self.teacher_frame)


        # 課程資料分頁

        self.course_frame = ttk.Frame(self.notebook)

        self.notebook.add(self.course_frame, text=' 課程資料 (course2) ')

        self.create_course_widgets(self.course_frame)


    # ==================================

    # === 老師資料 (Teacher2) 介面與功能 ===

    # ==================================

    def create_teacher_widgets(self, parent):

        # 輸入區

        input_frame = tk.LabelFrame(parent, text="輸入/查詢區", padx=10, pady=10)

        input_frame.pack(fill='x', padx=10, pady=5)


        tk.Label(input_frame, text="老師編號:").grid(row=0, column=0, padx=5, pady=2, sticky='w')

        self.t_entry_id = tk.Entry(input_frame)

        self.t_entry_id.grid(row=0, column=1, padx=5, pady=2, sticky='w')


        tk.Label(input_frame, text="老師姓名:").grid(row=0, column=2, padx=5, pady=2, sticky='w')

        self.t_entry_name = tk.Entry(input_frame)

        self.t_entry_name.grid(row=0, column=3, padx=5, pady=2, sticky='w')

        

        tk.Label(input_frame, text="研究領域:").grid(row=1, column=0, padx=5, pady=2, sticky='w')

        self.t_entry_field = tk.Entry(input_frame)

        self.t_entry_field.grid(row=1, column=1, padx=5, pady=2, sticky='w')


        # 按鈕區塊

        t_btn_frame = tk.Frame(parent, padx=10, pady=5)

        t_btn_frame.pack(fill='x')


        tk.Button(t_btn_frame, text="2. 新增資料", command=self.add_teacher_data).grid(row=0, column=0, padx=5)

        tk.Button(t_btn_frame, text="3. 更正資料", command=self.update_teacher_data).grid(row=0, column=1, padx=5)

        tk.Button(t_btn_frame, text="4. 刪除資料", command=self.delete_teacher_data).grid(row=0, column=2, padx=5)

        tk.Button(t_btn_frame, text="5. 顯示所有資料", command=self.load_teacher_data).grid(row=0, column=3, padx=5)

            

        # Treeview 顯示區

        self.teacher_tree = ttk.Treeview(parent, columns=('老師編號', '老師姓名', '研究領域'), show='headings')

        self.teacher_tree.heading('老師編號', text='老師編號')

        self.teacher_tree.column('老師編號', width=120, anchor='center')

        self.teacher_tree.heading('老師姓名', text='老師姓名')

        self.teacher_tree.column('老師姓名', width=150, anchor='w')

        self.teacher_tree.heading('研究領域', text='研究領域')

        self.teacher_tree.column('研究領域', width=200, anchor='w')

        self.teacher_tree.pack(fill='both', expand=True, padx=10, pady=10)

        

        self.teacher_tree.bind('<<TreeviewSelect>>', self.on_teacher_select)


    def on_teacher_select(self, event):

        selected_item = self.teacher_tree.focus()

        if selected_item:

            values = self.teacher_tree.item(selected_item, 'values')

            self.clear_teacher_entries()

            self.t_entry_id.insert(0, values[0])

            self.t_entry_name.insert(0, values[1])

            self.t_entry_field.insert(0, values[2])

            

    def clear_teacher_entries(self):

        self.t_entry_id.delete(0, tk.END)

        self.t_entry_name.delete(0, tk.END)

        self.t_entry_field.delete(0, tk.END)


    # 5. 顯示所有資料 (老師)

    def load_teacher_data(self):

        for item in self.teacher_tree.get_children():

            self.teacher_tree.delete(item)

            

        query = "SELECT 老師編號, 老師姓名, 研究領域 FROM teacher2 ORDER BY 老師編號"

        data = self.db.execute_teacher_crud(query, action="select")

        

        for row in data:

            self.teacher_tree.insert('', tk.END, values=row)


    # 2. 新增資料 (老師)

    def add_teacher_data(self):

        teacher_id = self.t_entry_id.get().strip()

        name = self.t_entry_name.get().strip()

        field = self.t_entry_field.get().strip()

        

        if not teacher_id or not name:

            messagebox.showwarning("警告", "老師編號和姓名為必填!")

            return


        query = "INSERT INTO teacher2 (老師編號, 老師姓名, 研究領域) VALUES (?, ?, ?)"

        if self.db.execute_teacher_crud(query, (teacher_id, name, field), action="insert"):

            messagebox.showinfo("成功", "老師資料新增成功!")

            self.clear_teacher_entries()

            self.load_teacher_data()


    # 3. 更正資料 (老師)

    def update_teacher_data(self):

        teacher_id = self.t_entry_id.get().strip()

        name = self.t_entry_name.get().strip()

        field = self.t_entry_field.get().strip()

        

        if not teacher_id or not name:

            messagebox.showwarning("警告", "請輸入要更正的老師編號和新的姓名!")

            return


        query = "UPDATE teacher2 SET 老師姓名=?, 研究領域=? WHERE 老師編號=?"

        if self.db.execute_teacher_crud(query, (name, field, teacher_id), action="update"):

            messagebox.showinfo("成功", f"老師編號 {teacher_id} 資料更正成功!")

            self.clear_teacher_entries()

            self.load_teacher_data()

            self.load_course_data() # 老師資料變動可能影響課程顯示


    # 4. 刪除資料 (老師)

    def delete_teacher_data(self):

        teacher_id = self.t_entry_id.get().strip()

        if not teacher_id:

            messagebox.showwarning("警告", "請輸入要刪除的老師編號!")

            return


        # 由於 teacher2 是 course2 的主鍵,刪除會影響 course2 中的外來鍵 (ON DELETE SET NULL)

        if messagebox.askyesno("確認刪除", f"**警告:** 確定要刪除老師編號 {teacher_id} 的資料嗎?\n相關課程的老師編號將被設為 NULL。"):

            query = "DELETE FROM teacher2 WHERE 老師編號=?"

            if self.db.execute_teacher_crud(query, (teacher_id,), action="delete"):

                messagebox.showinfo("成功", "老師資料刪除成功!")

                self.clear_teacher_entries()

                self.load_teacher_data()

                self.load_course_data() # 更新課程顯示


    # ==================================

    # === 課程資料 (Course2) 介面與功能 ===

    # ==================================

    def create_course_widgets(self, parent):

        # 輸入區

        input_frame = tk.LabelFrame(parent, text="輸入/查詢區", padx=10, pady=10)

        input_frame.pack(fill='x', padx=10, pady=5)


        tk.Label(input_frame, text="課程代號:").grid(row=0, column=0, padx=5, pady=2, sticky='w')

        self.c_entry_id = tk.Entry(input_frame)

        self.c_entry_id.grid(row=0, column=1, padx=5, pady=2, sticky='w')


        tk.Label(input_frame, text="課程名稱:").grid(row=0, column=2, padx=5, pady=2, sticky='w')

        self.c_entry_name = tk.Entry(input_frame)

        self.c_entry_name.grid(row=0, column=3, padx=5, pady=2, sticky='w')

        

        tk.Label(input_frame, text="學分數:").grid(row=1, column=0, padx=5, pady=2, sticky='w')

        self.c_entry_credits = tk.Entry(input_frame)

        self.c_entry_credits.grid(row=1, column=1, padx=5, pady=2, sticky='w')

        

        tk.Label(input_frame, text="老師編號 (#外來鍵):").grid(row=1, column=2, padx=5, pady=2, sticky='w')

        self.c_entry_teacher_id = tk.Entry(input_frame)

        self.c_entry_teacher_id.grid(row=1, column=3, padx=5, pady=2, sticky='w')


        # 按鈕區塊

        c_btn_frame = tk.Frame(parent, padx=10, pady=5)

        c_btn_frame.pack(fill='x')


        tk.Button(c_btn_frame, text="2. 新增資料", command=self.add_course_data).grid(row=0, column=0, padx=5)

        tk.Button(c_btn_frame, text="3. 更正資料", command=self.update_course_data).grid(row=0, column=1, padx=5)

        tk.Button(c_btn_frame, text="4. 刪除資料", command=self.delete_course_data).grid(row=0, column=2, padx=5)

        tk.Button(c_btn_frame, text="5. 顯示所有資料", command=self.load_course_data).grid(row=0, column=3, padx=5)

            

        # Treeview 顯示區

        self.course_tree = ttk.Treeview(parent, columns=('代號', '名稱', '學分', '老師編號', '老師姓名'), show='headings')

        self.course_tree.heading('代號', text='課程代號')

        self.course_tree.column('代號', width=100, anchor='center')

        self.course_tree.heading('名稱', text='課程名稱')

        self.course_tree.column('名稱', width=180, anchor='w')

        self.course_tree.heading('學分', text='學分數')

        self.course_tree.column('學分', width=80, anchor='center')

        self.course_tree.heading('老師編號', text='老師編號')

        self.course_tree.column('老師編號', width=120, anchor='center')

        self.course_tree.heading('老師姓名', text='授課老師')

        self.course_tree.column('老師姓名', width=150, anchor='w')

        self.course_tree.pack(fill='both', expand=True, padx=10, pady=10)

        

        self.course_tree.bind('<<TreeviewSelect>>', self.on_course_select)


    def on_course_select(self, event):

        selected_item = self.course_tree.focus()

        if selected_item:

            values = self.course_tree.item(selected_item, 'values')

            self.clear_course_entries()

            self.c_entry_id.insert(0, values[0])      # 課程代號

            self.c_entry_name.insert(0, values[1])    # 課程名稱

            self.c_entry_credits.insert(0, values[2]) # 學分數

            self.c_entry_teacher_id.insert(0, values[3]) # 老師編號

            

    def clear_course_entries(self):

        self.c_entry_id.delete(0, tk.END)

        self.c_entry_name.delete(0, tk.END)

        self.c_entry_credits.delete(0, tk.END)

        self.c_entry_teacher_id.delete(0, tk.END)


    # 5. 顯示所有資料 (課程)

    def load_course_data(self):

        for item in self.course_tree.get_children():

            self.course_tree.delete(item)

            

        # 查詢課程資料並連老師姓名

        query = '''

            SELECT 

                c.課程代號, c.課程名稱, c.學分數, c.老師編號, t.老師姓名

            FROM 

                course2 c

            LEFT JOIN

                teacher2 t ON c.老師編號 = t.老師編號

            ORDER BY c.課程代號

        '''

        data = self.db.execute_course_crud(query, action="select")

        

        for row in data:

             # 將 None 替換為空字串以便 Treeview 顯示

            processed_row = tuple('' if x is None else x for x in row)

            self.course_tree.insert('', tk.END, values=processed_row)


    # 2. 新增資料 (課程)

    def add_course_data(self):

        course_id = self.c_entry_id.get().strip()

        name = self.c_entry_name.get().strip()

        credits_str = self.c_entry_credits.get().strip()

        teacher_id = self.c_entry_teacher_id.get().strip()

        

        if not course_id or not name or not credits_str:

            messagebox.showwarning("警告", "課程代號、名稱和學分數為必填!")

            return

        

        try:

            credits = int(credits_str)

        except ValueError:

            messagebox.showwarning("警告", "學分數必須是數字!")

            return


        query = "INSERT INTO course2 (課程代號, 課程名稱, 學分數, 老師編號) VALUES (?, ?, ?, ?)"

        if self.db.execute_course_crud(query, (course_id, name, credits, teacher_id), action="insert"):

            messagebox.showinfo("成功", "課程資料新增成功!")

            self.clear_course_entries()

            self.load_course_data()


    # 3. 更正資料 (課程)

    def update_course_data(self):

        course_id = self.c_entry_id.get().strip()

        name = self.c_entry_name.get().strip()

        credits_str = self.c_entry_credits.get().strip()

        teacher_id = self.c_entry_teacher_id.get().strip()

        

        if not course_id or not name or not credits_str:

            messagebox.showwarning("警告", "請輸入完整的更正資訊!")

            return

            

        try:

            credits = int(credits_str)

        except ValueError:

            messagebox.showwarning("警告", "學分數必須是數字!")

            return


        query = "UPDATE course2 SET 課程名稱=?, 學分數=?, 老師編號=? WHERE 課程代號=?"

        if self.db.execute_course_crud(query, (name, credits, teacher_id, course_id), action="update"):

            messagebox.showinfo("成功", f"課程代號 {course_id} 資料更正成功!")

            self.clear_course_entries()

            self.load_course_data()


    # 4. 刪除資料 (課程)

    def delete_course_data(self):

        course_id = self.c_entry_id.get().strip()

        if not course_id:

            messagebox.showwarning("警告", "請輸入要刪除的課程代號!")

            return


        if messagebox.askyesno("確認刪除", f"確定要刪除課程代號 {course_id} 的資料嗎?"):

            query = "DELETE FROM course2 WHERE 課程代號=?"

            if self.db.execute_course_crud(query, (course_id,), action="delete"):

                messagebox.showinfo("成功", "課程資料刪除成功!")

                self.clear_course_entries()

                self.load_course_data()


    # ==================================

    # === 共同功能 ===

    # ==================================

    # 1. 建立資料庫

    def create_db_command(self):

        if self.db.create_tables():

            messagebox.showinfo("成功", f"資料庫 {DB_NAME} 及其所有資料表已成功建立或已存在。")

            self.load_teacher_data()

            self.load_course_data()


    # 6. 刪除資料庫

    def delete_db_command(self):

        if messagebox.askyesno("確認刪除資料庫", f"**警告:** 確定要刪除整個資料庫檔案 {DB_NAME} 嗎?所有資料將會遺失!"):

            if self.db.delete_database_file():

                messagebox.showinfo("成功", "資料庫檔案已刪除。")

                self.load_teacher_data() 

                self.load_course_data() 



if __name__ == '__main__':

    app = IntegratedManager()

    app.mainloop()



兩個獨立的 Python 程式:

  1. teacher_manager.py:管理 MySchoolDB.db 中的 teacher2 (老師資料表)。

  2. course_manager.py:管理 MySchoolDB.db 中的 course2 (課程資料表),並包含外來鍵 (#老師編號) 的邏輯。

好的,根據您提供的「一對多關聯」圖片(老師資料表和課程資料表)和您的要求,我將把程式碼修改並分為兩個獨立的 Python 程式:

  1. teacher_manager.py:管理 MySchoolDB.db 中的 teacher2 (老師資料表)。

  2. course_manager.py:管理 MySchoolDB.db 中的 course2 (課程資料表),並包含外來鍵 (#老師編號) 的邏輯。

這兩個程式都將包含您要求的所有 6 個功能。


程式一:teacher_manager.py (管理 老師資料表)

這個程式負責管理 teacher2 資料表,該表的欄位為:老師編號 (主鍵)、老師姓名研究領域

# teacher_manager.py



import tkinter as tk

from tkinter import messagebox, simpledialog, ttk

import sqlite3

import os


DB_NAME = "MySchoolDB.db"

TABLE_NAME = "teacher2"


# --- 資料庫操作類別 ---

class DatabaseManager:

    def __init__(self, db_name=DB_NAME, table_name=TABLE_NAME):

        self.db_name = db_name

        self.table_name = table_name


    def connect(self):

        """建立資料庫連線"""

        try:

            conn = sqlite3.connect(self.db_name)

            return conn

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"無法連線到資料庫: {e}")

            return None


    def create_table(self):

        """1. 建立資料表 (MySchoolDB.db 及 teacher2 表)"""

        conn = self.connect()

        if not conn:

            return False


        try:

            cursor = conn.cursor()

            # 建立 teacher2 資料表

            cursor.execute(f'''

                CREATE TABLE IF NOT EXISTS {self.table_name} (

                    老師編號 TEXT PRIMARY KEY,

                    老師姓名 TEXT NOT NULL,

                    研究領域 TEXT

                )

            ''')

            conn.commit()

            return True

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"建立資料表失敗: {e}")

            return False

        finally:

            if conn: conn.close()


    def insert_data(self, teacher_id, name, field):

        """2. 新增資料"""

        conn = self.connect()

        if not conn: return False

        

        try:

            cursor = conn.cursor()

            cursor.execute(f"INSERT INTO {self.table_name} (老師編號, 老師姓名, 研究領域) VALUES (?, ?, ?)", 

                           (teacher_id, name, field))

            conn.commit()

            return True

        except sqlite3.IntegrityError:

            messagebox.showwarning("新增失敗", f"老師編號 {teacher_id} 已存在!")

            return False

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"新增資料失敗: {e}")

            conn.rollback()

            return False

        finally:

            if conn: conn.close()


    def update_data(self, teacher_id, name, field):

        """3. 更正資料"""

        conn = self.connect()

        if not conn: return False

        

        try:

            cursor = conn.cursor()

            cursor.execute(f"UPDATE {self.table_name} SET 老師姓名=?, 研究領域=? WHERE 老師編號=?", 

                           (name, field, teacher_id))

            conn.commit()

            if cursor.rowcount == 0:

                messagebox.showwarning("更正失敗", f"老師編號 {teacher_id} 不存在!")

                return False

            return True

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"更正資料失敗: {e}")

            conn.rollback()

            return False

        finally:

            if conn: conn.close()


    def delete_data(self, teacher_id):

        """4. 刪除資料"""

        conn = self.connect()

        if not conn: return False


        try:

            cursor = conn.cursor()

            # 刪除老師資料

            cursor.execute(f"DELETE FROM {self.table_name} WHERE 老師編號=?", (teacher_id,))

            conn.commit()

            if cursor.rowcount == 0:

                messagebox.showwarning("刪除失敗", f"老師編號 {teacher_id} 不存在!")

                return False

            return True

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"刪除資料失敗: {e}")

            conn.rollback()

            return False

        finally:

            if conn: conn.close()

            

    def get_all_data(self):

        """5. 顯示所有資料"""

        conn = self.connect()

        if not conn: return []


        try:

            cursor = conn.cursor()

            cursor.execute(f"SELECT * FROM {self.table_name} ORDER BY 老師編號")

            rows = cursor.fetchall()

            return rows

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"查詢資料失敗: {e}")

            return []

        finally:

            if conn: conn.close()

            

    def delete_database_file(self):

        """6. 刪除資料庫檔案"""

        if os.path.exists(self.db_name):

            try:

                os.remove(self.db_name)

                return True

            except OSError as e:

                messagebox.showerror("刪除失敗", f"無法刪除資料庫檔案 {self.db_name}: {e}\n請確認是否有其他程式正在使用此檔案。")

                return False

        else:

            messagebox.showwarning("刪除失敗", f"資料庫檔案 {self.db_name} 不存在。")

            return False


# --- Tkinter GUI 應用程式類別 ---

class Application(tk.Tk):

    def __init__(self):

        super().__init__()

        self.title("老師資料管理系統 (teacher2)")

        self.db = DatabaseManager()

        self.geometry("600x450")

        

        self.create_widgets()

        self.load_data_to_treeview()


    def create_widgets(self):

        # 輸入區

        input_frame = tk.LabelFrame(self, text="輸入/查詢區", padx=10, pady=10)

        input_frame.pack(fill='x', padx=10, pady=5)


        tk.Label(input_frame, text="老師編號:").grid(row=0, column=0, padx=5, pady=2, sticky='w')

        self.entry_id = tk.Entry(input_frame)

        self.entry_id.grid(row=0, column=1, padx=5, pady=2, sticky='w')


        tk.Label(input_frame, text="老師姓名:").grid(row=0, column=2, padx=5, pady=2, sticky='w')

        self.entry_name = tk.Entry(input_frame)

        self.entry_name.grid(row=0, column=3, padx=5, pady=2, sticky='w')

        

        tk.Label(input_frame, text="研究領域:").grid(row=1, column=0, padx=5, pady=2, sticky='w')

        self.entry_field = tk.Entry(input_frame)

        self.entry_field.grid(row=1, column=1, padx=5, pady=2, sticky='w')


        # 按鈕區塊

        button_frame = tk.Frame(self, padx=10, pady=5)

        button_frame.pack(fill='x')


        buttons = [

            ("1. 建立資料庫", self.create_db_command),

            ("2. 新增資料", self.add_data_command),

            ("3. 更正資料", self.update_data_command),

            ("4. 刪除資料", self.delete_data_command),

            ("5. 顯示所有資料", self.load_data_to_treeview),

            ("6. 刪除資料庫", self.delete_db_command)

        ]


        for i, (text, command) in enumerate(buttons):

            btn = tk.Button(button_frame, text=text, command=command, width=12)

            btn.grid(row=0, column=i, padx=5, pady=5)

            

        # Treeview 顯示區

        self.tree = ttk.Treeview(self, columns=('老師編號', '老師姓名', '研究領域'), show='headings')

        self.tree.heading('老師編號', text='老師編號')

        self.tree.column('老師編號', width=120, anchor='center')

        self.tree.heading('老師姓名', text='老師姓名')

        self.tree.column('老師姓名', width=150, anchor='w')

        self.tree.heading('研究領域', text='研究領域')

        self.tree.column('研究領域', width=200, anchor='w')

        self.tree.pack(fill='both', expand=True, padx=10, pady=10)

        

        self.tree.bind('<<TreeviewSelect>>', self.on_select)


    def on_select(self, event):

        """點擊 Treeview 項目時,將資料填入輸入框"""

        selected_item = self.tree.focus()

        if selected_item:

            values = self.tree.item(selected_item, 'values')

            self.clear_entries()

            self.entry_id.insert(0, values[0])

            self.entry_name.insert(0, values[1])

            self.entry_field.insert(0, values[2])

            

    def clear_entries(self):

        """清空所有輸入框"""

        self.entry_id.delete(0, tk.END)

        self.entry_name.delete(0, tk.END)

        self.entry_field.delete(0, tk.END)


    def load_data_to_treeview(self):

        """5. 顯示所有資料"""

        for item in self.tree.get_children():

            self.tree.delete(item)

            

        data = self.db.get_all_data()

        

        for row in data:

            self.tree.insert('', tk.END, values=row)


    def create_db_command(self):

        """1. 建立資料庫"""

        if self.db.create_table():

            messagebox.showinfo("成功", f"資料庫 {DB_NAME} 及其資料表 {TABLE_NAME} 已成功建立或已存在。")

            self.load_data_to_treeview()


    def add_data_command(self):

        """2. 新增資料"""

        teacher_id = self.entry_id.get().strip()

        name = self.entry_name.get().strip()

        field = self.entry_field.get().strip()

        

        if not teacher_id or not name:

            messagebox.showwarning("警告", "老師編號和姓名為必填!")

            return


        if self.db.insert_data(teacher_id, name, field):

            messagebox.showinfo("成功", "資料新增成功!")

            self.clear_entries()

            self.load_data_to_treeview()


    def update_data_command(self):

        """3. 更正資料"""

        teacher_id = self.entry_id.get().strip()

        name = self.entry_name.get().strip()

        field = self.entry_field.get().strip()

        

        if not teacher_id or not name:

            messagebox.showwarning("警告", "請輸入要更正的老師編號和新的姓名!")

            return


        if self.db.update_data(teacher_id, name, field):

            messagebox.showinfo("成功", f"老師編號 {teacher_id} 資料更正成功!")

            self.clear_entries()

            self.load_data_to_treeview()


    def delete_data_command(self):

        """4. 刪除資料"""

        teacher_id = self.entry_id.get().strip()

        if not teacher_id:

            messagebox.showwarning("警告", "請輸入要刪除的老師編號!")

            return


        if messagebox.askyesno("確認刪除", f"確定要刪除老師編號 {teacher_id} 的資料嗎?"):

            if self.db.delete_data(teacher_id):

                messagebox.showinfo("成功", "資料刪除成功!")

                self.clear_entries()

                self.load_data_to_treeview()


    def delete_db_command(self):

        """6. 刪除資料庫檔案"""

        if messagebox.askyesno("確認刪除資料庫", f"**警告:** 確定要刪除整個資料庫檔案 {DB_NAME} 嗎?所有資料將會遺失!"):

            if self.db.delete_database_file():

                messagebox.showinfo("成功", "資料庫檔案已刪除。")

                self.load_data_to_treeview() # 清空顯示


if __name__ == '__main__':

    app = Application()

    app.mainloop()


程式二:course_manager.py (管理 課程資料表)

這個程式負責管理 course2 資料表,該表的欄位為:課程代號 (主鍵)、課程名稱學分數老師編號 (外來鍵)。

注意: 為了實現外來鍵的約束,我們必須在建立連線後啟用它 (PRAGMA foreign_keys = ON;)。





# course_manager.py


import tkinter as tk

from tkinter import messagebox, simpledialog, ttk

import sqlite3

import os


DB_NAME = "MySchoolDB.db"

TABLE_NAME = "course2"


# --- 資料庫操作類別 ---

class DatabaseManager:

    def __init__(self, db_name=DB_NAME, table_name=TABLE_NAME):

        self.db_name = db_name

        self.table_name = table_name


    def connect(self):

        """建立資料庫連線並啟用外來鍵約束"""

        try:

            conn = sqlite3.connect(self.db_name)

            # 啟用外來鍵約束

            conn.execute("PRAGMA foreign_keys = ON;")

            return conn

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"無法連線到資料庫: {e}")

            return None


    def create_table(self):

        """1. 建立資料表 (MySchoolDB.db 及 course2 表)"""

        conn = self.connect()

        if not conn:

            return False


        try:

            cursor = conn.cursor()

            # 建立 course2 資料表,包含外來鍵約束

            cursor.execute(f'''

                CREATE TABLE IF NOT EXISTS {self.table_name} (

                    課程代號 TEXT PRIMARY KEY,

                    課程名稱 TEXT NOT NULL,

                    學分數 INTEGER,

                    老師編號 TEXT,

                    FOREIGN KEY (老師編號) REFERENCES teacher2(老師編號) ON DELETE SET NULL ON UPDATE CASCADE

                )

            ''')

            conn.commit()

            return True

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"建立資料表失敗: {e}")

            return False

        finally:

            if conn: conn.close()


    def insert_data(self, course_id, name, credits, teacher_id):

        """2. 新增資料"""

        conn = self.connect()

        if not conn: return False

        

        try:

            cursor = conn.cursor()

            cursor.execute(f"INSERT INTO {self.table_name} (課程代號, 課程名稱, 學分數, 老師編號) VALUES (?, ?, ?, ?)", 

                           (course_id, name, credits, teacher_id))

            conn.commit()

            return True

        except sqlite3.IntegrityError as e:

            if "UNIQUE constraint failed" in str(e):

                messagebox.showwarning("新增失敗", f"課程代號 {course_id} 已存在!")

            elif "foreign key constraint failed" in str(e):

                messagebox.showwarning("新增失敗", f"老師編號 {teacher_id} 在 teacher2 表中不存在!")

            else:

                messagebox.showerror("資料庫錯誤", f"新增資料失敗: {e}")

            conn.rollback()

            return False

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"新增資料失敗: {e}")

            conn.rollback()

            return False

        finally:

            if conn: conn.close()


    def update_data(self, course_id, name, credits, teacher_id):

        """3. 更正資料"""

        conn = self.connect()

        if not conn: return False

        

        try:

            cursor = conn.cursor()

            cursor.execute(f"UPDATE {self.table_name} SET 課程名稱=?, 學分數=?, 老師編號=? WHERE 課程代號=?", 

                           (name, credits, teacher_id, course_id))

            conn.commit()

            if cursor.rowcount == 0:

                messagebox.showwarning("更正失敗", f"課程代號 {course_id} 不存在!")

                return False

            return True

        except sqlite3.IntegrityError as e:

            if "foreign key constraint failed" in str(e):

                messagebox.showwarning("更正失敗", f"老師編號 {teacher_id} 在 teacher2 表中不存在!")

            else:

                messagebox.showerror("資料庫錯誤", f"更正資料失敗: {e}")

            conn.rollback()

            return False

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"更正資料失敗: {e}")

            conn.rollback()

            return False

        finally:

            if conn: conn.close()


    def delete_data(self, course_id):

        """4. 刪除資料"""

        conn = self.connect()

        if not conn: return False


        try:

            cursor = conn.cursor()

            cursor.execute(f"DELETE FROM {self.table_name} WHERE 課程代號=?", (course_id,))

            conn.commit()

            if cursor.rowcount == 0:

                messagebox.showwarning("刪除失敗", f"課程代號 {course_id} 不存在!")

                return False

            return True

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"刪除資料失敗: {e}")

            conn.rollback()

            return False

        finally:

            if conn: conn.close()

            

    def get_all_data(self):

        """5. 顯示所有資料"""

        conn = self.connect()

        if not conn: return []


        try:

            cursor = conn.cursor()

            # 為了顯示完整的資訊,我們使用 LEFT JOIN 連接 teacher2 表

            cursor.execute(f'''

                SELECT 

                    c.課程代號, c.課程名稱, c.學分數, c.老師編號, t.老師姓名

                FROM 

                    {self.table_name} c

                LEFT JOIN

                    teacher2 t ON c.老師編號 = t.老師編號

                ORDER BY c.課程代號

            ''')

            rows = cursor.fetchall()

            return rows

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"查詢資料失敗: {e}")

            return []

        finally:

            if conn: conn.close()

            

    def delete_database_file(self):

        """6. 刪除資料庫檔案"""

        if os.path.exists(self.db_name):

            try:

                os.remove(self.db_name)

                return True

            except OSError as e:

                messagebox.showerror("刪除失敗", f"無法刪除資料庫檔案 {self.db_name}: {e}\n請確認是否有其他程式正在使用此檔案。")

                return False

        else:

            messagebox.showwarning("刪除失敗", f"資料庫檔案 {self.db_name} 不存在。")

            return False


# --- Tkinter GUI 應用程式類別 ---

class Application(tk.Tk):

    def __init__(self):

        super().__init__()

        self.title("課程資料管理系統 (course2)")

        self.db = DatabaseManager()

        self.geometry("800x450")

        

        self.create_widgets()

        self.load_data_to_treeview()


    def create_widgets(self):

        # 輸入區

        input_frame = tk.LabelFrame(self, text="輸入/查詢區", padx=10, pady=10)

        input_frame.pack(fill='x', padx=10, pady=5)


        tk.Label(input_frame, text="課程代號:").grid(row=0, column=0, padx=5, pady=2, sticky='w')

        self.entry_course_id = tk.Entry(input_frame)

        self.entry_course_id.grid(row=0, column=1, padx=5, pady=2, sticky='w')


        tk.Label(input_frame, text="課程名稱:").grid(row=0, column=2, padx=5, pady=2, sticky='w')

        self.entry_name = tk.Entry(input_frame)

        self.entry_name.grid(row=0, column=3, padx=5, pady=2, sticky='w')

        

        tk.Label(input_frame, text="學分數:").grid(row=1, column=0, padx=5, pady=2, sticky='w')

        self.entry_credits = tk.Entry(input_frame)

        self.entry_credits.grid(row=1, column=1, padx=5, pady=2, sticky='w')

        

        tk.Label(input_frame, text="老師編號:").grid(row=1, column=2, padx=5, pady=2, sticky='w')

        self.entry_teacher_id = tk.Entry(input_frame)

        self.entry_teacher_id.grid(row=1, column=3, padx=5, pady=2, sticky='w')


        # 按鈕區塊

        button_frame = tk.Frame(self, padx=10, pady=5)

        button_frame.pack(fill='x')


        buttons = [

            ("1. 建立資料庫", self.create_db_command),

            ("2. 新增資料", self.add_data_command),

            ("3. 更正資料", self.update_data_command),

            ("4. 刪除資料", self.delete_data_command),

            ("5. 顯示所有資料", self.load_data_to_treeview),

            ("6. 刪除資料庫", self.delete_db_command)

        ]


        for i, (text, command) in enumerate(buttons):

            btn = tk.Button(button_frame, text=text, command=command, width=12)

            btn.grid(row=0, column=i, padx=5, pady=5)

            

        # Treeview 顯示區

        self.tree = ttk.Treeview(self, columns=('代號', '名稱', '學分', '老師編號', '老師姓名'), show='headings')

        self.tree.heading('代號', text='課程代號')

        self.tree.column('代號', width=100, anchor='center')

        self.tree.heading('名稱', text='課程名稱')

        self.tree.column('名稱', width=180, anchor='w')

        self.tree.heading('學分', text='學分數')

        self.tree.column('學分', width=80, anchor='center')

        self.tree.heading('老師編號', text='老師編號')

        self.tree.column('老師編號', width=120, anchor='center')

        self.tree.heading('老師姓名', text='老師姓名')

        self.tree.column('老師姓名', width=180, anchor='w')

        self.tree.pack(fill='both', expand=True, padx=10, pady=10)

        

        self.tree.bind('<<TreeviewSelect>>', self.on_select)


    def on_select(self, event):

        """點擊 Treeview 項目時,將資料填入輸入框"""

        selected_item = self.tree.focus()

        if selected_item:

            values = self.tree.item(selected_item, 'values')

            self.clear_entries()

            self.entry_course_id.insert(0, values[0]) # 課程代號

            self.entry_name.insert(0, values[1])      # 課程名稱

            self.entry_credits.insert(0, values[2])   # 學分數

            self.entry_teacher_id.insert(0, values[3])# 老師編號

            

    def clear_entries(self):

        """清空所有輸入框"""

        self.entry_course_id.delete(0, tk.END)

        self.entry_name.delete(0, tk.END)

        self.entry_credits.delete(0, tk.END)

        self.entry_teacher_id.delete(0, tk.END)


    def load_data_to_treeview(self):

        """5. 顯示所有資料"""

        for item in self.tree.get_children():

            self.tree.delete(item)

            

        data = self.db.get_all_data()

        

        # 處理 'None' 顯示

        processed_data = []

        for row in data:

            # 將 None 替換為空字串或 'N/A' 以便顯示

            processed_data.append(tuple('' if x is None else x for x in row))


        for row in processed_data:

            self.tree.insert('', tk.END, values=row)


    def create_db_command(self):

        """1. 建立資料庫"""

        if self.db.create_table():

            messagebox.showinfo("成功", f"資料庫 {DB_NAME} 及其資料表 {TABLE_NAME} 已成功建立或已存在。")

            self.load_data_to_treeview()


    def add_data_command(self):

        """2. 新增資料"""

        course_id = self.entry_course_id.get().strip()

        name = self.entry_name.get().strip()

        credits_str = self.entry_credits.get().strip()

        teacher_id = self.entry_teacher_id.get().strip()

        

        if not course_id or not name or not credits_str:

            messagebox.showwarning("警告", "課程代號、課程名稱和學分數為必填!")

            return

        

        try:

            credits = int(credits_str)

        except ValueError:

            messagebox.showwarning("警告", "學分數必須是數字!")

            return


        if self.db.insert_data(course_id, name, credits, teacher_id):

            messagebox.showinfo("成功", "資料新增成功!")

            self.clear_entries()

            self.load_data_to_treeview()


    def update_data_command(self):

        """3. 更正資料"""

        course_id = self.entry_course_id.get().strip()

        name = self.entry_name.get().strip()

        credits_str = self.entry_credits.get().strip()

        teacher_id = self.entry_teacher_id.get().strip()

        

        if not course_id or not name or not credits_str:

            messagebox.showwarning("警告", "請輸入要更正的課程代號和新的課程名稱!")

            return

            

        try:

            credits = int(credits_str)

        except ValueError:

            messagebox.showwarning("警告", "學分數必須是數字!")

            return


        if self.db.update_data(course_id, name, credits, teacher_id):

            messagebox.showinfo("成功", f"課程代號 {course_id} 資料更正成功!")

            self.clear_entries()

            self.load_data_to_treeview()


    def delete_data_command(self):

        """4. 刪除資料"""

        course_id = self.entry_course_id.get().strip()

        if not course_id:

            messagebox.showwarning("警告", "請輸入要刪除的課程代號!")

            return


        if messagebox.askyesno("確認刪除", f"確定要刪除課程代號 {course_id} 的資料嗎?"):

            if self.db.delete_data(course_id):

                messagebox.showinfo("成功", "資料刪除成功!")

                self.clear_entries()

                self.load_data_to_treeview()


    def delete_db_command(self):

        """6. 刪除資料庫檔案"""

        if messagebox.askyesno("確認刪除資料庫", f"**警告:** 確定要刪除整個資料庫檔案 {DB_NAME} 嗎?所有資料將會遺失!"):

            if self.db.delete_database_file():

                messagebox.showinfo("成功", "資料庫檔案已刪除。")

                self.load_data_to_treeview()


if __name__ == '__main__':

    app = Application()

    app.mainloop()





沒有留言:

張貼留言

ESP32 (ESP-IDF in VS Code) MFRC522 + MQTT + PYTHON TKinter +SQLite

 ESP32 (ESP-IDF in VS Code) MFRC522 + MQTT + PYTHON TKinter +SQLite  ESP32 VS Code 程式 ; PlatformIO Project Configuration File ; ;   Build op...