Menu
ESC

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

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

Đang tải...

Bài 5 — Attributes — Loại và Constraints

Data Modeling and ERD Mastery Bài 5/60

Mở đầu — vì sao bài này quan trọng

Ở Bài 4, bạn đã học cách nhận diện các entity — những "danh từ" quan trọng mà hệ thống cần lưu trữ, ví dụ như Khách hàng, Đơn hàng, Sản phẩm. Nhưng một entity rỗng thì chẳng nói lên điều gì. Một "Khách hàng" mà không có tên, số điện thoại, ngày sinh thì cũng vô nghĩa như một tờ khai trống. Chính attributes (thuộc tính) mới là phần "thịt" làm cho entity sống dậy, mô tả nó là ai, trông như thế nào, và có gì đặc biệt.

Đối với một Business Analyst (BA), việc thiết kế attributes không đơn thuần là "liệt kê các cột trong bảng". Đây là nơi nghiệp vụ gặp dữ liệu. Một quyết định nhỏ — chẳng hạn lưu ngày sinh dưới dạng văn bản tự do hay dạng ngày tháng chuẩn — có thể quyết định liệu sau này phòng marketing có gửi được lời chúc sinh nhật tự động hay không, hay liệu hệ thống có chặn được người dưới 18 tuổi mở thẻ tín dụng hay không. Sai sót ở tầng attributes thường rất tốn kém để sửa, vì dữ liệu "bẩn" đã trót nằm trong cơ sở dữ liệu hàng triệu dòng.

Trong bài này, chúng ta sẽ đi sâu vào hai chủ đề cốt lõi: phân loại attributes (đơn, ghép, đa trị, dẫn xuất, định danh) và constraints (các ràng buộc đảm bảo dữ liệu luôn hợp lệ). Đây là kỹ năng nền tảng mà mọi mô hình dữ liệu — từ ERD đơn giản đến hệ thống ngân hàng phức tạp — đều phải dựa vào.

Khái niệm cốt lõi

Attribute là gì?

Attribute là một đặc tính mô tả của entity. Nếu entity là "Khách hàng", thì các attribute có thể là: họ tên, số CCCD, số điện thoại, email, ngày sinh, địa chỉ. Khi mô hình được hiện thực hóa trong cơ sở dữ liệu quan hệ, mỗi attribute thường tương ứng với một cột (column) trong bảng, còn mỗi dòng (row) là một thể hiện (instance) cụ thể của entity.

Mỗi attribute có một domain — tập hợp các giá trị hợp lệ mà nó được phép nhận. Domain của "tuổi" là số nguyên dương; domain của "giới tính" có thể là {Nam, Nữ, Khác}; domain của "email" là chuỗi văn bản theo định dạng email. Hiểu rõ domain chính là bước đầu để đặt ra constraints về sau.

Phân loại attributes

Trong mô hình hóa dữ liệu, người ta phân loại attributes theo nhiều trục khác nhau. Năm phân loại quan trọng nhất mà một BA cần nắm vững là:

1. Simple (đơn) và Composite (ghép)

  • Simple attribute là attribute không thể chia nhỏ thêm mà vẫn giữ ý nghĩa nghiệp vụ độc lập. Ví dụ: tuoi, gioi_tinh, email. Bạn không tách email thành "phần trước @" và "phần sau @" để xử lý nghiệp vụ riêng biệt.
  • Composite attribute là attribute có thể tách thành nhiều thành phần nhỏ có ý nghĩa riêng. Kinh điển nhất là địa chỉ: một địa chỉ Việt Nam gồm số nhà, tên đường, phường/xã, quận/huyện, tỉnh/thành. Tương tự, họ tên có thể tách thành họ, tên đệm, tên.
Quyết định nên giữ composite "gộp" hay "tách" phụ thuộc vào nghiệp vụ. Nếu bạn cần lọc khách hàng theo tỉnh để phân vùng giao hàng, bạn buộc phải tách địa chỉ ra. Nếu chỉ in lên hóa đơn, để nguyên một chuỗi cũng được.

2. Single-valued (đơn trị) và Multi-valued (đa trị)

  • Single-valued chỉ nhận đúng một giá trị tại một thời điểm: một người có đúng một ngày sinh.
  • Multi-valued có thể nhận nhiều giá trị: một khách hàng có thể có nhiều số điện thoại, nhiều email. Trong ký hiệu Chen, attribute đa trị được vẽ bằng hình bầu dục viền đôi.
Đây là điểm bẫy lớn: cơ sở dữ liệu quan hệ không hỗ trợ trực tiếp attribute đa trị trong một cột. Khi gặp attribute đa trị, BA thường phải tách nó thành một entity/bảng riêng (ví dụ bảng SoDienThoaiKhachHang). Chúng ta sẽ thấy điều này gắn chặt với chuẩn hóa (normalization) ở các bài sau.

