postgresql表约束不拒绝错误的插入

时间:2017-07-15 21:02:33

标签: sql postgresql

我想创建一个表来记录用户之间的匹配。所有比赛都是1v1,因此在比赛中总共有2个用户,其中1个必须是赢家,而另一个必须是输家,除非它是平局。

CREATE TABLE IF NOT EXISTS matches (
    match_id bigserial PRIMARY KEY,
    user_1_id bigint NOT NULL REFERENCES users(user_id),
    user_2_id bigint NOT NULL REFERENCES users(user_id),
    winner_id bigint REFERENCES users(user_id),
    loser_id bigint REFERENCES users(user_id),
    tied boolean,
    CONSTRAINT check_winner_loser_tied CHECK (
        (user_1_id != user_2_id) AND
        (
            (winner_id = user_1_id AND loser_id = user_2_id AND tied = FALSE) OR
            (winner_id = user_2_id AND loser_id = user_1_id AND tied = FALSE) OR
            (tied = TRUE AND winner_id = NULL AND loser_id = NULL)
        )
    )
);

正如您在上面所看到的,我添加了一个表格约束来强制执行上述条件,但是我仍然可以在表格中插入无效数据,例如:

INSERT INTO matches (user_1_id, user_2_id, winner_id, loser_id, tied)
VALUES (1, 2, 1, 2, TRUE); -- can't be winner & loser if it's a tie!

还有:

INSERT INTO matches (user_1_id, user_2_id, winner_id, loser_id, tied)
VALUES (1, 2, NULL, NULL, FALSE); -- must be a winner & loser if no tie!

我做错了什么?

更多信息,如果有用:SELECT version();会返回PostgreSQL 9.6.3 on x86_64-pc-linux-gnu, compiled by gcc (Debian 4.9.2-10) 4.9.2, 64-bit

1 个答案:

答案 0 :(得分:1)

你的约束包含一个错误:处理NULLS,你正在使用

(winner_id = user_1_id AND loser_id = user_2_id AND tied = FALSE) OR
(winner_id = user_2_id AND loser_id = user_1_id AND tied = FALSE) OR
(tied = TRUE AND winner_id = NULL AND loser_id = NULL)

何时使用

(winner_id IS NOT DISTINCT FROM user_1_id AND loser_id IS NOT DISTINCT FROM user_2_id AND tied = FALSE) OR
(winner_id IS NOT DISTINCT FROM user_2_id AND loser_id IS NOT DISTINCT FROM user_1_id AND tied = FALSE) OR
(tied = TRUE AND winner_id IS NULL AND loser_id IS NULL)

所有比较

(value = NULL) 
对于=运算符,

在SQL三态逻辑中返回NULL。

如果您只想使用两态逻辑,则使用IS NOT DISTINCT FROM比较谓词。

dbfiddle here

中查看所有内容

备选方案:如果您使用 semantinc命名并将它们缩小,则检查会提供更多信息。你可以使用,例如:

CONSTRAINT check_users_different 
    CHECK (user_1_id <> user_2_id),
CONSTRAINT check_when_tied_no_winner_and_no_loser
    CHECK (CASE WHEN tied 
               THEN winner_id IS NULL AND loser_id IS NULL
               ELSE true
           END),
CONSTRAINT check_when_not_tied_winner_not_null
    CHECK (CASE WHEN not tied
               THEN winner_id IS NOT NULL
               ELSE true
           END),
CONSTRAINT check_when_not_tied_loser_not_null
    CHECK (CASE WHEN not tied
               THEN loser_id IS NOT NULL
               ELSE true
           END),
CONSTRAINT check_when_not_tied_one_user_wins_the_other_loses
    CHECK (CASE WHEN not tied
               THEN (user_1_id = winner_id AND user_2_id = loser_id) OR
                    (user_1_id = loser_id  AND user_2_id = winner_id)
               ELSE true
           END)

(我知道:这更加冗长,只需使用OR或AND就可以简化CASE WHEN。我发现它写得更清楚了。)

然后你会得到更多信息错误:

INSERT INTO matches (user_1_id, user_2_id, winner_id, loser_id, tied)
VALUES (1, 2, 1, 2, TRUE); -- can't be winner & loser if it's a tie!
ERROR:  new row for relation "matches" violates check constraint "check_when_tied_no_winner_and_no_loser"
DETAIL:  Failing row contains (1, 1, 2, 1, 2, t).
INSERT INTO matches (user_1_id, user_2_id, winner_id, loser_id, tied)
VALUES (1, 2, NULL, NULL, FALSE); -- must be a winner & loser if no tie!
ERROR:  new row for relation "matches" violates check constraint "check_when_not_tied_loser_not_null"
DETAIL:  Failing row contains (2, 1, 2, null, null, f).

dbfiddle here

检查这个新的