使用 PostgreSQL 9.2.4,我有一个表 users
与表的 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)
我要确保列
users.employee_number
不能是 NULL
如果有相关行,其中 user_roles.role_name
包含员工角色名称。也就是说,我希望数据库强制执行某些角色的约束,users.employee_number
必须有值(value),但不是为别人。我怎样才能做到这一点,最好没有用户定义的函数或触发器?我发现( blog post , SO Answer )SQL Server 支持索引 View ,这听起来很适合我的目的。但是,我认为物化 View 在我的情况下不起作用,因为它们不是动态更新的。
最佳答案
澄清
这个的配方要求 留下解释的空间:
哪里UserRole.role_name
包含员工角色名称。
我的解读:
在 UserRole
中有一个条目有 role_name = 'employee'
.
您的 命名约定 是有问题的(现在更新)。 User
是标准 SQL 和 Postgres 中的保留字。除非双引号,否则作为标识符是非法的——这是不明智的。用户合法名称,因此您不必双引号。
我在我的实现中使用了无故障标识符。
问题FOREIGN KEY
和 CHECK
约束是经过验证的、密封的工具,用于强制执行关系完整性。触发器是强大、有用和通用的功能,但更复杂、更不严格,并且有更多的设计错误和极端情况的空间。
你的情况很困难,因为 FK 约束一开始似乎是不可能的:它需要一个 PRIMARY KEY
或 UNIQUE
引用约束 - 都不允许 NULL 值。没有部分 FK 约束,由于默认 ,唯一可以逃避严格参照完整性的是引用列中的 NULL 值。 MATCH SIMPLE
FK 约束的行为。 Per documentation:
MATCH SIMPLE
allows any of the foreign key columns to be null; if any of them are null, the row is not required to have a match in the referenced table.
dba.SE 上的相关答案更多:
解决方法是引入一个 bool 标志
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)
);
就这样。
这些 触发器 是可选的,但为了方便设置添加的标签,建议使用
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();
再次,废话,优化,仅在需要时调用。
SQL Fiddle Postgres 9.3 的演示。应该与 Postgres 9.1+ 一起使用。
要点
user_role.role_name = 'employee'
,那么必须有一个匹配的 user.employee_nr
第一的。 employee_nr
给任何用户,您(然后)仍然可以标记任何 user_role
与 is_employee
, 不管实际 role_name
.如果需要,很容易禁止,但此实现不会引入比要求更多的限制。 users.is_employee
只能是 true
或 false
并被迫反射(reflect) employee_nr
的存在由 CHECK
约束。触发器自动保持列同步。您可以允许 false
另外用于其他目的,只需对设计进行少量更新。 user_role.is_employee
略有不同:如果 role_name = 'employee'
一定是真的.由 CHECK
强制执行约束并再次由触发器自动设置。但允许更改role_name
到别的东西,仍然保持is_employee
.没有人说有 employee_nr
的用户需要在 user_role
中有相应的条目,恰恰相反!同样,如果需要,易于另外执行。 How To Avoid Looping Trigger Calls In PostgreSQL 9.2.1
但是我们不必担心可能会违反规则,因为上述触发器只是为了方便起见。规则本身通过
CHECK
强制执行。和 FK 约束,不允许有任何异常(exception)。 is_employee
约束中的第一个 UNIQUE (is_employee, users_id)
因为某种原因。 users_id
已经包含在 PK 中,因此它可以在此处排在第二位:DB associative entities and indexing
关于postgresql - PostgreSQL 中的跨表约束,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29673536/