PostgreSQL
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> PostgreSQL

Ràng buộc bảng chéo trong PostgreSQL

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 UserRolerole_name = 'employee' .

Quy ước đặt tên của bạn đã 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 KEYCHECK 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ới is_employee , bất thường của role_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ặc false và buộc phải phản ánh sự tồn tại của employee_nr bằng CHECK hạn chế. Trình kích hoạt giữ cột tự động đồng bộ hóa. Bạn có thể cho phép false 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ếu role_name = 'employee' . Thực thi bằng CHECK 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 đổi role_name sang thứ khác và vẫn giữ is_employee . Không ai nói người dùng có employee_nr bắt buộc để có một mục theo trong user_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ới CHECK 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ộc UNIQUE (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



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Làm cách nào để chuyển ứng dụng ray hiện có của tôi lên heroku? (sqlite đến postgres)

  2. PostgreSQL:So sánh chuỗi phân biệt chữ hoa chữ thường

  3. Thứ tự mặc định của danh sách được trả về từ lệnh gọi bộ lọc Django là gì?

  4. Tại sao postgres không sử dụng chỉ mục trong truy vấn của tôi

  5. Các chế độ xem PostgreSQL security_barrier hoạt động như thế nào?