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 SIMPLEstaat 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_nrtoevoegen naar elke gebruiker, en je kunt (dan) nog steeds elkeuser_roletag 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_employeekan alleentruezijn offalseen wordt gedwongen om het bestaan van eenemployee_nr. weer te geven door deCHECKbeperking. De trigger houdt de kolom automatisch gesynchroniseerd. U kuntfalseallow toestaan bovendien voor andere doeleinden met slechts kleine updates van het ontwerp. -
De regels voor
user_role.is_employeezijn iets anders:het moet waar zijn ifrole_name = 'employee'. Afgedwongen door eenCHECKbeperking en wordt automatisch opnieuw ingesteld door de trigger. Maar het is toegestaan omrole_namete wijzigen naar iets anders en tochis_employee. behouden . Niemand zei een gebruiker met eenemployee_nris 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 metCHECKen FK-beperkingen, die geen uitzonderingen toestaan. -
Terzijde:ik heb de kolom
is_employeeeerst in de beperkingUNIQUE (is_employee, users_id)met een reden .users_idwordt al behandeld in de PK, dus het kan hier de tweede plaats innemen:
DB associatieve entiteiten en indexering