sql >> Databáze >  >> RDS >> PostgreSQL

Omezení křížových tabulek v PostgreSQL

Vysvětlení

Formulace tohoto požadavku ponechává prostor pro interpretaci:
kde UserRole.role_name obsahuje název role zaměstnance.

Můj výklad:
se záznamem v UserRole který má role_name = 'employee' .

Vaše konvence pojmenování je byl problematický (nyní aktualizováno). User je vyhrazené slovo ve standardních SQL a Postgres. Je to nezákonné jako identifikátor, pokud není dvojitě uvozováno - což by bylo nerozumné. Oficiální jména uživatelů, abyste je nemuseli uvozovat.

Ve své implementaci používám bezproblémové identifikátory.

Problém

FOREIGN KEY a CHECK omezení jsou osvědčené, vzduchotěsné nástroje k posílení vztahové integrity. Spouště jsou výkonné, užitečné a všestranné funkce, ale jsou sofistikovanější, méně přísné a poskytují více prostoru pro chyby v návrhu a rohové pouzdra.

Váš případ je obtížný, protože omezení FK se zpočátku zdá nemožné:vyžaduje PRIMARY KEY nebo UNIQUE omezení na odkaz - ani jedno nepovoluje hodnoty NULL. Neexistují žádná částečná omezení FK, jediným únikem z přísné referenční integrity jsou hodnoty NULL v odkazování sloupců kvůli výchozímu nastavení MATCH SIMPLE chování FK omezení. Podle dokumentace:

MATCH SIMPLE umožňuje, aby kterýkoli ze sloupců cizího klíče byl null; pokud je některý z nich prázdný, řádek nemusí mít shodu v odkazované tabulce.

Související odpověď na dba.SE s dalšími:

  • Dvousloupcové omezení cizího klíče pouze v případě, že třetí sloupec NENÍ NULL

Řešením je zavedení booleovského příznaku is_employee pro označení zaměstnanců na obou stranách je definováno NOT NULL v users , ale může být NULL v user_role :

Řešení

To vynucuje vaše požadavky přesně , při zachování hluku a režie na minimu:

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)
);

To je vše.

Tyto spouštěče jsou volitelné, ale doporučují se pro usnadnění nastavení přidaných značek is_employee automaticky a vy nemusíte nic dělat navíc:

-- 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();

Opět bez nesmyslů, optimalizované a volané pouze v případě potřeby.

SQL Fiddle demo pro Postgres 9.3. Mělo by fungovat s Postgres 9.1+.

Hlavní body

  • Nyní, pokud chceme nastavit user_role.role_name = 'employee' , pak musí existovat odpovídající user.employee_nr první.

  • Stále můžete přidat employee_nr na libovolnou uživatele a můžete (pak) stále označit libovolnou user_role s is_employee , bez ohledu na skutečný role_name . V případě potřeby lze snadno zakázat, ale tato implementace nezavádí žádná další omezení, než je požadováno.

  • users.is_employee může být pouze true nebo false a je nucen odrážet existenci employee_nr pomocí CHECK omezení. Spouštěč automaticky udržuje sloupec v synchronizaci. Můžete povolit false navíc pro jiné účely s pouze drobnými úpravami designu.

  • Pravidla pro user_role.is_employee se mírně liší:musí to být pravda, pokud role_name = 'employee' . Vynuceno CHECK omezení a znovu se nastaví automaticky spouštěčem. Je však povoleno změnit role_name na něco jiného a stále si ponechte is_employee . Nikdo neřekl, že uživatel s employee_nr je povinné mít odpovídající záznam v user_role , právě naopak! Opět je snadné vynutit dodatečně v případě potřeby.

  • Pokud existují další spouštěče, které by mohly rušit, zvažte toto:
    Jak se vyhnout opakování spouštěcích volání v PostgreSQL 9.2.1
    Nemusíme se však obávat, že by mohla být porušena pravidla, protože výše uvedené spouštěče slouží pouze pro pohodlí. Pravidla jako taková jsou vynucována pomocí CHECK a FK omezení, která nepřipouštějí žádné výjimky.

  • Stranou:dal jsem sloupec is_employee první v omezení UNIQUE (is_employee, users_id) z nějakého důvodu . users_id je již zahrnuta v PK, takže zde může zaujmout druhé místo:
    Asociativní entity DB a indexování



  1. Použití Sysbenche ke generování testovacích dat pro sdílenou tabulku v MySQL

  2. Vrátit pouze číselné hodnoty v MariaDB

  3. Kurz transakcí SQL

  4. GROUP_CONCAT ORDER BY