Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn vừa nhận một file Excel quản lý đơn hàng từ phòng kinh doanh của một chuỗi mỹ phẩm. Trong một ô (cell) duy nhất ở cột "Sản phẩm", bạn thấy: "Son 3CE, Kem chống nắng Anessa, Mặt nạ Mediheal". Một ô khác lại chỉ có "Nước hoa hồng". Còn cột "Số điện thoại khách" thì có ô ghi "0901234567 / 0912345678" vì khách có hai số.
Trông thì vô hại, thậm chí còn "tiện" cho người nhập liệu. Nhưng khi bạn — với vai trò Business Analyst (BA) — phải trả lời những câu hỏi tưởng chừng đơn giản như "Sản phẩm nào bán chạy nhất tháng này?" hoặc "Khách hàng nào mua kem chống nắng Anessa?", bạn sẽ vỡ mộng. Không có cách nào viết một câu truy vấn sạch sẽ, không có cách nào tính tổng chính xác, và mỗi lần báo cáo bạn lại phải tách chuỗi thủ công.
Đây chính là lý do First Normal Form (1NF) ra đời. Nó là nấc thang đầu tiên trong chuỗi chuẩn hóa (normalization) và cũng là nền tảng bắt buộc trước khi bạn nghĩ tới 2NF hay 3NF (sẽ học ở Bài 13 và Bài 14). Nếu một bảng còn chưa đạt 1NF thì mọi nỗ lực chuẩn hóa phía sau đều vô nghĩa.
Trong bài này, chúng ta tập trung duy nhất vào 1NF: hiểu thế nào là atomic value (giá trị nguyên tử), thế nào là repeating group (nhóm lặp), nhận diện vi phạm, và biến một bảng "bẩn" thành bảng đạt chuẩn 1NF. Đây là kỹ năng nền mà bất kỳ BA làm việc với dữ liệu nào cũng phải thành thạo.
Khái niệm cốt lõi
Định nghĩa 1NF
Một bảng (table/relation) đạt First Normal Form (1NF) khi thỏa mãn đồng thời ba điều kiện:
- Mỗi cell chỉ chứa một giá trị nguyên tử (atomic value) — tức là giá trị không thể (và không nên) chia nhỏ hơn về mặt nghiệp vụ. Một ô không được chứa danh sách như "Son, Kem, Mặt nạ".
- Không có nhóm lặp (repeating group) — tức là không có chuyện tạo ra các cột kiểu
SanPham1,SanPham2,SanPham3để nhồi nhiều giá trị cùng loại vào một dòng. - Mỗi dòng phải được phân biệt duy nhất (thường là nhờ một primary key) và thứ tự dòng không mang ý nghĩa.
Atomic value — "nguyên tử" nghĩa là gì?
"Atomic" mượn từ vật lý: nguyên tử là đơn vị nhỏ nhất không chia nhỏ thêm được nữa (trong bối cảnh nghiệp vụ). Nhưng "atomic" không phải khái niệm tuyệt đối — nó phụ thuộc vào nhu cầu nghiệp vụ của bạn.
Ví dụ cột HoTen chứa "Nguyễn Thị Lan Anh":
- Nếu hệ thống của bạn không bao giờ cần tách họ và tên riêng, thì coi "Nguyễn Thị Lan Anh" là một giá trị atomic là chấp nhận được.
- Nhưng nếu bạn cần lọc theo họ, sắp xếp theo tên, hay gọi khách bằng tên riêng trong email marketing ("Chào Lan Anh"), thì "Nguyễn Thị Lan Anh" không còn atomic với nhu cầu đó, và bạn nên tách thành các cột riêng (chủ đề này sẽ được đào sâu ở Bài 26 về Vietnamese Names).
- Trong cell có dấu phân tách: dấu phẩy, gạch chéo, xuống dòng, chấm phẩy ("son, kem, mặt nạ").
- Bạn thấy mình phải dùng hàm tách chuỗi (
SPLIT,LIKE '%...%') để lấy thông tin ra. - Cùng một loại thông tin lại được "gộp" vào một ô.
Repeating group — nhóm lặp
Repeating group là khi cùng một loại thuộc tính bị lặp lại thành nhiều cột trong một dòng. Đây là "anh em sinh đôi" của vi phạm atomic, chỉ khác là người ta cố nhồi nhiều giá trị theo chiều ngang (cột) thay vì gộp vào một ô.
Ví dụ bảng DonHang có các cột: MaDon, SanPham1, SanPham2, SanPham3. Đây là repeating group kinh điển. Hậu quả:
- Nếu khách mua 4 sản phẩm thì sao? Bạn lại phải thêm cột
SanPham4, sửa cả cấu trúc bảng — một cơn ác mộng bảo trì. - Hầu hết các dòng sẽ có
SanPham2,SanPham3để trống (NULL), gây lãng phí và khó truy vấn. - Câu hỏi "sản phẩm X xuất hiện trong bao nhiêu đơn?" phải quét qua cả ba cột.
Vì sao 1NF quan trọng đến vậy
Một bảng đạt 1NF mở khóa cho bạn:
- Truy vấn sạch: đếm, lọc, nhóm (GROUP BY) dữ liệu dễ dàng.
- Toàn vẹn dữ liệu: ràng buộc (constraint) áp được lên từng giá trị riêng lẻ.
- Khả năng mở rộng: thêm sản phẩm thứ N không cần đổi cấu trúc bảng.
- Nền tảng cho 2NF, 3NF: bạn không thể leo lên nếu chưa đứng vững ở bậc một.
Tình huống thực tế
Ví dụ 1 — Tiệm mỹ phẩm bán hàng đa kênh (vi phạm atomic value)
Bối cảnh: Một cửa hàng mỹ phẩm online ở TP.HCM dùng Google Sheets để theo dõi đơn hàng. Bảng DonHang của họ trông như sau:
| MaDon | KhachHang | SanPham | SoDienThoai |
|---|---|---|---|
| 1001 | Trần Mai | Son 3CE, Kem Anessa, Mặt nạ Mediheal | 0901234567 / 0912345678 |
| 1002 | Lê Hùng | Nước hoa hồng | 0987654321 |
SanPham nhồi nhiều sản phẩm vào một ô — vi phạm atomic. Cột SoDienThoai của khách Trần Mai chứa hai số ngăn cách bằng dấu "/". Khi quản lý hỏi "Tháng này bán được bao nhiêu hộp kem Anessa?", nhân viên phải mở từng dòng, đọc bằng mắt và đếm tay. Mỗi lần khuyến mãi, con số báo cáo lại sai lệch vì không ai tách chuỗi chính xác.Cách sửa: Tách mỗi sản phẩm thành một dòng riêng. Tách số điện thoại thành các bản ghi riêng (hoặc một bảng SoDienThoai liên kết với khách). Sau khi tách, bảng chi tiết đơn hàng trở thành:
| MaDon | KhachHang | SanPham |
|---|---|---|
| 1001 | Trần Mai | Son 3CE |
| 1001 | Trần Mai | Kem Anessa |
| 1001 | Trần Mai | Mặt nạ Mediheal |
| 1002 | Lê Hùng | Nước hoa hồng |
COUNT đơn giản.Ví dụ 2 — Phần mềm tuyển sinh trung tâm tiếng Anh (vi phạm repeating group)
Bối cảnh: Một trung tâm Anh ngữ ở Đà Nẵng thiết kế bảng HocVien để lưu các khóa học mà mỗi học viên đăng ký:
| MaHV | TenHV | KhoaHoc1 | KhoaHoc2 | KhoaHoc3 |
|---|---|---|---|---|
| HV01 | Phạm Anh | IELTS 6.5 | Giao tiếp | NULL |
| HV02 | Vũ Linh | TOEIC 750 | NULL | NULL |
| HV03 | Đỗ Nam | IELTS 6.5 | Giao tiếp | Phát âm |
KhoaHoc1, KhoaHoc2, KhoaHoc3 là repeating group điển hình. Vấn đề bộc lộ ngay khi:
- Một học viên muốn đăng ký khóa thứ 4 — không có chỗ chứa, phải sửa cấu trúc bảng.
- Phòng đào tạo hỏi "Bao nhiêu học viên đang học khóa IELTS 6.5?" — phải kiểm tra cả ba cột, viết điều kiện
WHERE KhoaHoc1='IELTS 6.5' OR KhoaHoc2='IELTS 6.5' OR KhoaHoc3='IELTS 6.5', rất dễ sót. - Đa số ô bị NULL, gây lãng phí và khó thống kê.
DangKy:| MaHV | KhoaHoc |
|---|---|
| HV01 | IELTS 6.5 |
| HV01 | Giao tiếp |
| HV02 | TOEIC 750 |
| HV03 | IELTS 6.5 |
| HV03 | Giao tiếp |
| HV03 | Phát âm |
_1, _2, _3, đó là tín hiệu đỏ của repeating group. Hãy chuyển chúng thành các dòng trong một bảng quan hệ con. Học viên đăng ký bao nhiêu khóa cũng được, cấu trúc không đổi.Ví dụ 3 — Hệ thống giao hàng (atomic phụ thuộc nghiệp vụ)
Bối cảnh: Một công ty giao hàng nội thành lưu địa chỉ người nhận trong một cột duy nhất:
| MaDon | DiaChi |
|---|---|
| 2001 | 123 Lê Lợi, Phường Bến Thành, Quận 1, TP.HCM |
DiaChi này có vi phạm 1NF không? Câu trả lời là tùy nghiệp vụ.
- Nếu hệ thống chỉ in nhãn giao hàng và không bao giờ cần lọc theo quận/phường, thì coi cả chuỗi là atomic cũng tạm ổn.
- Nhưng công ty này cần phân tuyến giao hàng theo Quận và tính phí ship theo Phường. Lúc này, "Quận 1" và "Phường Bến Thành" là những đơn vị nghiệp vụ cần truy vấn độc lập, nên chuỗi địa chỉ gộp đã vi phạm tính atomic đối với nhu cầu của họ.
SoNhaDuong, Phuong, Quan, TinhThanh (chủ đề mô hình hóa địa chỉ Việt Nam sẽ được đào sâu ở Bài 25):| MaDon | SoNhaDuong | Phuong | Quan | TinhThanh |
|---|---|---|---|---|
| 2001 | 123 Lê Lợi | Bến Thành | Quận 1 | TP.HCM |
Hướng dẫn từng bước
Đây là quy trình thực dụng để đưa một bảng bất kỳ về 1NF:
Bước 1 — Liệt kê toàn bộ cột và quan sát dữ liệu mẫu. Đừng chỉ nhìn tên cột; hãy nhìn dữ liệu thật. Nhiều vi phạm 1NF chỉ lộ ra khi bạn thấy giá trị thực tế (ví dụ một ô có dấu phẩy giữa các giá trị).
Bước 2 — Soi từng cột tìm vi phạm atomic. Đặt câu hỏi cho mỗi cột: "Một ô ở đây có khả năng chứa nhiều giá trị không? Có dấu phân tách (phẩy, gạch chéo, xuống dòng) không? Nghiệp vụ có cần truy vấn từng phần riêng không?" Đánh dấu những cột nghi ngờ.
Bước 3 — Soi cấu trúc bảng tìm repeating group. Tìm các cột được đánh số hoặc lặp ý nghĩa: SanPham1/2/3, DienThoai_Nha/DienThoai_DiDong (nếu cùng loại "số điện thoại"), v.v.
Bước 4 — Quyết định mức atomic phù hợp với nghiệp vụ. Với mỗi cột nghi ngờ, xác định: nghiệp vụ cần chi tiết đến đâu? Họ tên có cần tách không? Địa chỉ có cần tách quận/phường không? Đây là bước cần "đội mũ BA" thay vì chỉ máy móc tách mọi thứ.
Bước 5 — Áp dụng phép biến đổi.
- Vi phạm atomic (nhiều giá trị trong một ô, danh sách): tách thành nhiều dòng, mỗi giá trị một dòng.
- Vi phạm atomic (giá trị tổng hợp như họ tên, địa chỉ): tách thành nhiều cột, mỗi thành phần một cột.
- Repeating group (cột đánh số): chuyển sang một bảng con với quan hệ một-nhiều.
(MaDon, SanPham).Bước 7 — Kiểm tra lại. Quét toàn bộ bảng mới: mỗi ô đúng một giá trị atomic chưa? Không còn cột đánh số chứ? Mỗi dòng phân biệt duy nhất chưa? Nếu cả ba đều "có" thì bạn đã đạt 1NF.
Lỗi thường gặp & mẹo
Lỗi 1 — Nhầm "tách thành cột" với "tách thành dòng". Khi gặp một ô chứa danh sách nhiều giá trị cùng loại (nhiều sản phẩm, nhiều khóa học), bạn phải tách thành dòng, không phải cột — vì tách thành cột lại tạo ra repeating group. Ngược lại, khi gặp một giá trị tổng hợp nhiều thành phần khác loại (họ + tên, số nhà + phường + quận), bạn tách thành cột.
Lỗi 2 — Tách quá đà. Không phải mọi thứ đều cần tách đến nguyên tử nhỏ nhất. Nếu nghiệp vụ không bao giờ cần lọc theo họ riêng, việc tách HoTen thành ba cột chỉ làm phức tạp thêm. Hãy để nhu cầu nghiệp vụ dẫn dắt, đừng chuẩn hóa vì "cho đẹp".
Lỗi 3 — Coi cột JSON/mảng là vi phạm 1NF một cách máy móc. Các CSDL hiện đại hỗ trợ kiểu JSON, array. Về lý thuyết quan hệ thuần túy, một cột chứa mảng vi phạm 1NF. Nhưng trong thực tế hiện đại, đôi khi lưu JSON là quyết định thiết kế có chủ đích (sẽ bàn ở Bài 29). Ở giai đoạn học nền tảng này, hãy nắm vững nguyên tắc 1NF cổ điển trước; biết khi nào "phá luật có chủ đích" là chuyện về sau.
Lỗi 4 — Quên xử lý dữ liệu lịch sử khi đã lỡ vi phạm. Sửa cấu trúc bảng là một chuyện; nhưng dữ liệu cũ "Son, Kem, Mặt nạ" vẫn nằm đó. Bạn cần một bước migration để tách dữ liệu cũ (chủ đề Migration được học ở Bài 30).
Mẹo: Một "đèn báo" cực nhanh để nghi ngờ vi phạm 1NF — nếu bạn thấy mình sắp viết LIKE '%,%', dùng hàm SPLIT/SUBSTRING_INDEX để tách chuỗi, hoặc viết OR lặp đi lặp lại trên các cột đánh số, thì gần như chắc chắn bảng đang chưa đạt 1NF.
Mẹo: Khi phỏng vấn người dùng nghiệp vụ, hãy hỏi thẳng: "Anh/chị có bao giờ cần đếm, lọc, hay tính tổng dựa trên một phần của thông tin này không?" Câu trả lời sẽ cho bạn biết mức atomic cần thiết.
Bài tập thực hành
Cho bảng DonDatBan của một nhà hàng lẩu ở Hà Nội dưới đây. Hãy phân tích và chuẩn hóa về 1NF.
| MaBan | TenKhach | MonAn | GioPhucVu |
|---|---|---|---|
| B01 | Nguyễn Hà | Lẩu thái, Rau muống, Nấm | 18:30 |
| B02 | Trần Bình | Lẩu nấm, Bò Mỹ | 19:00 |
| B03 | Lê Cường | Lẩu thái | 20:15 |
- Chỉ ra (các) cột đang vi phạm 1NF và giải thích vi phạm thuộc loại nào (atomic hay repeating group).
- Vẽ lại bảng đã chuẩn hóa về 1NF.
- Đề xuất primary key cho bảng kết quả.
- Câu hỏi mở rộng: Nếu nghiệp vụ cần thống kê "món nào được gọi nhiều nhất trong tuần", giải thích vì sao bảng gốc không thể trả lời còn bảng đã chuẩn hóa thì có thể.
- Cột
MonAnvi phạm atomic — mỗi ô chứa danh sách nhiều món ngăn cách bằng dấu phẩy. - Tách mỗi món thành một dòng riêng:
| MaBan | TenKhach | MonAn | GioPhucVu |
|---|---|---|---|
| B01 | Nguyễn Hà | Lẩu thái | 18:30 |
| B01 | Nguyễn Hà | Rau muống | 18:30 |
| B01 | Nguyễn Hà | Nấm | 18:30 |
| B02 | Trần Bình | Lẩu nấm | 19:00 |
| B02 | Trần Bình | Bò Mỹ | 19:00 |
| B03 | Lê Cường | Lẩu thái | 20:15 |
- Primary key: khóa kết hợp
(MaBan, MonAn)— phân biệt được từng dòng (giả định mỗi bàn không gọi trùng món; nếu có thể trùng, cần thêm một mã dòng riêng). - Bảng gốc không thể đếm món vì các món bị "khóa" trong chuỗi văn bản — muốn đếm phải tách chuỗi thủ công và dễ sai. Bảng đã chuẩn hóa cho phép
GROUP BY MonAnrồiCOUNT, trả lời câu hỏi chỉ trong một truy vấn.
Tóm tắt
First Normal Form (1NF) là bậc đầu tiên và bắt buộc của chuẩn hóa. Một bảng đạt 1NF khi: mỗi ô chứa đúng một giá trị atomic (không chia nhỏ hơn theo nhu cầu nghiệp vụ), không có repeating group (không có cột đánh số _1, _2, _3), và mỗi dòng phân biệt duy nhất.
Những điều cần khắc cốt ghi tâm:
- Danh sách nhiều giá trị cùng loại trong một ô → tách thành nhiều dòng.
- Giá trị tổng hợp nhiều thành phần khác loại → tách thành nhiều cột (nếu nghiệp vụ cần).
- Cột đánh số lặp lại → chuyển sang bảng con với quan hệ một-nhiều.
- Atomic phụ thuộc nghiệp vụ: hãy để nhu cầu truy vấn của người dùng quyết định mức chi tiết, đừng tách quá đà cũng đừng gộp ẩu.