Đặt tên trong PostgreSQL: snake_case, 63 byte và tên constraint Cơ bản
Thời gian đọc ước tính: 4 phút
Cần đọc trước
Trang này trả lời: vì sao database nên dùng snake_case, PostgreSQL xử lý tên dài và tên trùng ra sao, và nên đặt tên constraint, index thế nào.
Case folding: vì sao snake_case
Phần tiêu đề “Case folding: vì sao snake_case”PostgreSQL gập identifier không quoted về chữ thường: Orders, ORDERS, orders là cùng một bảng. Còn "Orders" (có ngoặc kép) là tên khác và phân biệt hoa thường.
Đang tải diagram…
-- ORM tạo bảng bằng tên có quoteCREATE TABLE "OrderItems" ("orderId" bigint, "createdAt" timestamp);
SELECT orderId FROM OrderItems;-- ERROR: relation "orderitems" does not exist
SELECT orderId FROM "OrderItems";-- ERROR: column "orderid" does not exist-- Từ giờ mọi câu SQL viết tay, dashboard, script đều phải quote từng tên.Quy tắc đơn giản nhất (cũng là khuyến nghị của PostgreSQL wiki): chỉ dùng a-z, 0-9, _, không bao giờ cần quote. Map sang camelCase ở tầng ORM/query builder:
| Công cụ | Cách map |
|---|---|
| Prisma | @map / @@map trong schema |
| TypeORM | naming strategy (ví dụ snake_case strategy) |
| Kysely | CamelCasePlugin |
| Knex | hook wrapIdentifier / postProcessResponse |
Giới hạn 63 byte
Phần tiêu đề “Giới hạn 63 byte”Identifier dài tối đa NAMEDATALEN - 1 byte, mặc định 63 byte. Tên dài hơn vẫn được chấp nhận nhưng bị cắt, PostgreSQL chỉ phát một NOTICE:
NOTICE: identifier "payment_provider_settlement_reconciliation_line_items_amount_cents_idx" will be truncated to "payment_provider_settlement_reconciliation_line_items_amount_ce"Migration tool thường nuốt NOTICE. Hậu quả thật (đã chạy trên PostgreSQL 17):
- Hai tên khác nhau nhưng trùng 63 byte đầu sẽ đụng nhau: index thứ hai báo
relation "..._amount_ce" already exists, migration fail giữa chừng. - Câu SQL dùng tên đầy đủ vẫn được cắt cùng cách nên vẫn chạy; nhưng công cụ so tên trong catalog với tên đầy đủ trong code (schema diff, kiểm tra drift) sẽ thấy lệch.
Giới hạn tính theo byte, không theo ký tự: đ chiếm 2 byte UTF-8, nên tên tiếng Việt có dấu ngắn hơn 63 ký tự nhiều. Thêm một lý do để chỉ dùng ASCII.
Quy tắc bảng và cột
Phần tiêu đề “Quy tắc bảng và cột”| Quyết định | Mặc định đề xuất | Lý do |
|---|---|---|
| Bảng số ít hay số nhiều | Chọn một, ví dụ số nhiều orders |
Không có đáp án đúng; trộn lẫn mới là lỗi |
| Primary key | id |
Ngắn, ORM nào cũng hiểu |
| Foreign key | <bảng số ít>_id: customer_id |
Đọc join là biết trỏ đi đâu |
| Thời điểm | created_at timestamptz |
Hậu tố _at cho mốc thời gian; timestamptz lưu thời điểm tuyệt đối |
| Ngày không giờ | birth_date date, due_on date |
Phân biệt với mốc thời gian |
| Boolean | is_active, has_invoice |
Cùng quy tắc với code |
| Tiền | amount_cents bigint hoặc numeric |
Đơn vị trong tên, không dùng float |
| Từ khoá SQL | Tránh user, order, group làm tên bảng/cột |
Phải quote mỗi lần dùng |
Với mốc thời gian thật, PostgreSQL wiki khuyên dùng timestamptz thay cho timestamp không time zone (trừ timestamp trừu tượng không làm phép tính).
Tên constraint và index: biết mặc định, rồi viết tường minh
Phần tiêu đề “Tên constraint và index: biết mặc định, rồi viết tường minh”Không đặt tên thì PostgreSQL tự sinh theo mẫu <bảng>_<cột>_<hậu tố> (đọc từ source PG 17, đã chạy thử):
| Đối tượng | Hậu tố mặc định | Ví dụ với bảng orders |
|---|---|---|
| Primary key | pkey (không kèm cột) |
orders_pkey |
| Unique | key |
orders_external_ref_key |
| Foreign key | fkey |
orders_customer_id_fkey |
| Check (một cột) | check |
orders_amount_cents_check |
| Exclusion | excl |
orders_period_excl |
| Index thường | idx |
orders_customer_id_idx |
- Trùng tên: PG thêm số (
orders_customer_id_idx1). - Quá dài: PG cắt phần tên bảng/cột nhưng giữ nguyên hậu tố.
Mẫu mặc định này dễ đọc, nên dùng chính nó làm quy ước và viết tên ra trong migration. Migration DROP CONSTRAINT / DROP INDEX cần tên ổn định, không phụ thuộc PG đã cắt hay đánh số ra sao trên môi trường đó.
CREATE TABLE orders ( id bigint GENERATED ALWAYS AS IDENTITY, customer_id bigint NOT NULL, amount_cents bigint NOT NULL, created_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT orders_pkey PRIMARY KEY (id), CONSTRAINT orders_customer_id_fkey FOREIGN KEY (customer_id) REFERENCES customers (id), CONSTRAINT orders_amount_cents_check CHECK (amount_cents >= 0));
CREATE INDEX orders_customer_id_created_at_idx ON orders (customer_id, created_at);Before → after ở tầng database
Phần tiêu đề “Before → after ở tầng database”| Trước | Vấn đề | Sau |
|---|---|---|
tblOrderInfo |
Tiền tố kiểu, Info rỗng nghĩa |
orders |
"CreatedDate" timestamp |
Phải quote; “Date” nhưng có giờ; không có time zone | created_at timestamptz |
cust (cột FK) |
Viết tắt, không rõ trỏ đi đâu | customer_id |
Tiếp theo: API, event, env.