Giải thích
Công thức của yêu cầu này chừa chỗ cho phần diễn giải:
where UserRole.role_name
chứa tên vai trò của nhân viên.
Diễn giải của tôi:
với một mục nhập trong UserRole
có role_name = 'employee'
.
Quy ước đặt tên của bạn là đã có vấn đề (được cập nhật ngay bây giờ). User
là một từ dành riêng trong SQL tiêu chuẩn và Postgres. Nó là bất hợp pháp với tư cách là số nhận dạng trừ khi được trích dẫn kép - điều này sẽ không được khuyến khích. Tên hợp pháp của người dùng để bạn không phải trích dẫn hai lần.
Tôi đang sử dụng số nhận dạng không gặp sự cố trong quá trình triển khai của mình.
Sự cố
FOREIGN KEY
và CHECK
ràng buộc là các công cụ chặt chẽ, đã được kiểm chứng để thực thi tính toàn vẹn của quan hệ. Bộ kích hoạt là các tính năng mạnh mẽ, hữu ích và linh hoạt nhưng phức tạp hơn, ít nghiêm ngặt hơn và có nhiều chỗ cho các lỗi thiết kế và trường hợp góc.
Trường hợp của bạn khó vì ràng buộc FK thoạt đầu có vẻ không thể thực hiện được:nó yêu cầu PRIMARY KEY
hoặc UNIQUE
ràng buộc tham chiếu - không cho phép giá trị NULL. Không có ràng buộc FK từng phần, cách duy nhất thoát khỏi tính toàn vẹn tham chiếu nghiêm ngặt là các giá trị NULL trong tham chiếu do MATCH SIMPLE
mặc định hành vi của các ràng buộc FK. Theo tài liệu:
MATCH SIMPLE
cho phép bất kỳ cột khóa ngoại nào được rỗng; nếu bất kỳ giá trị nào trong số chúng là rỗng, hàng không bắt buộc phải khớp trong bảng được tham chiếu.
Câu trả lời liên quan trên dba.SE với nhiều hơn:
- Ràng buộc khóa ngoại hai cột chỉ khi cột thứ ba KHÔNG ĐẦY ĐỦ
Cách giải quyết là giới thiệu cờ boolean is_employee
để đánh dấu nhân viên của cả hai bên, được xác định NOT NULL
trong users
, nhưng được phép là NULL
trong user_role
:
Giải pháp
Điều này thực thi các yêu cầu của bạn chính xác , đồng thời giữ tiếng ồn và chi phí ở mức tối thiểu:
CREATE TABLE users (
users_id serial PRIMARY KEY
, employee_nr int
, is_employee bool NOT NULL DEFAULT false
, CONSTRAINT role_employee CHECK (employee_nr IS NOT NULL = is_employee)
, UNIQUE (is_employee, users_id) -- required for FK (otherwise redundant)
);
CREATE TABLE user_role (
user_role_id serial PRIMARY KEY
, users_id int NOT NULL REFERENCES users
, role_name text NOT NULL
, is_employee bool CHECK(is_employee)
, CONSTRAINT role_employee
CHECK (role_name <> 'employee' OR is_employee IS TRUE)
, CONSTRAINT role_employee_requires_employee_nr_fk
FOREIGN KEY (is_employee, users_id) REFERENCES users(is_employee, users_id)
);
Đó là tất cả.
Các trình kích hoạt này là tùy chọn nhưng được khuyến nghị để thuận tiện cho việc đặt các thẻ đã thêm is_employee
tự động và bạn không phải làm bất cứ điều gì thêm:
-- users
CREATE OR REPLACE FUNCTION trg_users_insup_bef()
RETURNS trigger AS
$func$
BEGIN
NEW.is_employee = (NEW.employee_nr IS NOT NULL);
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
CREATE TRIGGER insup_bef
BEFORE INSERT OR UPDATE OF employee_nr ON users
FOR EACH ROW
EXECUTE PROCEDURE trg_users_insup_bef();
-- user_role
CREATE OR REPLACE FUNCTION trg_user_role_insup_bef()
RETURNS trigger AS
$func$
BEGIN
NEW.is_employee = true;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
CREATE TRIGGER insup_bef
BEFORE INSERT OR UPDATE OF role_name ON user_role
FOR EACH ROW
WHEN (NEW.role_name = 'employee')
EXECUTE PROCEDURE trg_user_role_insup_bef();
Một lần nữa, không vô nghĩa, được tối ưu hóa và chỉ được gọi khi cần thiết.
SQL Fiddle bản demo cho Postgres 9.3. Nên hoạt động với Postgres 9.1+.
Những điểm chính
-
Bây giờ, nếu chúng ta muốn đặt
user_role.role_name = 'employee'
thì phải cóuser.employee_nr
phù hợp đầu tiên. -
Bạn vẫn có thể thêm
employee_nr
đến bất kỳ người dùng và bạn có thể (sau đó) vẫn gắn thẻ bất kỳuser_role
nào vớiis_employee
, bất thường củarole_name
thực tế . Dễ dàng không cho phép nếu bạn cần, nhưng việc triển khai này không đưa ra bất kỳ hạn chế nào hơn yêu cầu. -
users.is_employee
chỉ có thể làtrue
hoặcfalse
và buộc phải phản ánh sự tồn tại củaemployee_nr
bằngCHECK
hạn chế. Trình kích hoạt giữ cột tự động đồng bộ hóa. Bạn có thể cho phépfalse
bổ sung cho các mục đích khác chỉ với những cập nhật nhỏ về thiết kế. -
Các quy tắc cho
user_role.is_employee
hơi khác:nó phải đúng nếurole_name = 'employee'
. Thực thi bằngCHECK
ràng buộc và thiết lập lại tự động bởi trình kích hoạt. Nhưng nó được phép thay đổirole_name
sang thứ khác và vẫn giữis_employee
. Không ai nói người dùng cóemployee_nr
là bắt buộc để có một mục theo tronguser_role
, chỉ là con đường khác! Một lần nữa, dễ dàng thực thi bổ sung nếu cần. -
Nếu có các trình kích hoạt khác có thể gây trở ngại, hãy xem xét điều này:
Cách tránh các cuộc gọi kích hoạt vòng lặp trong PostgreSQL 9.2.1
Nhưng chúng ta không cần lo lắng rằng các quy tắc có thể bị vi phạm vì các trình kích hoạt trên chỉ nhằm mục đích thuận tiện. Các quy tắc được thực thi vớiCHECK
và các ràng buộc FK, không cho phép ngoại lệ. -
Bên cạnh:Tôi đặt cột
is_employee
đầu tiên trong ràng buộcUNIQUE (is_employee, users_id)
vì một lý do .users_id
đã được đề cập trong PK, vì vậy nó có thể chiếm vị trí thứ hai ở đây:
Các thực thể liên kết DB và lập chỉ mục