Mở đầu — vì sao bài này quan trọng
Suốt các bài từ 12 đến 14, bạn đã học lý thuyết chuẩn hóa: 1NF loại bỏ nhóm lặp và giá trị đa trị, 2NF xử lý partial dependency, 3NF xử lý transitive dependency. Nhưng có một khoảng cách rất lớn giữa việc hiểu ba câu định nghĩa đó và việc ngồi trước một cái bảng bầy hầy có thật rồi tự tay tách nó ra thành một mô hình sạch.
Bài này chính là cây cầu bắc qua khoảng cách đó. Chúng ta sẽ không định nghĩa lại 1NF/2NF/3NF nữa — bạn đã có rồi. Thay vào đó, chúng ta lấy một bảng "hổ lốn" điển hình mà bạn sẽ gặp trong đời thực (thường là một file Excel do nghiệp vụ tự làm, hoặc một bảng legacy do lập trình viên thời trước thiết kế ẩu), rồi cùng nhau chuẩn hóa nó từng bước một, ghi rõ tại mỗi bước ta đang sửa vi phạm gì và tại sao.
Đây là kỹ năng cốt lõi của một Data BA. Trong dự án thật, không ai đưa cho bạn một schema đã sạch để bạn ngắm. Người ta đưa cho bạn một mớ dữ liệu lộn xộn và hỏi: "Cái này mô hình lại được không?". Sau bài này, bạn sẽ có một quy trình lặp đi lặp lại được, đủ tự tin để trả lời "Được" và làm ngay tại chỗ.
Khái niệm cốt lõi
Bảng nguồn "hổ lốn" — điểm xuất phát của mọi bài tập chuẩn hóa
Gần như mọi bảng chưa chuẩn hóa đều mang một hoặc nhiều trong bốn "mùi" (smell) sau. Nhận diện được mùi là nửa công việc:
- Nhóm lặp theo cột (repeating groups):
phone1,phone2,phone3— cùng một khái niệm bị nhồi thành nhiều cột. - Giá trị đa trị trong một ô (multi-valued cells): ô địa chỉ chứa cả "123 Lê Lợi, Q1, TP.HCM" gộp chung, hoặc ô sản phẩm chứa "Áo, Quần, Mũ" ngăn cách bằng dấu phẩy.
- Dữ liệu lặp theo hàng (redundancy): tên và số điện thoại khách hàng bị lặp lại y hệt trên mỗi dòng đơn hàng của họ.
- Thông tin mô tả bám nhầm chủ (misplaced description): tên danh mục sản phẩm nằm chung bảng với đơn hàng, dù nó phụ thuộc vào sản phẩm chứ không phụ thuộc vào đơn hàng.
Bảng khởi đầu của chúng ta
Đây là bảng nguồn — một bảng theo dõi đơn hàng của một shop bán lẻ, xuất từ Excel:
customer_id name phones address order_id order_date products category unit_price total C001 Trần Văn An 0901111, 09022 12 Lê Lợi, Q1, TP.HCM O100 2026-03-01 Áo thun, Quần jean Thời trang 200000, 450000 650000 C001 Trần Văn An 0901111, 09022 12 Lê Lợi, Q1, TP.HCM O105 2026-03-10 Mũ lưỡi trai Phụ kiện 150000 150000 C002 Lê Thị Bình 0933333 45 Trần Phú, Hải Châu, ĐN O101 2026-03-02 Áo thun, Giày sneaker Thời trang 200000, 800000 1000000
Bảng này vi phạm gần như mọi thứ. Nhìn vào đây, một người mới sẽ hoảng. Nhưng một Data BA có quy trình sẽ bình tĩnh bóc từng lớp.
Ba lớp cần bóc
Hãy nhớ nguyên tắc: chuẩn hóa là quá trình tiến dần. Ta không nhảy thẳng tới 3NF. Ta đi 1NF → 2NF → 3NF, mỗi bước giải quyết đúng một loại vấn đề, và đầu ra của bước trước là đầu vào của bước sau.
Tình huống thực tế
Tình huống 1 — Shop thời trang "Nắng Sài Gòn" và cái file Excel định mệnh
Chị Hạnh, chủ shop thời trang online "Nắng Sài Gòn" ở Quận 1, quản lý toàn bộ đơn hàng bằng một sheet Excel giống hệt bảng trên. Ban đầu shop nhỏ, sheet chạy tốt. Đến khi shop có 2.000 khách và 12.000 đơn, mọi thứ vỡ trận:
- Chị Hạnh đổi số điện thoại một khách VIP. Vì số đó lặp trên 47 dòng đơn hàng, chị chỉ sửa được 40 dòng, sót 7 dòng. Bộ phận giao hàng gọi nhầm số cũ — update anomaly.
- Một sản phẩm mới nhập về nhưng chưa ai đặt. Không có cách nào ghi nó vào sheet vì sheet tổ chức theo đơn hàng, không có đơn thì không có dòng — insertion anomaly.
- Khách C002 hủy đơn duy nhất của mình. Xóa dòng đó cũng xóa luôn tên và số điện thoại của khách — deletion anomaly.
Tình huống 2 — Đội BA của một chuỗi F&B và cái bẫy "unit_price"
Một chuỗi cà phê ở Đà Nẵng thuê đội BA mô hình lại hệ thống POS. Bảng nguồn có cột category (danh mục sản phẩm) nằm chung bảng đơn hàng. Một bạn BA junior lập luận: "category đã phụ thuộc order_id rồi vì mỗi dòng có một category, nên 3NF là ổn."
Sai. category không phụ thuộc vào order_id — nó phụ thuộc vào sản phẩm. Cùng một sản phẩm "Cà phê sữa đá" luôn thuộc danh mục "Đồ uống", bất kể nó xuất hiện trong đơn nào. Đây chính là transitive dependency: order_id → product → category. Việc để category cạnh order khiến khi công ty đổi tên danh mục "Đồ uống" thành "Thức uống", phải sửa hàng nghìn dòng.
Bài học rút ra: đừng để "mỗi dòng hiện có đúng một giá trị" đánh lừa rằng dữ liệu đã 3NF. Câu hỏi đúng luôn là: thuộc tính này thực sự phụ thuộc vào cái gì? Nếu nó phụ thuộc vào một cột không phải khóa chính, đó là transitive dependency phải tách ra.
Tình huống 3 — Cột unit_price và bài toán 2NF ẩn
Trong bảng của chị Hạnh, sau khi tách sản phẩm ra thành bảng riêng, ta có bảng chi tiết đơn hàng với khóa kép (order_id, product_id). Câu hỏi: unit_price phụ thuộc vào gì?
Có hai câu trả lời, và bạn phải hỏi nghiệp vụ mới biết:
- Nếu giá là giá niêm yết cố định của sản phẩm →
unit_pricephụ thuộc chỉ vàoproduct_id→ đây là partial dependency (phụ thuộc một phần khóa kép), vi phạm 2NF, phải chuyển sang bảngProduct. - Nếu giá là giá tại thời điểm bán (có khuyến mãi, thay đổi theo đơn) →
unit_pricephụ thuộc vào cả(order_id, product_id)→ nó ở đúng chỗ, không vi phạm 2NF.
Hướng dẫn từng bước
Giờ ta chuẩn hóa bảng của chị Hạnh một cách tuần tự.
Bước 0 — Xác định ý nghĩa từng cột và ghi chú functional dependency
Trước khi động tay, viết ra bằng lời:
- Một khách hàng (
customer_id) có tên, có thể có nhiều số điện thoại, một địa chỉ. - Một đơn hàng (
order_id) thuộc về một khách, có ngày đặt. - Một đơn có nhiều dòng sản phẩm, mỗi dòng có số lượng và giá.
- Một sản phẩm thuộc một danh mục.
Bước 1 — Đưa về 1NF (loại bỏ đa trị và nhóm lặp)
Xử lý ba ô đang chứa nhiều giá trị:
- Cột
phones"0901111, 09022" → tách ra bảng riêngCustomerPhone(customer_id, phone). Mỗi số một dòng. - Cột
productsvàunit_pricegộp nhiều sản phẩm/giá trong một ô → mỗi sản phẩm thành một dòng riêng. - Cột
addressgộp "12 Lê Lợi, Q1, TP.HCM" → tùy yêu cầu; nếu nghiệp vụ cần lọc theo tỉnh/quận thì tách thànhstreet,district,city(nguyên tắc atomic value). Nếu chỉ để in nhãn giao hàng, có thể giữ nguyên — lại là một quyết định nghiệp vụ.
Bước 2 — Đưa về 2NF (loại bỏ partial dependency)
Bảng chi tiết đơn hàng giờ có khóa kép (order_id, product_id). Kiểm tra từng cột non-key xem có phụ thuộc toàn bộ khóa không:
order_datephụ thuộc chỉorder_id→ partial dependency → tách sang bảngOrder.customer_id,name,addressphụ thuộc chỉorder_id(qua đơn) → chuyển sangOrder/Customer.product_name,category,unit_price(giả sử là giá niêm yết) phụ thuộc chỉproduct_id→ tách sang bảngProduct.quantityphụ thuộc cả(order_id, product_id)→ ở lại bảng chi tiết. Đúng chỗ.
Bước 3 — Đưa về 3NF (loại bỏ transitive dependency)
Giờ soi từng bảng:
- Trong
Product:category_namephụ thuộc vào sản phẩm, nhưng nếu danh mục còn có thuộc tính riêng (mô tả, thuế suất) thìproduct_id → category_id → category_namelà transitive → tách bảngCategory(category_id, category_name),Productchỉ giữcategory_idlàm khóa ngoại. - Trong
Order: nếu còncustomer_namesót lại thìorder_id → customer_id → customer_namelà transitive → đảm bảoOrderchỉ giữcustomer_id.
Kết quả cuối — mô hình sạch
Customer(customer_id PK, name, street, district, city)
CustomerPhone(customer_id FK, phone, PK(customer_id, phone))
Category(category_id PK, category_name)
Product(product_id PK, product_name, category_id FK, unit_price)
Order(order_id PK, customer_id FK, order_date)
OrderLine(order_id FK, product_id FK, quantity, PK(order_id, product_id))
Từ một bảng bầy hầy, ta có sáu bảng gọn gàng, mỗi bảng nói về đúng một thực thể, không còn dữ liệu lặp, không còn anomaly. total bị bỏ đi vì nó là dữ liệu suy diễn được (SUM(quantity * unit_price)) — lưu nó chỉ tạo thêm nguy cơ sai lệch.
Lỗi thường gặp & mẹo
Lỗi 1 — Nhảy cóc thẳng tới kết quả. Người mới thường nhìn bảng rồi vẽ luôn sáu bảng cuối, bỏ qua các bước trung gian. Nguy hiểm ở chỗ khi làm bảng phức tạp hơn, không có quy trình tuần tự bạn sẽ bỏ sót vi phạm. Hãy luôn đi qua 1NF → 2NF → 3NF một cách kỷ luật, kể cả khi thấy "đường tắt".
Lỗi 2 — Quên hỏi ngữ nghĩa của giá và ngày. Như tình huống 3, unit_price có thể vi phạm 2NF hoặc không, tùy nghiệp vụ. Đừng đoán. Ghi một câu hỏi vào danh sách và hỏi nghiệp vụ.
Lỗi 3 — Chẻ atomic quá đà. Không phải mọi ô đều cần tách. Tách địa chỉ thành street/district/city chỉ hợp lý nếu nghiệp vụ cần truy vấn theo từng phần. Chuẩn hóa phục vụ nghiệp vụ, không phải phục vụ sự "hoàn hảo" hình thức.
Lỗi 4 — Giữ lại cột tính toán. total là cột suy diễn. Giữ nó tạo redundancy và nguy cơ lệch số. Trừ khi có lý do performance rõ ràng (chuyện của denormalization, bài 15), hãy bỏ.
Mẹo — Bảng "chứng minh": với mỗi cột, viết ra một câu "cột X phụ thuộc hàm vào Y". Nếu Y không phải toàn bộ khóa chính của bảng đang xét, bạn vừa tìm ra một vi phạm. Đây là công cụ soi lỗi nhanh nhất.
Mẹo — Dùng dữ liệu mẫu thật để kiểm chứng. Sau khi tách bảng, thử "lắp ráp" lại (JOIN trên giấy) ba dòng gốc xem có tái tạo đúng không. Nếu tái tạo đúng và không mất thông tin, phép tách là lossless.
Bài tập thực hành
Cho bảng nguồn của một phòng khám tư ở Cần Thơ (xuất từ Excel), theo dõi lịch hẹn khám:
appt_id patient_name patient_phones doctor_name doctor_dept services service_prices appt_datetime A01 Nguyễn Thị C 0912xxx, 0988xxx BS. Long Nội tổng quát Khám tổng quát, Xét nghiệm 150000, 300000 2026-06-01 08:30 A02 Trần Văn D 0977xxx BS. Long Nội tổng quát Khám tổng quát 150000 2026-06-01 09:00
Yêu cầu:
- Liệt kê tất cả các "mùi" vi phạm bạn nhận thấy trong bảng này.
- Viết ra các functional dependency (ví dụ:
doctor_name → doctor_dept). - Chuẩn hóa tuần tự tới 3NF, ghi rõ mỗi bước sửa vi phạm gì.
- Vẽ danh sách các bảng cuối cùng kèm khóa chính, khóa ngoại.
- Nêu ít nhất một câu hỏi nghiệp vụ bạn cần hỏi phòng khám trước khi chốt mô hình (gợi ý:
service_priceslà giá niêm yết hay giá tại thời điểm hẹn?).
Patient, PatientPhone, Doctor, Department, Service, Appointment, và bảng nối AppointmentService. Chú ý doctor_dept là transitive dependency qua doctor.Tóm tắt
Chuẩn hóa trong thực tế không phải là học thuộc ba định nghĩa mà là thực hành một quy trình lặp: xác định ý nghĩa cột và functional dependency → 1NF (bỏ đa trị, nhóm lặp) → 2NF (bỏ partial dependency) → 3NF (bỏ transitive dependency) → kiểm chứng bằng dữ liệu mẫu. Ba câu chuyện anomaly của shop "Nắng Sài Gòn" cho thấy đây là vấn đề tiền bạc thật, không phải lý thuyết. Cái bẫy category và unit_price nhắc ta rằng ranh giới đúng/sai của chuẩn hóa nằm ở ngữ nghĩa nghiệp vụ — nên là BA, bạn phải hỏi. Nắm chắc quy trình này, bạn có thể ngồi trước bất kỳ bảng hổ lốn nào và bình tĩnh biến nó thành một mô hình sạch, sẵn sàng cho bước thiết kế vật lý ở các bài sau.