Tách sản phẩm (thông tin marketing) khỏi biến thể (thứ thực sự bán được).
Áo thun là product; "áo thun đen size M" là variant, và variant mới là đơn vị có SKU, giá, tồn kho.
sql
create table products (
id bigserial primary key,
name text not null,
description text
);
create table variants (
id bigserial primary key,
product_id bigint not null references products(id),
sku text not null unique,
price numeric(12,2) not null,
attributes jsonb not null default '{}' -- {"size":"M","color":"black"}
);
create table cart_items (
cart_id bigint not null references carts(id),
variant_id bigint not null references variants(id),
quantity int not null check (quantity > 0),
primary key (cart_id, variant_id)
);Điểm mấu chốt: giỏ hàng và chi tiết đơn hàng luôn trỏ tới variant_id, không phải product_id — vì chỉ variant mới trả lời được "còn hàng không" và "giá bao nhiêu".
Với thuộc tính biến thể, hai cách phổ biến: jsonb (nhanh, linh hoạt, khó ràng buộc) hoặc bảng attributes + variant_attributes chuẩn hoá (query lọc theo màu/size gọn hơn). Shop nhỏ hoặc bộ thuộc tính hay thay đổi thì chọn jsonb; sàn cần bộ lọc mặt hàng thì chọn bảng chuẩn hoá.