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 libovolnouuser_role
sis_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 pouzetrue
nebofalse
a je nucen odrážet existenciemployee_nr
pomocíCHECK
omezení. Spouštěč automaticky udržuje sloupec v synchronizaci. Můžete povolitfalse
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, pokudrole_name = 'employee'
. VynucenoCHECK
omezení a znovu se nastaví automaticky spouštěčem. Je však povoleno změnitrole_name
na něco jiného a stále si ponechteis_employee
. Nikdo neřekl, že uživatel semployee_nr
je 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í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í