Tách làm hai việc: địa chỉ hiện tại của khách và địa chỉ dùng cho một đơn cụ thể.
create table addresses (
id bigserial primary key,
user_id bigint not null references users(id),
line1 text not null,
ward text, district text, city text,
is_default boolean not null default false,
archived_at timestamptz
);
create table orders (
id bigserial primary key,
user_id bigint not null,
address_id bigint references addresses(id), -- traceability
ship_line1 text not null, -- snapshot
ship_city text not null,
ship_phone text not null
);Đơn hàng snapshot địa chỉ vì nó mô tả nơi hàng đã thực sự được giao. Nếu chỉ join sang addresses, khách sửa địa chỉ là toàn bộ đơn cũ đổi theo, tra soát khiếu nại không còn cơ sở.
Với hồ sơ khách, không sửa đè bản ghi địa chỉ đang được tham chiếu: thay vì UPDATE, hãy chèn bản ghi mới và đánh dấu bản cũ archived_at. Đây chính là slowly changing dimension kiểu 2 — mỗi lần đổi tạo một dòng mới với khoảng hiệu lực, thay vì kiểu 1 là ghi đè và mất lịch sử.
Khi cần truy vấn "địa chỉ có hiệu lực tại thời điểm T", thêm cặp valid_from / valid_to:
select * from addresses
where user_id = $1 and $2 >= valid_from and ($2 < valid_to or valid_to is null);Cùng nguyên tắc áp cho mọi thứ "thay đổi nhưng lịch sử vẫn phải đúng": thuế suất, bảng giá, mức lương, hạng thành viên.