PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Ограничения за кръстосани таблици в PostgreSQL

Пояснения

Формулирането на товаизискване оставя място за интерпретация:
където UserRole.role_name съдържа име на ролята на служител.

Моята интерпретация:
със запис в UserRole който има role_name = 'employee' .

Вашата условност за именуване е беше проблематично (актуализирано сега). User е запазена дума в стандартния SQL и Postgres. Незаконно е като идентификатор, освен ако не е в двойни кавички - което би било лошо. Юридически имена на потребители, така че да не се налага да цитирате двойни.

Използвам безпроблемни идентификатори в своята реализация.

Проблемът

FOREIGN KEY и CHECK ограниченията са доказаните, херметични инструменти за налагане на целостта на връзката. Тригерите са мощни, полезни и гъвкави функции, но по-сложни, по-малко строги и с повече място за грешки в дизайна и ъглови случаи.

Вашият случай е труден, защото FK ограничение изглежда невъзможно в началото:изисква PRIMARY KEY или UNIQUE ограничение за препратка - нито едно от двете не позволява NULL стойности. Няма частични FK ограничения, единственото избягване от строга референтна цялост са NULL стойности в препратката колони поради подразбиране MATCH SIMPLE поведение на FK ограниченията. По документация:

MATCH SIMPLE позволява всяка от колоните с външния ключ да бъде нула; ако някой от тях е нулев, не се изисква редът да има съвпадение в посочената таблица.

Свързан отговор на dba.SE с още:

  • Ограничение за външен ключ от две колони само когато третата колона НЕ е NULL

Заобиколното решение е да се въведе булев флаг is_employee за маркиране на служители от двете страни, дефинирани NOT NULL в users , но е позволено да бъде NULL в user_role :

Решение

Това налага вашите изисквания точно , като същевременно поддържате шума и режийните разходи до минимум:

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)
);

Това е всичко.

Тези задействания са незадължителни, но се препоръчват за удобство да зададете добавените тагове is_employee автоматично и не е нужно да правите нищо допълнително:

-- 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();

Отново, без глупости, оптимизиран и извикан само при нужда.

SQL Fiddle демо за Postgres 9.3. Трябва да работи с Postgres 9.1+.

Основни точки

  • Сега, ако искаме да зададем user_role.role_name = 'employee' , тогава трябва да има съответстващ user.employee_nr първо.

  • Все още можете да добавите employee_nr до всяко потребител и вие (след това) все още можете да маркирате всеки user_role с is_employee , независимо от действителното role_name . Лесно се забранява, ако е необходимо, но тази реализация не въвежда повече ограничения от необходимото.

  • users.is_employee може да бъде само true или false и е принуден да отразява съществуването на employee_nr чрез CHECK ограничение. Тригерът поддържа колоната в синхрон автоматично. Можете да разрешите false освен това за други цели само с незначителни актуализации на дизайна.

  • Правилата за user_role.is_employee са малко по-различни:трябва да е вярно, ако role_name = 'employee' . Наложено чрез CHECK ограничение и се задава автоматично от спусъка отново. Но е позволено да се промени role_name към нещо друго и пак запазвайте is_employee . Никой не е казал потребител с employee_nr е задължителен да има съответен запис в user_role , точно обратното! Отново, лесно да се наложи допълнително, ако е необходимо.

  • Ако има други тригери, които биха могли да попречат, помислете за това:
    Как да избегнете повторни извиквания на тригери в PostgreSQL 9.2.1
    Но не трябва да се притесняваме, че правилата може да бъдат нарушени, защото горните тригери са само за удобство. Правилата сами по себе си се прилагат с CHECK и FK ограничения, които не позволяват изключения.

  • Настрана:Сложих колоната is_employee първо в ограничението UNIQUE (is_employee, users_id) по причина . users_id вече е обхванат в PK, така че може да заеме второ място тук:
    Асоциативни обекти и индексиране на DB



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Често срещани грешки при мигриране на PostgreSQL бази данни от On-Prem към AWS RDS

  2. Как работи AT TIME ZONE в PostgreSQL

  3. GROUP BY и агрегирани последователни числови стойности

  4. Cloud9 постгрес

  5. Как да импортирам съществуващи *.sql файлове в PostgreSQL 8.4?