PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Ograniczenia tabel krzyżowych w PostgreSQL

Wyjaśnienia

Sformułowanie tego wymagania pozostawia miejsce na interpretację:
gdzie UserRole.role_name zawiera nazwę roli pracownika.

Moja interpretacja:
z wpisem w UserRole który ma role_name = 'employee' .

Twoja konwencja nazewnictwa jest był problematyczny (aktualizacja teraz). User jest słowem zastrzeżonym w standardowym SQL i Postgresie. Jako identyfikator jest niedozwolony, chyba że jest cytowany w podwójnym cudzysłowie - co byłoby nierozsądne. Oficjalne nazwy użytkowników, dzięki czemu nie trzeba wstawiać podwójnego cudzysłowu.

W mojej implementacji używam bezproblemowych identyfikatorów.

Problem

FOREIGN KEY i CHECK ograniczenia to sprawdzone, hermetyczne narzędzia do wymuszania integralności relacyjnej. Wyzwalacze to potężne, przydatne i wszechstronne funkcje, ale bardziej wyrafinowane, mniej rygorystyczne i z większą ilością miejsca na błędy projektowe i przypadki narożne.

Twoja sprawa jest trudna, ponieważ na początku ograniczenie FK wydaje się niemożliwe:wymaga PRIMARY KEY lub UNIQUE ograniczenie do odniesienia — żadne nie zezwala na wartości NULL. Nie ma częściowych ograniczeń FK, jedyną ucieczką od ścisłej spójności referencyjnej są wartości NULL w odwołaniach kolumny z powodu domyślnego MATCH SIMPLE zachowanie ograniczeń FK. Zgodnie z dokumentacją:

MATCH SIMPLE zezwala na wartość null dowolnej kolumny klucza obcego; jeśli którykolwiek z nich ma wartość null, wiersz nie musi mieć dopasowania w tabeli, do której się odwołuje.

Powiązana odpowiedź na dba.SE z więcej:

  • Dwukolumnowe ograniczenie klucza obcego tylko wtedy, gdy trzecia kolumna NIE JEST NULL

Obejściem tego problemu jest wprowadzenie flagi logicznej is_employee aby oznaczyć pracowników po obu stronach, zdefiniowano NOT NULL w users , ale może być NULL w user_role :

Rozwiązanie

To wymusza Twoje wymagania dokładnie , ograniczając do minimum hałas i koszty ogólne:

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

To wszystko.

Te wyzwalacze są opcjonalne, ale zalecane dla wygody, aby ustawić dodane tagi is_employee automatycznie i nie musisz nic robić dodatkowo:

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

Ponownie, bez sensu, zoptymalizowany i wywoływany tylko w razie potrzeby.

Skrzypce SQL demo dla Postgresa 9.3. Powinien działać z Postgresem 9.1+.

Główne punkty

  • Teraz, jeśli chcemy ustawić user_role.role_name = 'employee' , musi istnieć pasujący user.employee_nr pierwszy.

  • Nadal możesz dodać employee_nr do dowolnego użytkownika i nadal możesz (wtedy) otagować każdą user_role z is_employee , niezależnie od rzeczywistej role_name . Łatwe do odrzucenia, jeśli zajdzie taka potrzeba, ale ta implementacja nie wprowadza więcej ograniczeń niż jest to wymagane.

  • users.is_employee może być tylko true lub false i jest zmuszony do odzwierciedlenia istnienia employee_nr przez CHECK ograniczenie. Wyzwalacz automatycznie synchronizuje kolumnę. Możesz zezwolić na false dodatkowo do innych celów z niewielkimi aktualizacjami projektu.

  • Zasady dla user_role.is_employee są nieco inne:musi to być prawda, jeśli role_name = 'employee' . Wymuszone przez CHECK ograniczenie i ponownie ustawiane automatycznie przez wyzwalacz. Ale można zmienić role_name do czegoś innego i nadal zachowaj is_employee . Nikt nie powiedział, że użytkownik z employee_nr jest wymagane mieć odpowiedni wpis w user_role , tylko na odwrót! W razie potrzeby można je dodatkowo łatwo wyegzekwować.

  • Jeśli istnieją inne wyzwalacze, które mogą przeszkadzać, rozważ to:
    Jak uniknąć zapętlania wywołań wyzwalaczy w PostgreSQL 9.2.1
    Ale nie musimy się martwić, że reguły mogą zostać naruszone, ponieważ powyższe wyzwalacze są tylko dla wygody. Zasady same w sobie są egzekwowane za pomocą CHECK i ograniczenia FK, które nie dopuszczają wyjątków.

  • Na bok:umieszczam kolumnę is_employee najpierw w ograniczeniu UNIQUE (is_employee, users_id) z jakiegoś powodu . users_id jest już omówiony w PK, więc może zająć drugie miejsce tutaj:
    Jednostki asocjacyjne DB i indeksowanie



  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 GROUP_CONCAT() Odpowiednik

  2. Rails i PostgreSQL:rola postgres nie istnieje

  3. LIKE zapytanie dotyczące elementów płaskiej tablicy jsonb

  4. Jak dodać liczbę dni roboczych do podanej daty?

  5. PostgreSQL:Pokaż tabele w PostgreSQL