PostgreSQL 检查外键条件约束

问题描述 投票:0回答:4

我有一个用户表,例如:

create table "user" (
    id serial primary key,
    name text not null,
    superuser boolean not null default false
);

还有一张工作表:

create table job (
    id serial primary key,
    description text
);

可以将作业分配给用户,但仅限于超级用户。其他用户无法分配作业。

所以我有一个表,可以通过它查看哪个作业分配给哪个用户:

create table user_has_job (
    user_id integer references "user"(id),
    job_id integer references job(id),
    constraint user_has_job_pk PRIMARY KEY (user_id, job_id)
);

但我想创建一个检查约束,使

user_id
引用具有
user.superuser = True
的用户。

这可能吗?或者还有其他解决办法吗?

sql postgresql foreign-keys check-constraints
4个回答
22
投票

这适用于插入:

create or replace function is_superuser(int) returns boolean as $$
select superuser from "user" where id = $1;
$$ language sql;

然后对 user_has_job 表进行检查约束:

create table user_has_job (
    user_id integer references "user"(id),
    job_id integer references job(id),
    constraint user_has_job_pk PRIMARY KEY (user_id, job_id),
    constraint chk_is_superuser check (is_superuser(user_id))
);

适用于刀片:

postgres=# insert into "user" (name,superuser) values ('name1',false);
INSERT 0 1
postgres=# insert into "user" (name,superuser) values ('name2',true);
INSERT 0 1

postgres=# insert into job (description) values ('test');
INSERT 0 1
postgres=# insert into user_has_job (user_id,job_id) values (1,1);
ERROR:  new row for relation "user_has_job" violates check constraint "chk_is_superuser"
DETAIL:  Failing row contains (1, 1).
postgres=# insert into user_has_job (user_id,job_id) values (2,1);
INSERT 0 1

但是这是可能的:

postgres=# update "user" set superuser=false;
UPDATE 2

因此,如果您允许更新用户,则需要在用户表上创建更新触发器,以防止用户有工作时发生这种情况。


5
投票

我能想到的唯一方法是将

(id, superuser)
添加到
users
表中,并通过“复制”
user_has_job
标志来引用
superuser
表中的约束:

create table users (
    id serial primary key,
    name text not null,
    superuser boolean not null default false
);

-- as id is already unique there is no harm adding this additional 
-- unique constraint (from a business perspective)
alter table users add constraint uc_users unique (id, superuser);

create table job (
    id serial primary key,
    description text
);

create table user_has_job (
    user_id integer references users (id),
    -- we need a column in order to be able to reference the unique constraint in users
    -- the check constraint ensures we only reference superuser
    superuser boolean not null default true check (superuser), 
    job_id integer references job(id),
    constraint user_has_job_pk PRIMARY KEY (user_id, job_id),
    foreign key (user_id, superuser) references users (id, superuser)
);


insert into users 
 (id, name, superuser)
values 
  (1, 'arthur', false),
  (2, 'ford', true);

insert into job 
  (id, description)
values   
  (1, 'foo'),
  (2, 'bar');

由于

default
值的存在,您在插入
superuser
表时不必指定
user_has_job
列。所以以下插入有效:

insert into user_has_job 
  (user_id, job_id)
values
  (2, 1);

但是尝试将 Arthur 插入表中失败:

insert into user_has_job 
  (user_id, job_id)
values
  (1, 1);

这也可以防止将福特变成非超级用户。以下更新:

update users 
  set superuser = false 
where id = 2;

因错误而失败

错误:表“users”上的更新或删除违反了表“user_has_job”上的外键约束“user_has_job_user_id_fkey1”
详细信息:键(id,超级用户)=(2,t)仍然从表“user_has_job”中引用。


2
投票

创建一个继承自

superuser
表的单独
user
表:

CREATE TABLE "user" (
    id serial PRIMARY KEY,
    name text NOT NULL,
);

CREATE TABLE superuser () INHERITS ("user");

user_has_job
表可以引用
superuser
表:

CREATE TABLE user_has_job (
    user_id integer REFERENCES superuser (id),
    job_id integer REFERENCES job(id),
    PRIMARY KEY (user_id, job_id)
);

通过插入和删除根据需要在表之间移动用户:

WITH promoted_user AS (
    DELETE FROM "user" WHERE id = 1 RETURNING *
) INSERT INTO superuser (id, name) SELECT id, name FROM promoted_user;

0
投票

我不知道这是否是一个好方法,但似乎有效

    INSERT INTO user_has_job (user_id, job_id) VALUES (you_user_id, your_job_id)
    WHERE EXIST (
        SELECT * FROM user WHERE id=your_user_id AND superuser=true
    );
© www.soinside.com 2019 - 2024. All rights reserved.