sql >> Databasteknik >  >> RDS >> PostgreSQL

Korstabellbegränsningar i PostgreSQL

Förtydliganden

Formuleringen av detta krav lämnar utrymme för tolkning:
där UserRole.role_name innehåller ett anställds rollnamn.

Min tolkning:
med en post i UserRole som har role_name = 'employee' .

Din namnkonvention är var problematisk (uppdaterad nu). User är ett reserverat ord i standard SQL och Postgres. Det är olagligt som identifierare såvida det inte är dubbelt citerat - vilket skulle vara felaktigt. Användarrättsliga namn så att du inte behöver dubbelcitera.

Jag använder problemfria identifierare i min implementering.

Problemet

FOREIGN KEY och CHECK begränsning är de beprövade, lufttäta verktygen för att upprätthålla relationsintegritet. Utlösare är kraftfulla, användbara och mångsidiga funktioner men mer sofistikerade, mindre strikta och med mer utrymme för designfel och hörnfall.

Ditt fall är svårt eftersom en FK-begränsning verkar omöjlig till en början:den kräver en PRIMARY KEY eller UNIQUE begränsning till referens - varken tillåter NULL-värden. Det finns inga partiella FK-begränsningar, den enda flykten från strikt referensintegritet är NULL-värden i referensen kolumner på grund av standardvärdet MATCH SIMPLE FK-begränsningarnas beteende. Per dokumentation:

MATCH SIMPLE tillåter vilken som helst av kolumnerna för främmande nyckel att vara null; om någon av dem är null krävs inte att raden matchar den refererade tabellen.

Relaterat svar på dba.SE med mer:

  • Tvåkolumns främmande nyckelbegränsning endast när den tredje kolumnen INTE är NULL

Lösningen är att introducera en boolesk flagga is_employee för att markera anställda på båda sidor, definierad NOT NULL i users , men får vara NULL i user_role :

Lösning

Detta upprätthåller dina krav exakt , samtidigt som buller och overhead hålls till ett 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 är allt.

Dessa utlösare är valfria men rekommenderas för enkelhets skull för att ställa in de tillagda taggarna is_employee automatiskt och du behöver inte göra något 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();

Återigen, no-nonsense, optimerad och bara anropad när det behövs.

SQL Fiddle demo för Postgres 9.3. Bör fungera med Postgres 9.1+.

Huvudpunkter

  • Om vi ​​nu vill ställa in user_role.role_name = 'employee' , då måste det finnas en matchande user.employee_nr först.

  • Du kan fortfarande lägga till en employee_nr till alla användare, och du kan (då) fortfarande tagga vilken user_role som helst med is_employee , oavsett det faktiska role_name . Lätt att tillåta om du behöver, men den här implementeringen introducerar inte fler begränsningar än vad som krävs.

  • users.is_employee kan bara vara true eller false och tvingas återspegla existensen av en employee_nr med CHECK begränsning. Utlösaren håller kolumnen automatiskt synkroniserad. Du kan tillåta false dessutom för andra ändamål med endast mindre uppdateringar av designen.

  • Reglerna för user_role.is_employee är något annorlunda:det måste vara sant om role_name = 'employee' . Tillämpas av en CHECK begränsning och ställs in automatiskt av utlösaren igen. Men det är tillåtet att ändra role_name till något annat och fortfarande behålla is_employee . Ingen sa en användare med en employee_nr är obligatoriskt att ha en motsvarande post i user_role , precis tvärtom! Återigen, lätt att genomdriva om det behövs.

  • Om det finns andra utlösare som kan störa, överväg detta:
    Hur man undviker att utlösa utlösare i PostgreSQL 9.2.1
    Men vi behöver inte oroa oss för att reglerna kan överträdas eftersom ovanstående utlösare bara är för bekvämlighets skull. Reglerna i sig tillämpas med CHECK och FK-begränsningar, som inte tillåter några undantag.

  • Åt sidan:Jag lägger kolumnen is_employee först i begränsningen UNIQUE (is_employee, users_id) av en anledning . users_id är redan täckt i PK, så det kan ta andra plats här:
    DB-associativa enheter och indexering



  1. Cloud Backup-alternativ för PostgreSQL

  2. Hur tar man bort ledande och efterföljande blanksteg i ett MySQL-fält?

  3. Skapa en SQL Server-databas med Azure Data Studio

  4. Förbättra vår onlinejobbportaldatamodell