DE & DA

[DE & DA Blog #59] Thiết Kế Cơ Sở Dữ Liệu Quan Hệ Chuẩn Hóa Từ Số 0 (Database Normalization): Bản Chất 1NF, 2NF, 3NF & Biến Bảng Excel Rối Rắm Thành Schema Chuẩn Chỉnh

0 views
[DE & DA Blog #59] Thiết Kế Cơ Sở Dữ Liệu Quan Hệ Chuẩn Hóa Từ Số 0 (Database Normalization): Bản Chất 1NF, 2NF, 3NF & Biến Bảng Excel Rối Rắm Thành Schema Chuẩn Chỉnh

1. Bối cảnh thực tế (Context & Problem Statement)

Khi một cửa hàng nhỏ mới khởi nghiệp, người ta thường ghi nhận toàn bộ đơn hàng vào duy nhất một trang tính Excel phẳng (Flat Table):

  • Một dòng ghi: Mã đơn hàng, Ngày mua, Tên khách hàng, Số điện thoại, Địa chỉ, Danh sách món hàng, Tên người bán...

Cách làm này ban đầu có vẻ tiện lợi. Nhưng khi hệ thống phát triển lên hàng chục nghìn đơn hàng, doanh nghiệp sẽ rơi vào "3 cơn ác mộng dị thường dữ liệu" (Data Anomalies):

  1. Dị thường Cập nhật (Update Anomaly): Khách hàng đổi số điện thoại mới. Do khách đã mua 50 đơn hàng trước đó, bạn buộc phải tìm và cập nhật đúng 50 dòng dữ liệu. Nếu sót dù chỉ 1 dòng, dữ liệu sẽ bị "bất nhất" (Dòng ghi số cũ, dòng ghi số mới).
  2. Dị thường Thêm mới (Insertion Anomaly): Cửa hàng nhập về một món đồ mới (ví dụ: Tai nghe Bluetooth). Bạn không thể lưu sản phẩm này vào database vì chưa có khách hàng nào mua, mà bảng lại yêu cầu bắt buộc phải có Mã đơn hàng!
  3. Dị thường Xóa (Deletion Anomaly): Một khách hàng hủy đơn hàng duy nhất của họ. Khi bạn xóa dòng đơn hàng đó, bạn vô tình xóa sạch luôn toàn bộ thông tin số điện thoại và địa chỉ của khách hàng đó khỏi hệ thống!

Chuẩn hóa cơ sở dữ liệu (Database Normalization) ra đời nhằm mục đích: Tổ chức lại các bảng dữ liệu để triệt tiêu sự trùng lặp (Redundancy), ngăn chặn các dị thường dữ liệu và đảm bảo tính toàn vẹn tuyệt đối cho hệ thống vận hành (OLTP).


2. Các Khái Niệm & Cơ Chế Cốt Lõi

2.1. Khung Chuẩn Hóa Căn Bản (1NF →\rightarrow 2NF →\rightarrow 3NF)

[Bảng thô chưa chuẩn hóa (UNF)]
               |
               v (Tách các giá trị đa trị, đảm bảo mỗi ô là nguyên tử)
[Chuẩn 1 (1NF - First Normal Form)]
               |
               v (Triệt tiêu phụ thuộc hàm bộ phận - Partial Dependencies)
[Chuẩn 2 (2NF - Second Normal Form)]
               |
               v (Triệt tiêu phụ thuộc bắc cầu - Transitive Dependencies)
[Chuẩn 3 (3NF - Third Normal Form)] ===> ĐẠT CHUẨN THIẾT KẾ CƠ SỞ DỮ LIỆU VẬN HÀNH!

2.2. Chuẩn 1 (1NF - First Normal Form): Tính Nguyên Tử (Atomicity)

  • Quy tắc vàng: Mỗi ô dữ liệu (giao giữa hàng và cột) chỉ được chứa DUY NHẤT 1 giá trị nguyên tử (Atomic Value), không được chứa danh sách, mảng hay tập hợp lặp lại.
  • Vi phạm 1NF thường gặp:
    • Cột Mon_Hang: Ghi "Bánh mì, Sữa tươi, Trứng gà" trong cùng 1 ô.
    • Cột So_Dien_Thoai: Ghi "0903123456 / 0912987654".
  • Cách đưa về 1NF: Tách các món hàng thành các dòng riêng biệt, hoặc tách các số điện thoại thành cột riêng/bảng riêng; xác định một Khóa chính (Primary Key) duy nhất cho mỗi dòng.

2.3. Chuẩn 2 (2NF - Second Normal Form): Triệt Tiêu Phụ Thuộc Bộ Phận

  • Điều kiện tiên quyết: Bảng đã đạt chuẩn 1NF.
  • Quy tắc vàng: Mọi cột không phải là khóa (Non-key columns) bắt buộc phải phụ thuộc vào TOÀN BỘ Khóa chính, không được chỉ phụ thuộc vào một phần của khóa chính (No Partial Dependency).
    • Lưu ý quan trọng: Chuẩn 2NF chỉ áp dụng cho các bảng có Khóa chính kết hợp (Composite Primary Key - gồm 2 cột trở lên). Nếu bảng chỉ có khóa chính là 1 cột duy nhất (ví dụ id), bảng đó tự động đạt 2NF!
  • Vi phạm 2NF:
    • Bảng Chi_Tiet_Don_Hang có khóa chính kết hợp gồm (Ma_Don_Hang, Ma_San_Pham).
    • Các cột: Ten_San_Pham, Don_Gia_Goc.
    • Rõ ràng: Ten_San_Pham chỉ cần Ma_San_Pham là xác định được, hoàn toàn không cần biết Ma_Don_Hang là gì! →\rightarrow Cột này bị "phụ thuộc bộ phận" vào một nửa khóa chính.
  • Cách đưa về 2NF: Cắt các cột phụ thuộc bộ phận ra thành một bảng riêng (San_Pham).

2.4. Chuẩn 3 (3NF - Third Normal Form): Triệt Tiêu Phụ Thuộc Bắc Cầu

  • Điều kiện tiên quyết: Bảng đã đạt chuẩn 2NF.
  • Quy tắc vàng: Cột không khóa không được phụ thuộc vào một cột không khóa khác (No Transitive Dependency).
    • Nói cách khác: A→BA \to B và B→CB \to C (trong đó AA là Khóa chính, BB và CC là các cột thường). Cột CC đang phụ thuộc bắc cầu vào AA thông qua BB.
  • Vi phạm 3NF:
    • Bảng Don_Hang có Khóa chính là Ma_Don_Hang.
    • Các cột thường gồm: Ma_Khach_Hang, Ten_Khach_Hang, So_Dien_Thoai.
    • Ta thấy: Ma_Don_Hang →\to xác định Ma_Khach_Hang. Sau đó Ma_Khach_Hang →\to lại xác định Ten_Khach_Hang và So_Dien_Thoai.
    • →\rightarrow Ten_Khach_Hang đang phụ thuộc bắc cầu vào Ma_Don_Hang qua Ma_Khach_Hang!
  • Cách đưa về 3NF: Cắt toàn bộ thông tin khách hàng ra thành một bảng riêng (Khach_Hang), bảng Don_Hang chỉ giữ lại duy nhất Ma_Khach_Hang làm Khóa ngoại (Foreign Key).

2.5. Khi Nào Nên Khử Chuẩn Hóa (Denormalization) Trong Thực Tế?

Chuẩn hóa đến 3NF là tuyệt vời cho hệ thống giao dịch (OLTP). Tuy nhiên:

  • Để lấy ra thông tin một đơn hàng hoàn chỉnh trên hệ thống 3NF, bạn phải viết câu lệnh JOIN qua 4−54 - 5 bảng khác nhau (Orders ⋈\bowtie Customers ⋈\bowtie Order_Items ⋈\bowtie Products).
  • Phép JOIN nhiều bảng làm giảm tốc độ truy vấn báo cáo phân tích trên hàng triệu dòng dữ liệu.
  • →\rightarrow Trong Data Warehouse / Data Analytics (OLAP): Kỹ sư dữ liệu chủ động Khử chuẩn hóa (Denormalization), chấp nhận lưu trùng lặp dữ liệu để đưa về mô hình hình sao (Star Schema) với 1 bảng Fact nối trực tiếp các bảng Dimension, giúp câu lệnh SQL chạy siêu nhanh mà không cần JOIN nhiều tầng!

3. Các Bảng Markdown So Sánh Chi Tiết

📊 Bảng 1: So Sánh Toàn Diện Tiến Trình Chuẩn Hóa (1NF →\rightarrow 2NF →\rightarrow 3NF)

Cấp độ chuẩn hóaĐiều kiện bắt buộcDị thường loại bỏ đượcHành động kỹ thuật chính
1NF (First Normal Form)Mỗi ô chứa 1 giá trị nguyên tử duy nhất; Có khóa chínhLoại bỏ mảng danh sách lặp lại trong 1 ôTách các giá trị gộp thành nhiều dòng riêng biệt
2NF (Second Normal Form)Đạt 1NF + Triệt tiêu phụ thuộc hàm bộ phậnLoại bỏ việc trùng lặp thông tin sản phẩm trên từng đơn hàngTách các thuộc tính của sản phẩm ra bảng Products riêng
3NF (Third Normal Form)Đạt 2NF + Triệt tiêu phụ thuộc bắc cầu (A→B→CA \to B \to C)Loại bỏ việc trùng lặp thông tin khách hàng trên nhiều đơn hàngTách thông tin khách hàng ra bảng Customers riêng

📊 Bảng 2: So Sánh Chuẩn Hóa (3NF - OLTP) vs. Khử Chuẩn Hóa (Denormalization - OLAP)

Tiêu chíChuẩn Hóa (3NF - Normalized)Khử Chuẩn Hóa (Denormalized - Star Schema)
Mục đích sử dụng chínhVận hành giao dịch ứng dụng hàng ngày (OLTP)Phân tích số liệu, Báo cáo BI, Dashboard (OLAP)
Mức độ trùng lặp dữ liệuBằng 0 (Hoàn toàn không có dữ liệu dư thừa)Cao (Lưu lặp lại tên khách hàng, tên danh mục)
Hiệu năng thao tác Ghi (INSERT/UPDATE)Cực nhanh (Chỉ cần sửa đúng 1 dòng ở 1 bảng)Chậm hơn (Phải cập nhật ở nhiều nơi)
Hiệu năng thao tác Đọc (SELECT)Chậm hơn khi cần JOIN nhiều bảng phức tạpSiêu tốc (Ít phép JOIN, đọc trực tiếp dữ liệu phẳng)
Cơ sở dữ liệu áp dụngPostgreSQL, MySQL, SQL Server (Production App)Snowflake, BigQuery, ClickHouse, Data Marts

📊 Bảng 3: So Sánh Khóa Tự Nhiên (Natural Key) vs. Khóa Đại Diện (Surrogate Key)

Tiêu chíKhóa Tự Nhiên (Natural Key)Khóa Đại Diện Tự Sinh (Surrogate Key)
Nguồn gốcDữ liệu nghiệp vụ có thật (Số CCCD, Email, Mã số thuế)Số nguyên tự tăng (id SERIAL / BIGINT) hoặc UUID do hệ thống tự sinh
Tính bất biếnKém (Người dùng có thể đổi email, làm lại CCCD)Bất biến 100%100\% (Không bao giờ thay đổi trong suốt vòng đời)
Hiệu năng JOINChậm nếu khóa là chuỗi văn bản dài (VARCHAR)Rất nhanh (Số nguyên INT/BIGINT tối ưu hóa CPU)
Khuyến nghị thiết kếDùng làm Unique Constraint nghiệp vụLuôn dùng làm Khóa chính (Primary Key) thực tế

4. Thực Hành Thực Chiến: Từng Bước Chuẩn Hóa Bảng Đơn Hàng (Walkthrough)

📌 Dữ liệu ban đầu chưa chuẩn hóa (Unnormalized Form - UNF):

Giả sử bạn nhận được một bảng Excel bán hàng như sau:

Ma_DonNgay_MuaKhach_HangDia_ChiDanh_Sach_Mon_HangTong_Tien
DH012026-09-12Nguyễn Văn An (0903123456)Q1, HCMBánh mì (20k20k), Sữa tươi (15k15k)35,00035,000
DH022026-09-12Trần Thị Bình (0912987654)Cầu Giấy, HNBánh mì (20k20k)20,00020,000

🛠️ Bước 1: Đưa về Chuẩn 1NF (Làm cho dữ liệu nguyên tử)

Tách ô Danh_Sach_Mon_Hang thành từng dòng riêng biệt, tách Họ tên và SĐT:

Ma_Don (PK)Ma_SP (PK)Ngay_MuaTen_KhachSDT_KhachDia_ChiTen_SPDon_GiaSo_Luong
DH01SP012026-09-12Nguyễn An0903123456Q1, HCMBánh mì20,00020,0001
DH01SP022026-09-12Nguyễn An0903123456Q1, HCMSữa tươi15,00015,0001
DH02SP012026-09-12Trần Bình0912987654Cầu Giấy, HNBánh mì20,00020,0001
  • Khóa chính kết hợp: (Ma_Don, Ma_SP).

🛠️ Bước 2: Đưa về Chuẩn 2NF (Tách thuộc tính sản phẩm)

Nhận thấy Ten_SP và Don_Gia chỉ phụ thuộc vào Ma_SP, không phụ thuộc vào Ma_Don. Ta tách ra bảng San_Pham:

  1. Bảng San_Pham: Ma_SP (PK), Ten_SP, Don_Gia.
  2. Bảng Don_Hang_Tam: Ma_Don, Ma_SP, Ngay_Mua, Ten_Khach, SDT_Khach, Dia_Chi, So_Luong.

🛠️ Bước 3: Đưa về Chuẩn 3NF (Tách thuộc tính khách hàng)

Trong bảng Don_Hang_Tam, Ten_Khach, SDT_Khach, Dia_Chi phụ thuộc vào Ma_Khach chứ không phụ thuộc trực tiếp vào Ma_Don. Ta tách thành 4 bảng chuẩn chỉnh hoàn hảo:

[Bảng KHACH_HANG] (1) <------- (N) [Bảng DON_HANG] (1)
                                         |
                                         | (1)
                                         v (N)
[Bảng SAN_PHAM]   (1) <------- (N) [Bảng CHI_TIET_DON_HANG]

💻 Mã Lệnh SQL DDL Tạo CSDL Chuẩn 3NF Hoàn Chỉnh:

-- 1. Bảng Khách hàng (Lưu thông tin định danh duy nhất của khách):
CREATE TABLE customers (
    customer_id SERIAL PRIMARY KEY,
    full_name VARCHAR(100) NOT NULL,
    phone_number VARCHAR(20) UNIQUE NOT NULL,
    address TEXT
);

-- 2. Bảng Sản phẩm (Lưu thông tin và giá niêm yết của mặt hàng):
CREATE TABLE products (
    product_id SERIAL PRIMARY KEY,
    product_code VARCHAR(50) UNIQUE NOT NULL,
    product_name VARCHAR(150) NOT NULL,
    unit_price DECIMAL(12, 2) NOT NULL CHECK (unit_price >= 0)
);

-- 3. Bảng Đơn hàng (Lưu thông tin giao dịch chung):
CREATE TABLE orders (
    order_id SERIAL PRIMARY KEY,
    order_code VARCHAR(50) UNIQUE NOT NULL,
    order_date DATE NOT NULL DEFAULT CURRENT_DATE,
    customer_id INT NOT NULL,
    -- Ràng buộc khóa ngoại trỏ tới bảng customers:
    CONSTRAINT fk_orders_customer FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);

-- 4. Bảng Chi tiết đơn hàng (Bảng cầu nối N-N giữa Đơn hàng và Sản phẩm):
CREATE TABLE order_items (
    order_id INT NOT NULL,
    product_id INT NOT NULL,
    quantity INT NOT NULL CHECK (quantity > 0),
    actual_price DECIMAL(12, 2) NOT NULL, -- Giá bán thực tế tại thời điểm mua
    PRIMARY KEY (order_id, product_id),
    CONSTRAINT fk_items_order FOREIGN KEY (order_id) REFERENCES orders(order_id) ON DELETE CASCADE,
    CONSTRAINT fk_items_product FOREIGN KEY (product_id) REFERENCES products(product_id)
);

5. Góc Ôn Luyện Phỏng Vấn (Interview Corner)

❓ Câu hỏi 1: Thế nào là Dị thường Cập nhật (Update Anomaly), Dị thường Thêm mới (Insertion Anomaly) và Dị thường Xóa (Deletion Anomaly)? Cho một ví dụ cụ thể.

  • Gợi ý trả lời:
    • Insertion Anomaly (Dị thường thêm mới): Xảy ra khi không thể chèn một dữ liệu mới vào bảng nếu thiếu một dữ liệu khác không liên quan.
      • Ví dụ: Nếu bảng lưu chung Sinh viên và Khóa học, ta không thể tạo một Khóa học mới mở vào hệ thống nếu chưa có sinh viên nào đăng ký học khóa đó.
    • Deletion Anomaly (Dị thường xóa): Xảy ra khi việc xóa một thông tin lại vô tình xóa mất một thông tin quan trọng khác không hề muốn xóa.
      • Ví dụ: Sinh viên duy nhất đăng ký môn "Xác suất thống kê" quyết định hủy môn. Khi xóa dòng sinh viên đó, toàn bộ thông tin về môn học (tên môn, mã môn, số tín chỉ) bị biến mất vĩnh viễn khỏi trường đại học.
    • Update Anomaly (Dị thường cập nhật): Xảy ra khi một thông tin bị lặp lại ở nhiều dòng. Khi cần chỉnh sửa, ta phải cập nhật ở hàng loạt nơi. Nếu bỏ sót một dòng, cơ sở dữ liệu sẽ rơi vào trạng thái mâu thuẫn bất nhất.

❓ Câu hỏi 2: Phân biệt sự khác nhau bản chất giữa Chuẩn 2 (2NF) và Chuẩn 3 (3NF). Khái niệm Phụ thuộc bộ phận (Partial Dependency) và Phụ thuộc bắc cầu (Transitive Dependency) là gì?

  • Gợi ý trả lời:
    • Chuẩn 2 (2NF) - Xử lý Phụ thuộc bộ phận:
      • Chỉ xảy ra khi bảng có Khóa chính kết hợp (gồm 2 cột trở lên).
      • Phụ thuộc bộ phận là khi một cột thường chỉ phụ thuộc vào một nửa/một phần của khóa chính mà không phụ thuộc vào toàn bộ khóa.
      • Để đạt 2NF: Mọi cột không khóa bắt buộc phải phụ thuộc vào trọn vẹn toàn bộ khóa chính kết hợp.
    • Chuẩn 3 (3NF) - Xử lý Phụ thuộc bắc cầu:
      • Xảy ra khi một cột thường phụ thuộc vào một cột thường khác (không liên quan đến việc khóa chính có mấy cột).
      • Phụ thuộc bắc cầu có dạng: Khóa chính A→A \to xác định Cột thường B→B \to xác định Cột thường CC.
      • Để đạt 3NF: Bảng phải đạt 2NF và loại bỏ hoàn toàn các mối quan hệ bắc cầu này bằng cách cắt BB và CC ra một bảng danh mục riêng biệt.

❓ Câu hỏi 3: Tại sao các hệ thống Backend vận hành (OLTP) luôn ưu tiên chuẩn hóa cao đến 3NF, trong khi các hệ thống Phân tích Báo cáo (Data Warehouse / OLAP) lại chủ động Khử chuẩn hóa (Denormalization)?

  • Gợi ý trả lời:
    • Hệ thống OLTP ưu tiên 3NF vì:
      • Mục tiêu số 1 là Độ toàn vẹn dữ liệu tuyệt đối và Tốc độ Ghi (INSERT / UPDATE).
      • Khi dữ liệu ở chuẩn 3NF, không có sự dư thừa dữ liệu. Một thay đổi (như đổi số điện thoại khách hàng) chỉ cần cập nhật đúng 1 dòng duy nhất trong 1 bảng →\rightarrow Tốc độ ghi cực nhanh, không bao giờ bị nghẽn khóa và không lo sai lệch số liệu.
    • Hệ thống OLAP / Data Warehouse chủ động Khử chuẩn hóa vì:
      • Mục tiêu số 1 là Tốc độ Đọc phân tích (SELECT / GROUP BY) trên hàng triệu đến hàng tỷ bản ghi.
      • Nếu giữ nguyên 3NF, các câu lệnh báo cáo BI phải thực hiện phép JOIN qua 5–10 bảng lớn, gây tốn kém CPU và nghẽn băng thông trao đổi dữ liệu.
      • Bằng cách khử chuẩn hóa (gộp thông tin khách hàng, sản phẩm trực tiếp vào bảng Fact hoặc bảng Dimension phẳng), hệ thống giảm thiểu tối đa phép JOIN, giúp các công cụ phân tích (Snowflake, BigQuery, Tableau) quét dữ liệu theo cột siêu tốc.

6. Tóm Tắt Ghi Nhớ Nhanh (Key Takeaways)

  1. Chuẩn 1NF: Mỗi ô là một giá trị nguyên tử duy nhất; có khóa chính phân biệt từng dòng.
  2. Chuẩn 2NF: Loại bỏ phụ thuộc bộ phận (áp dụng cho khóa chính kết hợp).
  3. Chuẩn 3NF: Loại bỏ phụ thuộc bắc cầu (A→B→CA \to B \to C).
  4. Thiết kế chuẩn 3NF là tiêu chuẩn vàng để bảo vệ cơ sở dữ liệu OLTP khỏi Dị thường dữ liệu (Anomalies).
  5. Luôn sử dụng Surrogate Key (Khóa tự sinh SERIAL/BIGINT) làm khóa chính thay vì dùng khóa tự nhiên dễ thay đổi.
  6. Khử chuẩn hóa (Denormalization) là lựa chọn có chủ đích trong Data Warehouse (OLAP) để tăng tốc độ truy vấn đọc báo cáo.
0Claps