Thông Báo Lỗi
Bạn thực thi lệnh INSERT hoặc UPDATE và PostgreSQL dừng lại ngay lập tức:
ERROR: null value in column "user_id" of relation "orders" violates not-null constraint
DETAIL: Failing row contains (null, 101, 2024-01-15).
Thông báo lỗi rõ ràng và hữu ích. Bạn đã cố ghi NULL vào cột user_id, trong khi cột này có ràng buộc NOT NULL — nó yêu cầu một giá trị thực, mọi lúc mọi nơi, không có ngoại lệ.
Nguyên Nhân Gây Ra Lỗi
Có một số tình huống dẫn đến lỗi này. Phổ biến nhất là:
- Câu lệnh
INSERTbỏ qua cột hoàn toàn và không cóDEFAULTnào được định nghĩa — PostgreSQL không có giá trị dự phòng. - Code ứng dụng truyền thẳng
None/null/undefinedvào câu truy vấn mà không có bất kỳ bước kiểm tra nào trước đó. - Ai đó đã thêm ràng buộc
NOT NULLvào một cột đã tồn tại, nhưng code cũ trong ứng dụng không biết điều này và vẫn bỏ qua trường đó. - Model ORM hoặc script migration không còn đồng bộ với schema thực tế của bảng.
Bước 1 — Kiểm Tra Schema Bảng
Trước khi làm bất cứ điều gì, hãy xác nhận những cột nào thực sự có ràng buộc NOT NULL. Trong psql:
\d orders
Hoặc dùng câu truy vấn này — hoạt động trên mọi client, kể cả pgAdmin và DBeaver:
SELECT column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_name = 'orders'
ORDER BY ordinal_position;
Tập trung vào các dòng có is_nullable = 'NO' và column_default trống. Những cột đó cần một giá trị tường minh trong mỗi lệnh insert — không có giá trị mặc định nào để cứu bạn.
Bước 2 — Tìm Lỗi Trong Câu Lệnh
Đặt câu lệnh INSERT của bạn cạnh kết quả schema. Hai dạng phổ biến gây ra hầu hết các lỗi này.
Dạng thứ nhất: bỏ qua cột hoàn toàn:
-- user_id bị thiếu — PostgreSQL nhận NULL cho cột này
INSERT INTO orders (product_id, order_date)
VALUES (101, '2024-01-15');
Dạng thứ hai: truyền NULL một cách tường minh:
INSERT INTO orders (user_id, product_id, order_date)
VALUES (NULL, 101, '2024-01-15'); -- bị từ chối ngay lập tức
Dù theo cách nào, PostgreSQL đều thấy NULL được ghi vào một cột không chấp nhận nó.
Bước 3 — Chọn Cách Khắc Phục
Phương án A: Cung cấp giá trị còn thiếu (xử lý ~80% trường hợp)
Chỉ cần đưa cột bắt buộc vào với một giá trị thực:
INSERT INTO orders (user_id, product_id, order_date)
VALUES (42, 101, '2024-01-15');
Khi insert từ code ứng dụng, hãy kiểm tra trước khi chạy truy vấn — không phải sau:
# Ví dụ Python
if user_id is None:
raise ValueError("user_id is required before inserting an order")
cursor.execute(
"INSERT INTO orders (user_id, product_id, order_date) VALUES (%s, %s, %s)",
(user_id, product_id, order_date)
)
Phương án B: Thêm DEFAULT cho cột
Bạn có một giá trị dự phòng hợp lý? Ví dụ, các đơn hàng hệ thống tự động có thể luôn thuộc về user 0. Thiết lập một lần trên cột:
ALTER TABLE orders
ALTER COLUMN user_id SET DEFAULT 0;
Từ đây, bất kỳ lệnh INSERT nào bỏ qua user_id sẽ tự động dùng 0 thay vì báo lỗi.
Phương án C: Xóa ràng buộc NOT NULL (chỉ khi NULL thực sự có ý nghĩa)
Thanh toán khách vãng lai là ví dụ thực tế — một đơn hàng có thể hợp lệ khi không có user nào liên kết. Nếu đó là trường hợp của bạn:
ALTER TABLE orders
ALTER COLUMN user_id DROP NOT NULL;
Đừng làm điều này chỉ để tắt lỗi. Chỉ thực hiện khi NULL mang ý nghĩa thực sự trong mô hình dữ liệu của bạn.
Phương án D: Điền dữ liệu cho các dòng cũ trước khi áp dụng NOT NULL
Thêm cột NOT NULL mới vào bảng đã có dữ liệu? Các dòng hiện có sẽ có giá trị NULL và chặn migration. Hãy điền dữ liệu trước, áp ràng buộc sau:
-- Bước 1: thêm cột, tạm thời cho phép NULL
ALTER TABLE orders ADD COLUMN user_id INTEGER;
-- Bước 2: điền giá trị thực cho tất cả dòng hiện có
UPDATE orders SET user_id = 0 WHERE user_id IS NULL;
-- Bước 3: khóa ràng buộc lại
ALTER TABLE orders ALTER COLUMN user_id SET NOT NULL;
Bỏ qua Bước 2 là lỗi migration phổ biến nhất dẫn đến lỗi này.
Bước 4 — Xác Nhận Kết Quả
Chạy lại câu lệnh gốc với RETURNING * ở cuối:
INSERT INTO orders (user_id, product_id, order_date)
VALUES (42, 101, '2024-01-15')
RETURNING *;
PostgreSQL trả về dòng vừa insert khi thành công. Không có lỗi nghĩa là ràng buộc đã được thỏa mãn. Kiểm tra lại dữ liệu đã được lưu đúng chưa:
SELECT * FROM orders WHERE product_id = 101 ORDER BY order_date DESC LIMIT 5;
Phòng Tránh Lỗi Về Sau
- Kiểm tra trước khi truy vấn, không phải bên trong nó — từ chối
None/nullngay tại tầng ứng dụng. Khi một giá trị đến được database, nó đã phải sạch rồi. - Luôn dùng
RETURNINGtrong lệnh insert — nó xác nhận việc ghi thành công và hiển thị chính xác những gì đã được lưu. Bảo hiểm rẻ mà hiệu quả. - Điền dữ liệu trước khi áp ràng buộc — thêm
NOT NULLvào bảng đang hoạt động mà không có giá trị mặc định sẽ thất bại ngay nếu có dòng nào đang cóNULLở đó. Migration ba bước ở trên là cách an toàn. - Giữ ORM model đồng bộ — sau mỗi migration, hãy xác nhận rằng các model SQLAlchemy, Django ORM hoặc Prisma phản ánh đúng schema thực tế. Sự lệch lạc giữa model và bảng là nguồn gốc của nhiều lỗi âm thầm.
- Đọc kỹ thông báo lỗi — PostgreSQL cho bạn biết tên cột (
"user_id") và tên bảng ("orders") ngay trong thông báo lỗi. Hãy đi thẳng vào cột đó thay vì dò tìm toàn bộ schema.

