Verduidelijkingen
De formulering van deze eis laat ruimte voor interpretatie:
waar UserRole.role_name
bevat de naam van een werknemersrol.
Mijn interpretatie:
met een vermelding in UserRole
die role_name = 'employee'
. heeft .
Uw naamgevingsconventie is was problematisch (nu bijgewerkt). User
is een gereserveerd woord in standaard SQL en Postgres. Het is illegaal als identifier, tenzij dubbel geciteerd - wat onverstandig zou zijn. Wettelijke gebruikersnamen, zodat u niet dubbel hoeft te citeren.
Ik gebruik probleemloze identifiers in mijn implementatie.
Het probleem
FOREIGN KEY
en CHECK
constraint zijn de bewezen, waterdichte instrumenten om relationele integriteit af te dwingen. Triggers zijn krachtige, nuttige en veelzijdige functies, maar geavanceerder, minder streng en met meer ruimte voor ontwerpfouten en hoekgevallen.
Uw geval is moeilijk omdat een FK-beperking in eerste instantie onmogelijk lijkt:het vereist een PRIMARY KEY
of UNIQUE
beperking naar verwijzing - geen van beide staat NULL-waarden toe. Er zijn geen gedeeltelijke FK-beperkingen, de enige ontsnapping aan strikte referentiële integriteit zijn NULL-waarden in de verwijzing kolommen vanwege de standaard MATCH SIMPLE
gedrag van FK-beperkingen. Per documentatie:
MATCH SIMPLE
staat toe dat een van de vreemde-sleutelkolommen null is; als een van deze nul is, hoeft de rij niet overeen te komen in de tabel waarnaar wordt verwezen.
Gerelateerd antwoord op dba.SE met meer:
- Restrictie met twee kolommen voor refererende sleutels alleen als de derde kolom NIET NULL is
De oplossing is om een booleaanse vlag te introduceren is_employee
om werknemers aan beide zijden te markeren, gedefinieerd NOT NULL
in users
, maar mag NULL
zijn in user_role
:
Oplossing
Dit dwingt uw vereisten exact af , terwijl geluidsoverlast en overhead tot een minimum worden beperkt:
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)
);
Dat is alles.
Deze triggers zijn optioneel maar aanbevolen voor het gemak om de toegevoegde tags in te stellen is_employee
automatisch en u hoeft niets te doen extra:
-- 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();
Nogmaals, no-nonsense, geoptimaliseerd en alleen gebeld als dat nodig is.
SQL Fiddle demo voor Postgres 9.3. Zou moeten werken met Postgres 9.1+.
Belangrijkste punten
-
Als we nu
user_role.role_name = 'employee'
willen instellen , dan moet er een overeenkomendeuser.employee_nr
. zijn eerst. -
U kunt nog steeds een
employee_nr
toevoegen naar elke gebruiker, en je kunt (dan) nog steeds elkeuser_role
tag taggen metis_employee
, ongeacht de werkelijkerole_name
. Makkelijk te weigeren als dat nodig is, maar deze implementatie introduceert niet meer beperkingen dan vereist. -
users.is_employee
kan alleentrue
zijn offalse
en wordt gedwongen om het bestaan van eenemployee_nr
. weer te geven door deCHECK
beperking. De trigger houdt de kolom automatisch gesynchroniseerd. U kuntfalse
allow toestaan bovendien voor andere doeleinden met slechts kleine updates van het ontwerp. -
De regels voor
user_role.is_employee
zijn iets anders:het moet waar zijn ifrole_name = 'employee'
. Afgedwongen door eenCHECK
beperking en wordt automatisch opnieuw ingesteld door de trigger. Maar het is toegestaan omrole_name
te wijzigen naar iets anders en tochis_employee
. behouden . Niemand zei een gebruiker met eenemployee_nr
is vereist om een overeenkomstig item te hebben inuser_role
, net andersom! Nogmaals, gemakkelijk af te dwingen indien nodig. -
Als er andere triggers zijn die interfereren, overweeg dan het volgende:
Hoe u trigger-oproepen in PostgreSQL 9.2.1 in een lus kunt voorkomen
Maar we hoeven ons geen zorgen te maken dat regels worden geschonden, omdat de bovenstaande triggers alleen voor het gemak zijn. De regels op zich worden afgedwongen metCHECK
en FK-beperkingen, die geen uitzonderingen toestaan. -
Terzijde:ik heb de kolom
is_employee
eerst in de beperkingUNIQUE (is_employee, users_id)
met een reden .users_id
wordt al behandeld in de PK, dus het kan hier de tweede plaats innemen:
DB associatieve entiteiten en indexering