3. Stored (lưu trữ) và Derived (dẫn xuất)

  • Stored attribute là giá trị được lưu thẳng vào cơ sở dữ liệu, ví dụ ngay_sinh.
  • Derived attribute là giá trị được tính ra từ các attribute khác, không nên lưu cố định. Ví dụ kinh điển: tuoi được tính từ ngay_sinh và ngày hiện tại. Nếu bạn lưu cứng tuoi = 30, thì sang năm nó vẫn là 30 — sai. Trong ERD Chen, derived attribute vẽ bằng đường viền nét đứt.
Một ví dụ derived khác: tong_tien của đơn hàng = tổng (số lượng × đơn giá) của các dòng hàng. Tuy nhiên ở đây có ngoại lệ — đôi khi ta vẫn lưu cứng vì lý do hiệu năng hoặc vì giá có thể thay đổi theo thời gian; chủ đề denormalization này sẽ bàn riêng ở Bài 15.

4. Key attribute (định danh)

Là attribute (hoặc tập attribute) dùng để phân biệt duy nhất từng instance của entity, ví dụ số CCCD của công dân hay mã đơn hàng. Trong ERD Chen, key attribute được gạch chân. Đây là tiền đề cho khái niệm Primary Key — sẽ được mổ xẻ kỹ ở Bài 6, nên ở bài này ta chỉ nhận diện chứ chưa đi sâu.

Constraints — các ràng buộc trên attribute

Nếu phân loại trả lời câu hỏi "attribute này thuộc loại gì", thì constraints trả lời câu hỏi "giá trị nào được phép, giá trị nào bị cấm". Constraints chính là tấm lưới bảo vệ chất lượng dữ liệu. Những loại constraint phổ biến nhất ở cấp attribute:

  • Data type (kiểu dữ liệu): INTEGER, VARCHAR, DATE, BOOLEAN, DECIMAL... Đây là ràng buộc cơ bản nhất. Lưu số tiền thì dùng DECIMAL chứ không dùng số thực dấu phẩy động (FLOAT) để tránh sai số làm tròn.
  • NOT NULL (bắt buộc): attribute không được để trống. Họ tên khách hàng nên là NOT NULL; ghi chú thì có thể NULL.
  • UNIQUE (duy nhất): không hai dòng nào trùng giá trị này, ví dụ email đăng nhập.
  • DEFAULT (giá trị mặc định): nếu không nhập, hệ thống tự gán, ví dụ trang_thai = 'cho_xu_ly'.
  • CHECK (kiểm tra điều kiện): ràng buộc logic nghiệp vụ, ví dụ tuoi >= 0, luong > 0, hoặc gioi_tinh IN ('Nam','Nu','Khac').
  • Length / size (độ dài): số CCCD Việt Nam chính xác 12 ký tự, số điện thoại tối đa 11 chữ số.
  • Format (định dạng): email phải đúng cấu trúc, mã số thuế phải đúng số chữ số.
Với vai trò BA, bạn không cần viết câu lệnh SQL cho từng constraint, nhưng bạn phải mô tả rõ ràng quy tắc nghiệp vụ để lập trình viên hiện thực hóa. Một constraint bị bỏ sót thường biến thành một lỗi dữ liệu im lặng, âm thầm tích lũy.

Tình huống thực tế

Tình huống 1 — Tiki và bài toán "một khách, nhiều số điện thoại"

Hãy tưởng tượng một sàn thương mại điện tử lớn như Tiki. Ban đầu, đội thiết kế lưu số điện thoại khách hàng trong một cột duy nhất so_dien_thoai của bảng KhachHang. Mọi thứ ổn cho đến khi bộ phận giao hàng phản ánh: nhiều khách muốn lưu số nhà riêng và số người thân để shipper gọi dự phòng khi không liên lạc được.

Đội nghiệp vụ "chữa cháy" bằng cách nhồi cả hai số vào một ô, ngăn cách bằng dấu phẩy: 0901234567, 0987654321. Hậu quả: khi hệ thống SMS gửi mã OTP, nó gửi tới cả chuỗi "0901234567, 0987654321" và thất bại. Việc lọc khách theo đầu số mạng cũng vỡ trận.

Bài học: số điện thoại ở đây là một multi-valued attribute. Cách đúng là tách thành bảng riêng SoDienThoai(id, khach_hang_id, so, loai, la_chinh), trong đó loai phân biệt "cá nhân/người thân", la_chinh đánh dấu số mặc định. Nhận diện đúng tính đa trị ngay từ giai đoạn ERD giúp tránh khoản nợ kỹ thuật khổng lồ sau này.

