Bạn hoàn toàn không cần kích hoạt hoặc PL / pgSQL.
Bạn thậm chí không cần các DEFERRABLE
ràng buộc.
Và bạn không cần lưu trữ bất kỳ thông tin dư thừa.
Bao gồm ID của email hoạt động trong users
bảng, dẫn đến các tham chiếu lẫn nhau. Mọi người có thể nghĩ rằng chúng ta cần một DEFERRABLE
ràng buộc để giải quyết vấn đề trứng gà khi chèn người dùng và email đang hoạt động của mình, nhưng sử dụng CTE sửa đổi dữ liệu, chúng ta thậm chí không cần điều đó.
Điều này thực thi chính xác một email hoạt động cho mỗi người dùng mọi lúc:
CREATE TABLE users (
user_id serial PRIMARY KEY
, username text NOT NULL
, email_id int NOT NULL -- FK to active email, constraint added below
);
CREATE TABLE email (
email_id serial PRIMARY KEY
, user_id int NOT NULL REFERENCES users ON DELETE CASCADE ON UPDATE CASCADE
, email text NOT NULL
, CONSTRAINT email_fk_uni UNIQUE(user_id, email_id) -- for FK constraint below
);
ALTER TABLE users ADD CONSTRAINT active_email_fkey
FOREIGN KEY (user_id, email_id) REFERENCES email(user_id, email_id);
Xóa các NOT NULL
ràng buộc users.email_id
để làm cho nó "nhiều nhất là một email hoạt động". (Bạn vẫn có thể lưu trữ nhiều email cho mỗi người dùng, nhưng không ai trong số họ là "hoạt động".)
Bạn có thể thực hiện active_email_fkey
DEFERRABLE
để cho phép nhiều thời gian hơn (chèn người dùng và email vào các lệnh riêng biệt của cùng một giao dịch), nhưng điều đó không cần thiết .
Tôi đặt user_id
đầu tiên trong các UNIQUE
ràng buộc email_fk_uni
để tối ưu hóa phạm vi chỉ số. Chi tiết:
Chế độ xem tùy chọn:
CREATE VIEW user_with_active_email AS
SELECT * FROM users JOIN email USING (user_id, email_id);
Đây là cách bạn chèn người dùng mới với một email hoạt động (theo yêu cầu):
WITH new_data(username, email) AS (
VALUES
('usr1', 'abc@d.com') -- new users with *1* active email
, ('usr2', 'def3@d.com')
, ('usr3', 'ghi1@d.com')
)
, u AS (
INSERT INTO users(username, email_id)
SELECT n.username, nextval('email_email_id_seq'::regclass)
FROM new_data n
RETURNING *
)
INSERT INTO email(email_id, user_id, email)
SELECT u.email_id, u.user_id, n.email
FROM u
JOIN new_data n USING (username);
Khó khăn cụ thể là chúng tôi không có user_id
cũng không email_id
bắt đầu. Cả hai đều là số serial được cung cấp từ tương ứng SEQUENCE
. Nó không thể được giải quyết bằng một RETURNING
mệnh đề duy nhất (một vấn đề gà và trứng khác). Giải pháp là nextval()
như giải thích chi tiết trong câu trả lời liên kết dưới đây .
Nếu bạn không biết tên của chuỗi được đính kèm cho serial
cột, email.email_id
bạn có thể thay thế:
nextval('email_email_id_seq'::regclass)
với
nextval(pg_get_serial_sequence('email', 'email_id'))
Đây là cách bạn thêm một email "hoạt động" mới:
WITH e AS (
INSERT INTO email (user_id, email)
VALUES (3, 'new_active@d.com')
RETURNING *
)
UPDATE users u
SET email_id = e.email_id
FROM e
WHERE u.user_id = e.user_id;
Câu đố SQL.
Bạn có thể gói gọn các lệnh SQL trong các chức năng phía máy chủ nếu một số ORM có đầu óc đơn giản không đủ thông minh để đối phó với điều này.
Liên quan chặt chẽ, với lời giải thích phong phú:
Cũng liên quan:
Về DEFERRABLE
những hạn chế:
Giới thiệu nextval()
và pg_get_serial_sequence()
: