Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 42 — Hands-on: Normalization Exercise

Data Modeling and ERD Mastery Bài 42/60

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_idnamephonesaddressorder_idorder_dateproductscategoryunit_pricetotal
C001Trần Văn An0901111, 0902212 Lê Lợi, Q1, TP.HCMO1002026-03-01Áo thun, Quần jeanThời trang200000, 450000650000
C001Trần Văn An0901111, 0902212 Lê Lợi, Q1, TP.HCMO1052026-03-10Mũ lưỡi traiPhụ kiện150000150000
C002Lê Thị Bình093333345 Trần Phú, Hải Châu, ĐNO1012026-03-02Áo thun, Giày sneakerThời trang200000, 8000001000000

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.
Bài học rút ra: ba loại anomaly này không phải lý thuyết suông trong sách. Chúng là lý do thực tế khiến chị Hạnh mất khách và giao hàng sai. Khi bạn trình bày với nghiệp vụ tại sao phải chuẩn hóa, hãy kể đúng ba câu chuyện anomaly này thay vì nói "vì nó vi phạm 3NF".

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_price phụ thuộc chỉ vào product_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ảng Product.
  • Nếu giá là giá tại thời điểm bán (có khuyến mãi, thay đổi theo đơn) → unit_price phụ thuộc vào cả (order_id, product_id) → nó ở đúng chỗ, không vi phạm 2NF.
Bài học rút ra: chuẩn hóa không phải trò chơi máy móc chỉ nhìn cấu trúc. Ranh giới giữa "vi phạm 2NF" và "hợp lệ" nằm ở ngữ nghĩa nghiệp vụ. Là BA, bạn có lợi thế hơn DBA thuần túy ở chính điểm này: bạn được phép và cần phải hỏi nghiệp vụ.

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.
Chỉ riêng việc viết ra bốn câu này, bạn đã thấy lờ mờ bốn thực thể: Customer, Order, Product, và bảng nối OrderLine.

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êng CustomerPhone(customer_id, phone). Mỗi số một dòng.
  • Cột productsunit_price gộ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 address gộ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ành street, 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ụ.
Sau bước 1, bảng đơn hàng "phẳng" ra: mỗi dòng đúng một sản phẩm của một đơn, không còn ô nào chứa nhiều giá trị.

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_date phụ thuộc chỉ order_id → partial dependency → tách sang bảng Order.
  • customer_id, name, address phụ thuộc chỉ order_id (qua đơn) → chuyển sang Order/Customer.
  • product_name, category, unit_price (giả sử là giá niêm yết) phụ thuộc chỉ product_id → tách sang bảng Product.
  • quantity phụ 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_name phụ 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_name là transitive → tách bảng Category(category_id, category_name), Product chỉ giữ category_id làm khóa ngoại.
  • Trong Order: nếu còn customer_name sót lại thì order_id → customer_id → customer_name là transitive → đảm bảo Order chỉ 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_idpatient_namepatient_phonesdoctor_namedoctor_deptservicesservice_pricesappt_datetime
A01Nguyễn Thị C0912xxx, 0988xxxBS. LongNội tổng quátKhám tổng quát, Xét nghiệm150000, 3000002026-06-01 08:30
A02Trần Văn D0977xxxBS. LongNội tổng quátKhám tổng quát1500002026-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_prices là giá niêm yết hay giá tại thời điểm hẹn?).
Gợi ý đáp án phần khung: bạn sẽ tách ra 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 categoryunit_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.