Tình huống 2 — Ngân hàng và derived attribute "tuổi" gây hậu quả pháp lý

Một ngân hàng tại TP.HCM xây hệ thống mở thẻ tín dụng. Yêu cầu nghiệp vụ: khách hàng phải đủ 18 tuổi. Lập trình viên ban đầu tạo cột tuoi (stored), nhập tay khi đăng ký. Một khách 17 tuổi 11 tháng được nhân viên làm tròn thành "18" cho nhanh — và hệ thống cấp thẻ. Vài tháng sau, kiểm toán nội bộ phát hiện đã cấp thẻ cho người chưa đủ tuổi, dẫn tới rủi ro pháp lý và phải thu hồi.

Bài học: tuoi là một derived attribute — phải tính từ ngay_sinh (stored, NOT NULL) so với ngày hệ thống, kèm constraint nghiệp vụ CHECK (tuoi_tinh_tu_ngay_sinh >= 18) tại thời điểm xét duyệt. Lưu cứng tuổi vừa sai theo thời gian, vừa cho phép con người can thiệp thủ công vào dữ liệu lẽ ra phải được tính tự động. Quy tắc vàng: cái gì tính được thì đừng lưu cứng, trừ khi có lý do hiệu năng rõ ràng.

Tình huống 3 — Grab Food và constraint thiếu trên giá món ăn

Một startup giao đồ ăn (tạm gọi mô hình giống Grab Food) cho phép nhà hàng tự nhập giá món. Bảng MonAn có cột gia kiểu số, nhưng đội thiết kế quên đặt constraint. Một chủ quán gõ nhầm dấu trừ thành -50000. Hệ thống tính khuyến mãi cộng dồn khiến đơn hàng có tổng tiền âm, và ví điện tử... cộng tiền cho khách thay vì trừ. Chỉ trong một buổi tối, công ty mất hàng chục triệu đồng.

Bài học: cột gia cần ít nhất ba constraint — kiểu DECIMAL(12,0) (đồng VND không có phần lẻ), NOT NULL, và CHECK (gia > 0). Khi mô tả attribute, BA nên luôn tự hỏi: "Giá trị âm có hợp lệ không? Số 0 có hợp lệ không? Giá trị lớn bất thường thì sao?". Những câu hỏi biên (edge case) này chính là nơi sinh ra constraints cứu nguy.

Hướng dẫn từng bước

Khi đứng trước một entity và cần xác định attributes cùng constraints, bạn có thể làm theo quy trình sau:

  • Liệt kê mọi đặc tính mô tả entity. Phỏng vấn người dùng nghiệp vụ, đọc tài liệu, xem các biểu mẫu giấy đang dùng. Với entity KhachHang: họ tên, ngày sinh, giới tính, CCCD, điện thoại, email, địa chỉ, ngày đăng ký.
  • Phân loại từng attribute theo các trục đã học. Đánh dấu cái nào composite (địa chỉ, họ tên), cái nào multi-valued (điện thoại, email), cái nào derived (tuổi). Việc gắn nhãn này quyết định attribute đó nằm trong bảng chính hay phải tách ra.
  • Xác định domain và data type cho từng attribute. CCCD là chuỗi 12 ký tự; ngày sinh là DATE; giới tính là tập giá trị giới hạn.
  • Đặt constraints dựa trên quy tắc nghiệp vụ. Với mỗi attribute, lần lượt hỏi: bắt buộc nhập không (NOT NULL)? duy nhất không (UNIQUE)? có giá trị mặc định không (DEFAULT)? có điều kiện hợp lệ nào (CHECK)? có giới hạn độ dài/định dạng không?
  • Xử lý composite và multi-valued. Quyết định tách composite ra thành nhiều cột nếu cần truy vấn riêng. Đưa multi-valued ra bảng quan hệ riêng.
  • Loại bỏ derived attribute khỏi phần lưu trữ. Ghi chú rõ "tuổi = tính từ ngày sinh" để đội phát triển xử lý ở tầng ứng dụng hoặc view, thay vì lưu cột cố định.
  • Ghi lại quyết định. Mỗi attribute kèm kiểu dữ liệu, ràng buộc và mô tả nghiệp vụ nên được đưa vào tài liệu — đây chính là tiền đề cho Data Dictionary (Bài 17).

