Afklaringer
Formuleringen af dette krav giver plads til fortolkning:
hvor UserRole.role_name indeholder et medarbejderrollenavn.
Min fortolkning:
med en indtastning i UserRole der har role_name = 'employee' .
Din navngivningskonvention er var problematisk (opdateret nu). User er et reserveret ord i standard SQL og Postgres. Det er ulovligt som identifikator, medmindre det er dobbelt-citeret - hvilket ville være urådigt. Bruger juridiske navne, så du ikke behøver at citere dobbelt.
Jeg bruger problemfrie identifikatorer i min implementering.
Problemet
FOREIGN KEY og CHECK constraint er de gennemprøvede, lufttætte værktøjer til at håndhæve relationel integritet. Udløsere er kraftfulde, nyttige og alsidige funktioner, men mere sofistikerede, mindre strenge og med mere plads til designfejl og hjørnekasser.
Din sag er vanskelig, fordi en FK-begrænsning umiddelbart virker umulig:den kræver en PRIMARY KEY eller UNIQUE begrænsning til reference - hverken tillader NULL-værdier. Der er ingen delvise FK-begrænsninger, den eneste undslippe fra streng referenceintegritet er NULL-værdier i henvisningen kolonner på grund af standarden MATCH SIMPLE opførsel af FK-begrænsninger. Per dokumentation:
MATCH SIMPLE tillader enhver af de fremmede nøglekolonner at være nul; hvis nogen af dem er nul, er rækken ikke forpligtet til at have en match i den refererede tabel.
Relateret svar på dba.SE med mere:
- Kun begrænsning af fremmednøgle med to kolonner, når tredje kolonne IKKE er NULL
Løsningen er at introducere et boolesk flag is_employee at markere medarbejdere på begge sider, defineret NOT NULL i users , men tilladt at være NULL i user_role :
Løsning
Dette håndhæver dine krav præcis , mens støj og overhead holdes på et minimum:
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)
);
Det er alt.
Disse triggere er valgfrie, men anbefales for nemheds skyld for at indstille de tilføjede tags is_employee automatisk, og du behøver ikke at gøre noget ekstra:
-- 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();
Igen, no-nonsense, optimeret og kun ringet op, når det er nødvendigt.
SQL Fiddle demo til Postgres 9.3. Bør fungere med Postgres 9.1+.
Vigtige punkter
-
Hvis vi nu vil indstille
user_role.role_name = 'employee', så skal der være en matchendeuser.employee_nrførst. -
Du kan stadig tilføje en
employee_nrtil enhver bruger, og du kan (så) stadig tagge enhveruser_rolemedis_employee, uanset det faktiskerole_name. Let at afvise, hvis du har brug for det, men denne implementering indfører ikke flere begrænsninger end nødvendigt. -
users.is_employeekan kun væretrueellerfalseog er tvunget til at afspejle eksistensen af enemployee_nrvedCHECKbegrænsning. Udløseren holder automatisk kolonnen synkroniseret. Du kan tilladefalsedesuden til andre formål med kun mindre opdateringer af designet. -
Reglerne for
user_role.is_employeeer lidt anderledes:det skal være sandt, hvisrole_name = 'employee'. Håndhæves af enCHECKbegrænsning og indstilles automatisk af udløseren igen. Men det er tilladt at ændrerole_nametil noget andet og stadig beholdeis_employee. Ingen sagde en bruger med enemployee_nrer påkrævet at have en tilsvarende post iuser_role, bare omvendt! Igen, let at håndhæve yderligere, hvis det er nødvendigt. -
Hvis der er andre triggere, der kan forstyrre, så overvej dette:
Sådan undgår du sløjfer af triggeropkald i PostgreSQL 9.2.1
Men vi behøver ikke bekymre os om, at reglerne kan blive overtrådt, fordi ovenstående triggere kun er for nemheds skyld. Reglerne i sig selv håndhæves medCHECKog FK-begrænsninger, som ikke tillader undtagelser. -
Til side:Jeg lægger kolonnen
is_employeeførst i begrænsningenUNIQUE (is_employee, users_id)af en grund .users_ider allerede dækket af PK, så den kan indtage andenpladsen her:
DB-associative entiteter og indeksering