Dag*_*ahl 3 postgresql triggers database-design referential-integrity constraints
使用 PostgreSQL 9.2.4,我有一个users与table有 1:many 关系的表user_roles。该users表存储员工和其他类型的用户。
Table "public.users"
Column | Type | Modifiers
-----------------+-------------------+-----------------------------------------------------
uid | integer | not null default nextval('users_uid_seq'::regclass)
employee_number | character varying |
name | character varying |
Indexes:
"users_pkey" PRIMARY KEY, btree (uid)
Referenced by:
TABLE "user_roles" CONSTRAINT "user_roles_uid_fkey" FOREIGN KEY (uid) REFERENCES users(uid)
Table "public.user_roles"
Column | Type | Modifiers
-----------+-------------------+------------------------------------------------------------------
id | integer | not null default nextval('user_roles_id_seq'::regclass)
uid | integer |
role | character varying | not null
Indexes:
"user_roles_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
"user_roles_uid_fkey" FOREIGN KEY (uid) REFERENCES users(uid)
Run Code Online (Sandbox Code Playgroud)
如果有包含员工角色名称的相关行,我想确保该列users.employee_number不能NULL存在user_roles.role_name。也就是说,我希望数据库强制执行约束,即对于某些角色,users.employee_number必须具有值,但对于其他角色则没有。
我怎样才能做到这一点,最好没有用户定义的函数或触发器?我发现(博客文章,SO Answer)SQL Server 支持索引视图,这听起来很适合我的目的。但是,我认为物化视图在我的情况下不起作用,因为它们不是动态更新的。
此要求的制定留下了解释空间:
whereUserRole.role_name包含员工角色名称。
我的解释:
有一个条目UserRole有role_name = 'employee'.
您的命名约定 有问题(现已更新)。User是标准 SQL 和 Postgres 中的保留字。除非双引号,否则作为标识符是非法的——这是不明智的。用户合法名称,因此您不必双引号。
我在我的实现中使用了无故障标识符。
FOREIGN KEY和CHECK约束是经过验证的、密封的工具,用于强制执行关系完整性。触发器是强大、有用和通用的功能,但更复杂、更不严格,并且有更多的设计错误和极端情况的空间。
您的情况很困难,因为 FK 约束一开始似乎是不可能的:它需要一个PRIMARY KEYorUNIQUE约束来引用 - 两者都不允许 NULL 值。没有部分 FK 约束,由于FK 约束的默认行为,严格参照完整性的唯一逃避是引用列中的 NULL 值MATCH SIMPLE。根据文档:
MATCH SIMPLE允许任何外键列为空;如果它们中的任何一个为空,则该行不需要在引用的表中具有匹配项。
dba.SE 上的相关答案更多:
解决办法是引入一个布尔标志is_employee来标记员工两侧,定义NOT NULL中users,但允许为NULL在user_role:
这完全符合您的要求,同时将噪音和开销降至最低:
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)
);
Run Code Online (Sandbox Code Playgroud)
就这样。
这些触发器是可选的,但为了方便起见,建议is_employee您自动设置添加的标签,您无需执行任何额外操作:
-- 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();
Run Code Online (Sandbox Code Playgroud)
再次,废话,优化,仅在需要时调用。
Postgres 9.3 的SQL Fiddle演示。应该与 Postgres 9.1+ 一起使用。
现在,如果我们想设置user_role.role_name = 'employee',那么user.employee_nr首先必须有一个匹配。
您还可以添加employee_nr到任何用户,并且你可以(当时)还是任何标记user_role与is_employee实际的,irregardless role_name。如果需要,很容易禁止,但此实现不会引入比要求更多的限制。
users.is_employee只能是trueorfalse并且被强制反映一个employee_nr受CHECK约束的存在。触发器自动保持列同步。您可以允许false其他目的,只需对设计进行少量更新。
的规则user_role.is_employee略有不同:如果 必须为真role_name = 'employee'。由CHECK约束强制执行并再次由触发器自动设置。但是允许更改role_name为其他内容并仍然保留is_employee. 没有人说有一个用户employee_nr被要求有在根据条目user_role,只是另一种方式圆!同样,如果需要,易于另外执行。
如果还有其他触发器可能会干扰,请考虑:
如何避免在 PostgreSQL 9.2.1 中循环触发器调用
但是我们不必担心可能会违反规则,因为上述触发器只是为了方便起见。规则本身是由CHECKFK 和 FK 约束强制执行的,不允许有任何例外。
旁白:出于某种原因,我将列is_employee放在约束UNIQUE (is_employee, users_id) 中的首位。users_idPK 中已经涵盖,因此它可以在此处排在第二位:
DB 关联实体和索引