zalo-icon
facebook-icon
phone-icon
Thiết kế bảng PostgreSQL: Data Type, Constraint và Identity | Table Design Guide

Một bảng chạy được chưa chắc là một bảng được thiết kế tốt. Những quyết định nhỏ về kiểu dữ liệu, khóa và constraint sẽ quyết định dữ liệu có đáng tin sau vài năm vận hành hay không.

Bài viết thiết kế lại lớp dữ liệu cho hệ thống bán hàng, giải thích từng lựa chọn và kiểm tra bằng các trường hợp hợp lệ lẫn cố tình sai. Mục tiêu không phải ghi nhớ danh sách kiểu dữ liệu, mà biết cách biến quy tắc kinh doanh thành cấu trúc được PostgreSQL bảo vệ.

Bắt đầu từ quy tắc nghiệp vụ, không bắt đầu từ kiểu dữ liệu

Trước khi viết CREATE TABLE, cần trả lời dữ liệu đại diện cho điều gì, phạm vi giá trị hợp lệ là gì và có được thay đổi hay không. “Giá sản phẩm” khác “giá tại thời điểm đặt hàng”; một giá trị thay đổi theo catalog, giá còn lại là bằng chứng lịch sử.

Với mỗi cột, hãy ghi rõ đơn vị, tính bắt buộc, mức duy nhất, nguồn sinh và vòng đời. Bản mô tả ngắn này ngăn nhóm phát triển tự hiểu khác nhau khi cùng xây một hệ thống.

Chọn data type phù hợp trong PostgreSQL

Chọn data type vừa đúng nghĩa vừa đủ phạm vi

bigint phù hợp cho định danh tăng dài hạn; integer thường đủ cho số lượng. Tiền nên dùng numeric(p,s). Chuỗi có giới hạn nghiệp vụ rõ có thể dùng varchar(n); văn bản mô tả dài dùng text. Không nên chọn kiểu lớn nhất theo phản xạ vì kiểu dữ liệu còn truyền tải ý nghĩa.

CREATE TABLE catalog.products (
  product_id bigint GENERATED ALWAYS AS IDENTITY,
  sku varchar(40) NOT NULL,
  product_name varchar(160) NOT NULL,
  description text,
  list_price numeric(14,2) NOT NULL,
  stock_quantity integer NOT NULL DEFAULT 0,
  active boolean NOT NULL DEFAULT true,
  PRIMARY KEY (product_id)
);

Mô hình hóa tiền tệ mà không mất độ chính xác

Kiểu real và double precision lưu số gần đúng, thích hợp cho đo lường khoa học hơn là tiền. numeric(14,2) lưu giá trị thập phân chính xác và cho phép kiểm soát số chữ số.

SELECT 0.1::double precision + 0.2::double precision AS approximate,
       0.1::numeric + 0.2::numeric AS exact;
ALTER TABLE catalog.products
  ADD CONSTRAINT products_price_non_negative CHECK (list_price >= 0);

Nếu hệ thống đa tiền tệ, cần thêm mã ISO như VND hoặc USD. Không cộng các giá trị khác loại tiền chỉ vì chúng cùng là số.

Identity và khóa chính PostgreSQL

Identity và Primary Key: giống nhau ở đâu, khác nhau ở đâu?

GENERATED ... AS IDENTITY tạo giá trị tăng tự động theo chuẩn SQL. PRIMARY KEY bảo đảm giá trị không null và duy nhất. Hai khái niệm thường đi cùng nhưng không thay thế nhau: identity sinh số, primary key bảo vệ danh tính bản ghi.

CREATE TABLE sales.orders (
  order_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  order_code varchar(30) NOT NULL UNIQUE,
  created_at timestamptz NOT NULL DEFAULT now()
);

order_id tối ưu cho liên kết nội bộ; order_code phục vụ giao tiếp với người dùng. Không nên dùng mã có ý nghĩa kinh doanh làm khóa chính nếu quy tắc tạo mã có thể thay đổi.

Constraint bảo vệ dữ liệu PostgreSQL

Constraint là lớp kiểm soát dữ liệu gần nguồn nhất

NOT NULL, UNIQUE, CHECK và FOREIGN KEY giúp mọi ứng dụng tuân thủ cùng một quy tắc. Nếu chỉ kiểm tra ở giao diện, một script nhập dữ liệu hoặc dịch vụ khác vẫn có thể ghi sai.

ALTER TABLE sales.orders
  ADD COLUMN status varchar(20) NOT NULL DEFAULT 'pending',
  ADD CONSTRAINT orders_status_valid
    CHECK (status IN ('pending','paid','cancelled','refunded'));

ALTER TABLE sales.order_items
  ADD CONSTRAINT order_items_quantity_positive CHECK (quantity > 0);