Lỗi thường gặp & mẹo

  • Nhồi nhiều giá trị vào một cột. Lưu "Hà Nội, Đà Nẵng" trong một ô địa chỉ, hay nhiều số điện thoại ngăn bởi dấu phẩy. Đây là vi phạm chuẩn 1NF và là dấu hiệu attribute đa trị bị bỏ sót. Mẹo: nếu bạn thấy mình muốn dùng dấu phẩy bên trong một ô, gần như chắc chắn cần tách bảng.
  • Lưu cứng derived attribute. Lưu tuoi, tong_tien rồi quên cập nhật, dẫn tới dữ liệu lệch. Mẹo: cái gì tính được thì để tính, trừ khi có lý do hiệu năng rõ ràng và có cơ chế đồng bộ.
  • Quên NOT NULL cho dữ liệu thiết yếu. Cho phép họ tên hoặc số CCCD để trống khiến báo cáo và đối soát về sau khốn khổ. Mẹo: với mỗi cột, mặc định hỏi "trống có chấp nhận được không?" trước khi quyết định cho NULL.
  • Dùng sai kiểu dữ liệu cho tiền và số điện thoại. Tiền dùng FLOAT gây sai số làm tròn; số điện thoại lưu kiểu số nguyên làm mất số 0 đầu (0901... thành 901...). Mẹo: tiền dùng DECIMAL, số điện thoại và mã định danh dùng chuỗi (VARCHAR).
  • Bỏ qua constraint biên. Không chặn giá âm, tuổi âm, số lượng bằng 0. Mẹo: với mỗi attribute số, luôn kiểm tra ba biên: giá trị âm, giá trị 0, và giá trị lớn bất thường.
  • Gộp composite quá sớm hoặc tách quá đà. Tách họ tên thành 5 cột trong khi nghiệp vụ chỉ cần in ra một dòng là lãng phí; ngược lại gộp địa chỉ khi cần lọc theo tỉnh lại gây tắc. Mẹo: để nghiệp vụ (truy vấn thực tế) dẫn dắt quyết định tách/gộp.

Bài tập thực hành

Cho entity SinhVien của một trường đại học tại Việt Nam, với các thông tin thu thập từ biểu mẫu nhập học: họ và tên, ngày sinh, tuổi, mã số sinh viên, số CCCD, các số điện thoại liên hệ, email, địa chỉ thường trú (số nhà, đường, phường, quận, tỉnh), điểm trung bình tích lũy (GPA), và danh sách các ngoại ngữ biết.

Hãy thực hiện:

  • Phân loại từng attribute: cái nào simple, composite, multi-valued, derived, key? (Gợi ý: chú ý "tuổi", "các số điện thoại", "địa chỉ", "danh sách ngoại ngữ".)
  • Xác định data type và domain cho từng attribute.
  • Đề xuất constraints cho ít nhất 5 attribute, nêu rõ NOT NULL / UNIQUE / CHECK / DEFAULT phù hợp. Ví dụ: GPA cần CHECK (gpa >= 0 AND gpa <= 4.0).
  • Chỉ ra attribute nào cần tách thành bảng riêng và giải thích vì sao.
  • Viết một đoạn ngắn giải thích tại sao "tuổi" không nên là cột lưu trữ.
Sau khi làm xong, hãy đối chiếu: bạn có nhận ra "danh sách ngoại ngữ" là multi-valued (phải tách bảng) và "tuổi" là derived (không lưu) không? Đó là hai bẫy phổ biến nhất.

Tóm tắt

  • Attribute là đặc tính mô tả entity; trong cơ sở dữ liệu quan hệ, mỗi attribute thường là một cột, và mỗi attribute có một domain giá trị hợp lệ.
  • Năm phân loại cốt lõi: simple/composite (chia nhỏ được hay không), single/multi-valued (một hay nhiều giá trị), stored/derived (lưu thẳng hay tính ra), và key attribute (định danh).
  • Composite cần cân nhắc tách/gộp theo nhu cầu truy vấn; multi-valued thường phải tách thành bảng riêng; derived thì không nên lưu cứng.
  • Constraints — data type, NOT NULL, UNIQUE, DEFAULT, CHECK, length, format — là tấm lưới bảo vệ chất lượng dữ liệu. BA phải mô tả rõ quy tắc nghiệp vụ để đội phát triển hiện thực hóa.
  • Ba câu chuyện thực tế (Tiki, ngân hàng, Grab Food) cho thấy: bỏ sót tính đa trị, lưu cứng giá trị dẫn xuất, hay quên constraint biên đều dẫn tới hậu quả tốn kém và đôi khi cả rủi ro pháp lý.
Nắm vững attributes và constraints là bước chuyển bạn từ "vẽ ô vuông trên ERD" sang "thiết kế dữ liệu thật sự đáng tin cậy". Ở bài tiếp theo, chúng ta sẽ đào sâu vào loại attribute quan trọng nhất — Primary Key — và các chiến lược chọn khóa: natural, surrogate và composite.