sql >> Database teknologi >  >> RDS >> PostgreSQL

Krydstabel-begrænsninger i PostgreSQL

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 matchende user.employee_nr først.

  • Du kan stadig tilføje en employee_nr til enhver bruger, og du kan (så) stadig tagge enhver user_role med is_employee , uanset det faktiske role_name . Let at afvise, hvis du har brug for det, men denne implementering indfører ikke flere begrænsninger end nødvendigt.

  • users.is_employee kan kun være true eller false og er tvunget til at afspejle eksistensen af ​​en employee_nr ved CHECK begrænsning. Udløseren holder automatisk kolonnen synkroniseret. Du kan tillade false desuden til andre formål med kun mindre opdateringer af designet.

  • Reglerne for user_role.is_employee er lidt anderledes:det skal være sandt, hvis role_name = 'employee' . Håndhæves af en CHECK begrænsning og indstilles automatisk af udløseren igen. Men det er tilladt at ændre role_name til noget andet og stadig beholde is_employee . Ingen sagde en bruger med en employee_nr er påkrævet at have en tilsvarende post i user_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 med CHECK og FK-begrænsninger, som ikke tillader undtagelser.

  • Til side:Jeg lægger kolonnen is_employee først i begrænsningen UNIQUE (is_employee, users_id) af en grund . users_id er allerede dækket af PK, så den kan indtage andenpladsen her:
    DB-associative entiteter og indeksering



  1. `pg_tblspc` mangler efter installation af seneste version af OS X (Yosemite eller El Capitan)

  2. Hvordan kan jeg skrive SQL i Oracle i mit tilfælde?

  3. Oracle 12.2 Sharding

  4. Hvordan opretter man en Oracle-sekvens, der starter med maks. værdi fra en tabel?