使用PostgreSQL 9.2.4,我有一个表users
,与表user_roles
的关系为1:很多。 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
必须具有值,但不能用于其他角色。
如何实现这一目标,最好不使用用户定义的函数或触发器?我发现(blog post,SO Answer)SQL Server支持索引视图,这听起来像是为了我的目的。但是,我认为物化视图在我的情况下不起作用,因为它们不是动态更新的。
答案 0 :(得分:3)
要求的表述为解释留下了空间:
其中UserRole.role_name
包含员工角色名称。
我的解释:
,UserRole
中的条目有role_name = 'employee'
。
您的命名惯例 存在问题(现在更新)。 User
是标准SQL和Postgres中的保留字。除非双重引用,否则它作为标识符是非法的 - 这是不明智的。用户法律名称,因此您不必加倍引用。
我在实现中使用了无故障标识符。
FOREIGN KEY
和CHECK
约束是经过验证的,不透气的工具,可以强制执行关系完整性。触发器功能强大,实用且功能多样,但更复杂,不那么严格,并且设计错误和边角情况有更多空间。
你的情况很难,因为一开始似乎不可能有FK约束:它需要PRIMARY KEY
或UNIQUE
约束来引用 - 既不允许NULL值。没有部分FK约束,由于FK约束的默认 MATCH SIMPLE
行为,引用列中唯一逃避严格参照完整性的是NULL值。 Per documentation:
MATCH SIMPLE
允许任何外键列为null;如果有的话 如果它们为null,则该行不需要在引用的表中具有匹配项。
关于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)
);
这就是全部。
这些触发器是可选的,但为方便起见,我们建议您自动设置添加的代码is_employee
,而不必另外任何 <:> < / p>
-- 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();
同样,没有废话,经过优化,只在需要时才被调用。
Postgres 9.3的SQL Fiddle演示。应该使用Postgres 9.1 +。
现在,如果我们要设置user_role.role_name = 'employee'
,则必须首先匹配user.employee_nr
。
您仍然可以向任何用户添加employee_nr
,然后您可以(然后)使用user_role
标记任何is_employee
,无论如何实际的role_name
。如果需要,很容易禁止,但此实现不会引入任何超出要求的限制。
users.is_employee
只能是true
或false
,并且被强制反映employee_nr
存在CHECK
1}}约束。触发器使列自动保持同步。您可以将false
另外用于其他目的,只需对设计进行少量更新。
user_role.is_employee
的规则略有不同:如果role_name = 'employee'
,则必须为true。由CHECK
约束强制执行并再次由触发器自动设置。但是,它允许将role_name
更改为其他内容并仍保留is_employee
。没有人说employee_nr
的用户需要 才能在user_role
中获得相应的条目,反之亦然!同样,如果需要,还可以轻松执行。
如果有其他触发器可能会产生干扰,请考虑以下事项:
How To Avoid Looping Trigger Calls In PostgreSQL 9.2.1
但我们不必担心规则可能会被违反,因为上述触发仅仅是为了方便。规则本身是使用CHECK
和FK约束强制执行的,这些约束不允许例外。
除此之外:我将列is_employee
放在约束UNIQUE (is_employee, users_id)
中的第一个。 PK中已涵盖users_id
,因此它可以在这里取得第二名:
DB associative entities and indexing
答案 1 :(得分:1)
首先,您可以使用触发器解决此问题。
但是,我认为你可以使用约束来解决这个问题,只是有点奇怪:
create table UserRoles (
UserRoleId int not null primary key,
. . .
NeedsEmployeeNumber boolean not null,
. . .
);
create table Users (
. . .
UserRoleId int,
NeedsEmployeeNumber boolean,
EmployeeNumber,
foreign key (UserRoleId, NeedsEmployeeNumber) references UserRoles(UserRoleId, NeedsEmployeeNumber),
check ((NeedsEmployeeNumber and EmployeeNumber is not null) or
(not NeedsEmployeeNumber and EmployeeNumber is null)
)
);
这应该有效,但这是一个尴尬的解决方案:
EmployeeNumber
。答案 2 :(得分:0)
新答案:
这个(SQL Sub queries in check constraint)似乎回答了你的问题,语言仍在9.4文档中(http://www.postgresql.org/docs/9.4/interactive/sql-createtable.html)。
旧答案:
SELECT
User.*
, UserRole1.*
FROM
User
LEFT JOIN UserRole UserRole1
ON User.id = UserRole1.UserId
AND (
(
User.employee_number IS NOT NULL AND UserRole1.role_name IN (enumerate employee role names here)
)
OR
(User.employee_number IS NULL)
)
以上查询选择User
中的所有字段以及UserRole
中的所有字段(别名为UserRole1)。我假设两个字段之间的关键字段称为User.id和U serRole1.UserId
,请将这些字段更改为实际值。
在查询的JOIN
部分,左侧有一个OR,要求用户表中的员工编号不是NULL
,而 UserRole1.role_name在列表中您必须向IN ()
运营商提供。
JOIN
的正确部分恰恰相反,它要求User.employee_number
为NULL
(这应该是您的非员工集)。
如果您需要更精确的解决方案,请提供有关您的桌面结构的更多详细信息以及必须为员工选择的角色。