sql >> Database >  >> RDS >> PostgreSQL

Beperkingen voor kruistabel in PostgreSQL

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 overeenkomende user.employee_nr . zijn eerst.

  • U kunt nog steeds een employee_nr toevoegen naar elke gebruiker, en je kunt (dan) nog steeds elke user_role tag taggen met is_employee , ongeacht de werkelijke role_name . Makkelijk te weigeren als dat nodig is, maar deze implementatie introduceert niet meer beperkingen dan vereist.

  • users.is_employee kan alleen true zijn of false en wordt gedwongen om het bestaan ​​van een employee_nr . weer te geven door de CHECK beperking. De trigger houdt de kolom automatisch gesynchroniseerd. U kunt false 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 if role_name = 'employee' . Afgedwongen door een CHECK beperking en wordt automatisch opnieuw ingesteld door de trigger. Maar het is toegestaan ​​om role_name te wijzigen naar iets anders en toch is_employee . behouden . Niemand zei een gebruiker met een employee_nr is vereist om een ​​overeenkomstig item te hebben in user_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 met CHECK en FK-beperkingen, die geen uitzonderingen toestaan.

  • Terzijde:ik heb de kolom is_employee eerst in de beperking UNIQUE (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



  1. SQL Server 2016:een weergave maken

  2. Meerdere tabellen retourneren vanuit een opgeslagen procedure

  3. Rijen zoeken die geen numerieke gegevens bevatten in Oracle

  4. Een module ontwikkelen met Java 9 in Eclipse IDE, deel 2