簡體   English   中英

PostgreSQL檢查外鍵條件的約束

[英]PostgreSQL check constraint for foreign key condition

我有一個用戶表,例如:

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的用戶。

那可能嗎? 還是有其他解決方案?

這將適用於INSERTS:

create or replace function is_superuser(int) returns boolean as $$
select exists (
    select 1
    from "user"
    where id   = $1
      and superuser = true
);
$$ 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

因此,如果允許更新用戶,則需要在users表上創建一個更新觸發器,以防止該用戶有工作。

我能想到的唯一方法是在(id, superuser)上向users表添加唯一約束(id, superuser)並通過“復制” superuser標志從user_has_job表中引用該user_has_job

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值,因此在插入到user_has_job表中時不必指定superuser列。 因此,以下插入有效:

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

但是嘗試將亞瑟插入表中失敗:

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”
詳細信息:仍從表“ user_has_job”中引用鍵(id,superuser)=(2,t)。

創建一個從user表繼承的單獨的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;

我不知道這是否是一個好方法,但似乎可行

    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
    );

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM