我想创建一个表来记录用户之间的匹配。所有比赛都是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
。
答案 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
检查这个新的