Khái niệm "Khóa Ngoại" (Foreign Key) và "Phép JOIN"

Một bài toán thực tế :

*"Giả sử trong kho báu của mình, các món đồ được chia thành các Danh mục (Category) khác nhau như: Vũ khí, Thực phẩm thần kỳ, Trang phục phòng ngự.

Nếu món đồ nào chúng ta cũng gõ lại chữ 'Thực phẩm thần kỳ' thì rất mất thời gian và dễ gõ sai chính tả (chỗ gõ chữ hoa, chỗ gõ chữ thường). Giải pháp là chúng ta sẽ xây riêng một cái bảng số 2 để quản lý danh mục!"*

Chúng ta sẽ có 2 bảng:

  1. Bảng danh_muc: Chỉ làm một nhiệm vụ là lưu tên các loại nhóm đồ.

  2. Bảng kho_bau: Lưu thông tin vật phẩm, nhưng có thêm một cột đặc biệt tên là danh_muc_id. Cột này sẽ chỉ ghi một con số (1, 2, 3...) để trỏ sang ID của bảng danh mục. Trong lập trình, cột kết nối này được gọi là Foreign Key (Khóa ngoại).

💻 Phần 2: Thực hành viết Code tạo cấu trúc 2 Bảng có quan hệ

Tạo một file mới tên là bai4_quan_he.py. Chúng ta sẽ viết code để xóa cái bảng cũ đi, và xây lại hệ thống 2 bảng mới tinh có kết nối với nhau.

import sqlite3

ket_noi = sqlite3.connect("kho_bau_nang_cap.db")
nguoi_tho = ket_noi.cursor()

# BẬT TÍNH NĂNG KHÓA NGOẠI TRONG SQLITE (Bắt buộc phải có dòng này)
nguoi_tho.execute("PRAGMA foreign_keys = ON;")

# 1. Tạo bảng DANH MỤC trước
nguoi_tho.execute("""
CREATE TABLE IF NOT EXISTS danh_muc (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    ten_danh_muc TEXT NOT NULL
)
""")

# 2. Tạo bảng KHO BÁU có chứa KHÓA NGOẠI liên kết với bảng danh mục
nguoi_tho.execute("""
CREATE TABLE IF NOT EXISTS kho_bau (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    ten_vat_pham TEXT NOT NULL,
    so_luong INTEGER DEFAULT 0,
    gia_tri INTEGER DEFAULT 0,
    danh_muc_id INTEGER,
    -- Đoạn code phép thuật thiết lập Khóa Ngoại:
    FOREIGN KEY (danh_muc_id) REFERENCES danh_muc(id)
)
""")

# 3. Nạp dữ liệu mẫu vào bảng DANH MỤC
# (Dùng lệnh executemany để nạp nhanh một danh sách)
cac_nhom = [('Vũ khí',), ('Thực phẩm thần kỳ',), ('Trang phục',)]
nguoi_tho.executemany("INSERT INTO danh_muc (ten_danh_muc) VALUES (?)", cac_nhom)

# 4. Nạp dữ liệu vào bảng KHO BÁU
# Chú ý: Cột cuối cùng chính là danh_muc_id (Số 1 là Vũ khí, Số 2 là Thực phẩm...)
cac_vat_pham = [
    ('Kiếm Kim Cương', 1, 500, 1), 
    ('Táo Vàng Ròng', 15, 50, 2),  
    ('Giáp Bạc', 3, 200, 3),       
    ('Cung Tên Lửa', 2, 350, 1)    
]
nguoi_tho.executemany("""
INSERT INTO kho_bau (ten_vat_pham, so_luong, gia_tri, danh_muc_id) 
VALUES (?, ?, ?, ?)
""", cac_vat_pham)

ket_noi.commit()
print("Đã khởi tạo hệ thống cơ sở dữ liệu quan hệ thành công!")

🔗 Phần 3: Phép thuật INNER JOIN — Gộp bảng để đọc dữ liệu

Bây giờ, nếu dùng lệnh SELECT * FROM kho_bau, máy tính sẽ in ra cột danh_muc_id là các con số số 1, 2, 3 trông rất vô cảm và khó hiểu.

Làm sao để bắt máy tính tự dò theo con số đó, sang bảng danh_muc lấy cái tên chữ (Vũ khí, Thực phẩm...) về hiển thị ra? Chúng ta dùng phép thần thông INNER JOIN ... ON.

Hãy gõ tiếp đoạn code đọc dữ liệu tối cao này vào cuối file nhé:

print("\n--- BÁO CÁO KHO BÁU HOÀN CHỈNH (ĐÃ GỘP BẢNG) ---")

# Câu lệnh SQL gộp 2 bảng dựa trên điều kiện: danh_muc_id của bảng này phải bằng id của bảng kia
cau_lenh_join = """
SELECT kho_bau.id, kho_bau.ten_vat_pham, kho_bau.so_luong, danh_muc.ten_danh_muc
FROM kho_bau
INNER JOIN danh_muc ON kho_bau.danh_muc_id = danh_muc.id
"""

nguoi_tho.execute(cau_lenh_join)
ket_qua = nguoi_tho.fetchall()

for dong in ket_qua:
    print(f"Mã: {dong[0]} | Tên: {dong[1]} | SL: {dong[2]} | Thuộc nhóm: {dong[3]}")

ket_noi.close()

🔍 Nghiệm thu và Thử thách cho hai con:

Khi bấm Run, màn hình Terminal sẽ in ra kết quả cực kỳ đẹp mắt:

  • Mã: 1 | Tên: Kiếm Kim Cương | SL: 1 | Thuộc nhóm: Vũ khí

  • Mã: 2 | Tên: Táo Vàng Ròng | SL: 15 | Thuộc nhóm: Thực phẩm thần kỳ

Thử thách tư duy logic: Hãy mở phần mềm DB Browser for SQLite, mở file kho_bau_nang_cap.db mới này ra.

  1. Vào bảng kho_bau, thử thêm một dòng thủ công nhưng ở cột danh_muc_id con cố tình nhập số 99 (một ID không hề tồn tại bên bảng danh mục).

  2. Bấm nút Write Changes để lưu lại xem phần mềm có cho phép không?

💡 Giải thích: Hệ thống sẽ báo lỗi ngay lập tức! Đó chính là sức mạnh của Khóa Ngoại. Nó giống như một người bảo vệ nghiêm khắc, ngăn chặn không cho ai nạp dữ liệu "rác" hay dữ liệu sai vặt vào hệ thống.