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 SIMPLEumožň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_nrprvní. -
Stále můžete přidat
employee_nrna libovolnou uživatele a můžete (pak) stále označit libovolnouuser_rolesis_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_employeemůže být pouzetruenebofalsea je nucen odrážet existenciemployee_nrpomocíCHECKomezení. Spouštěč automaticky udržuje sloupec v synchronizaci. Můžete povolitfalsenavíc pro jiné účely s pouze drobnými úpravami designu. -
Pravidla pro
user_role.is_employeese mírně liší:musí to být pravda, pokudrole_name = 'employee'. VynucenoCHECKomezení a znovu se nastaví automaticky spouštěčem. Je však povoleno změnitrole_namena něco jiného a stále si ponechteis_employee. Nikdo neřekl, že uživatel semployee_nrje povinné mít odpovídající záznam vuser_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íCHECKa FK omezení, která nepřipouštějí žádné výjimky. -
Stranou:dal jsem sloupec
is_employeeprvní v omezeníUNIQUE (is_employee, users_id)z nějakého důvodu .users_idje již zahrnuta v PK, takže zde může zaujmout druhé místo:
Asociativní entity DB a indexování