Tên constraint rõ ràng làm thông báo lỗi dễ đọc và hỗ trợ migration. Quy tắc quá phức tạp hoặc phụ thuộc nhiều bảng nên được xử lý bằng transaction hay logic nghiệp vụ, không ép tất cả vào CHECK.

Date, timestamp và timestamptz: lựa chọn ảnh hưởng báo cáo

date biểu diễn một ngày không có múi giờ. timestamptz biểu diễn một thời điểm tuyệt đối và hiển thị theo múi giờ phiên làm việc. Với sự kiện như tạo đơn hay thanh toán, timestamptz thường an toàn hơn.

SET TIME ZONE 'Asia/Ho_Chi_Minh';
SELECT now() AS local_display,
       now() AT TIME ZONE 'UTC' AS utc_time;

Nên thống nhất lưu thời điểm và quy tắc chuyển đổi ngay từ đầu. Lỗi lệch ngày cuối tháng thường không đến từ SQL khó, mà từ việc các hệ thống hiểu múi giờ khác nhau.

Quan hệ giữa các bảng PostgreSQL

Khóa ngoại và chiến lược khi xóa dữ liệu

ON DELETE CASCADE phù hợp khi bản ghi con không có ý nghĩa nếu bản ghi cha biến mất, như dòng chi tiết thuộc đơn hàng nháp. RESTRICT phù hợp khi cần ngăn xóa dữ liệu đang được tham chiếu. SET NULL chỉ hợp lệ nếu quan hệ là tùy chọn.

Với giao dịch đã hoàn tất, doanh nghiệp thường không xóa vật lý mà chuyển trạng thái hoặc ẩn khỏi giao diện. Audit và nghĩa vụ kế toán quan trọng hơn cảm giác “database sạch”.

Migration thay đổi schema PostgreSQL có kiểm soát

Thay đổi schema an toàn khi hệ thống đang chạy

Thêm cột NOT NULL vào bảng lớn có thể khóa hoặc thất bại nếu dữ liệu cũ chưa có giá trị. Cách an toàn là thêm cột cho phép null, backfill theo lô, thêm constraint ở bước sau rồi mới siết NOT NULL.

ALTER TABLE sales.orders ADD COLUMN channel varchar(20);
UPDATE sales.orders SET channel='web' WHERE channel IS NULL;
ALTER TABLE sales.orders ALTER COLUMN channel SET NOT NULL;
ALTER TABLE sales.orders ADD CONSTRAINT orders_channel_valid
  CHECK (channel IN ('web','store','marketplace'));

Kiểm thử thiết kế bằng dữ liệu cố tình sai

Thiết kế chỉ được xem là hoàn thành khi đã thử phá vỡ nó. Hãy chèn giá âm, SKU trùng, trạng thái không hợp lệ và khóa ngoại không tồn tại. Mỗi lệnh phải bị từ chối bởi đúng constraint dự kiến.

INSERT INTO catalog.products(sku,product_name,list_price)
VALUES ('KB-01','Bàn phím thử nghiệm',-1000);

SELECT conname, pg_get_constraintdef(oid)
FROM pg_constraint
WHERE conrelid='catalog.products'::regclass;

Song song với kiểm thử lỗi, cần kiểm tra dữ liệu hợp lệ vẫn ghi được. Constraint quá chặt cũng có thể trở thành lỗi thiết kế nếu nó ngăn một tình huống nghiệp vụ chính đáng.

Checklist review một bảng trước khi đưa vào production

Mỗi bảng cần có khóa chính ổn định, tên cột nhất quán, data type đúng nghĩa, constraint cho quy tắc quan trọng, thời gian có múi giờ rõ ràng và index cho quan hệ thường truy vấn. Cần biết dữ liệu nào được xóa, dữ liệu nào chỉ đổi trạng thái và dữ liệu nào phải lưu để audit.

Cuối cùng, lưu DDL trong Git, có migration thuận và rollback, chạy thử trên dữ liệu gần production, đo thời gian khóa bảng và cập nhật tài liệu mô hình. Một bảng tốt là bảng mà người khác đọc schema có thể hiểu được phần lớn nghiệp vụ.

Kết luận

Thiết kế bảng là bước biến kiến thức nghiệp vụ thành cam kết kỹ thuật. Data type thể hiện ý nghĩa, constraint giữ dữ liệu đúng và identity giải quyết việc sinh định danh; khi ba phần này được chọn có chủ đích, những lớp ứng dụng phía trên trở nên đơn giản và đáng tin hơn.

TechData.AI - Leading the Future.
Tham khảo các khoá học theo link: https://techdata.ai/techdata-ai-course/
Hoàng Minh.

Scroll to Top