如何创建具有唯一组合主键的 Postgres table?
How to create a Postgres table with unique combined primary key?
我在 Postgres 数据库中有两个名为 players
和 matches
的 table,如下所示:
CREATE TABLE players (
name text NOT NULL,
id serial PRIMARY KEY
);
CREATE TABLE matches (
winner int REFERENCES players (id),
loser int REFERENCES players (id),
-- to prevent rematch btw players
CONSTRAINT unique_matches
PRIMARY KEY (winner, loser)
);
如何确保只有 (winner, loser)
或 (loser, winner)
的唯一组合用于 matches
主键,以便 matches
table不允许插入:
INSERT INTO matches VALUES (2, 1);
如果它已经有包含 VALUES (1, 2)
的行,例如:
winner | loser
--------+-------
1 | 2
目标是避免相同玩家之间的比赛。
Postgres 不支持对表达式的约束,所以我想不出将此要求表达为约束的直接方法。不过,您可以做的一件事是更改 table 的结构,使其有两列用于比赛的球员(主键),这是一个确保 player1 始终具有两者中较小 id 的约束和一个额外的列来表明获胜者:
CREATE TABLE matches (
p1 int REFERENCES players (id),
p2 int REFERENCES players (id),
p1winner boolean,
CONSTRAINT matches_pk PRIMARY KEY (p1, p2),
CONSTRAINT matches_players_order CHECK (p1 < p2)
);
创建唯一索引:
CREATE UNIQUE INDEX matches_uni_idx ON matches
(greatest(winner, loser), least(winner, loser));
不能是 UNIQUE
or PRIMARY KEY
constraint,因为它们只适用于列,不适用于表达式。
您可以添加一个 serial
列作为 PK,但只有两个整数列,您的原始 PK 也非常有效(请参阅评论)。它会自动使两列 NOT NULL
。 (否则,添加 NOT NULL
约束。)
您还可以添加一个 CHECK
约束来排除玩家与自己对战:
CHECK (winner <> loser)
提示:要搜索一对 ID(您不知道谁赢了),请在查询中构建相同的表达式,然后将使用索引:
SELECT * FROM matches
WHERE greatest(winner, loser) = 3 -- the greater value, obviously
AND least(winner, loser) = 1;
如果您处理未知参数并且您不知道提前哪个更大:
WITH input AS (SELECT $id1 AS _id1, $id2 AS _id2) -- input once
SELECT * FROM matches, input
WHERE greatest(winner, loser) = greatest(_id1, _id2)
AND least(winner, loser) = least(_id1, _id2);
CTE 包装器只是为了方便输入一次参数,在某些情况下不是必需的。
我在 Postgres 数据库中有两个名为 players
和 matches
的 table,如下所示:
CREATE TABLE players (
name text NOT NULL,
id serial PRIMARY KEY
);
CREATE TABLE matches (
winner int REFERENCES players (id),
loser int REFERENCES players (id),
-- to prevent rematch btw players
CONSTRAINT unique_matches
PRIMARY KEY (winner, loser)
);
如何确保只有 (winner, loser)
或 (loser, winner)
的唯一组合用于 matches
主键,以便 matches
table不允许插入:
INSERT INTO matches VALUES (2, 1);
如果它已经有包含 VALUES (1, 2)
的行,例如:
winner | loser
--------+-------
1 | 2
目标是避免相同玩家之间的比赛。
Postgres 不支持对表达式的约束,所以我想不出将此要求表达为约束的直接方法。不过,您可以做的一件事是更改 table 的结构,使其有两列用于比赛的球员(主键),这是一个确保 player1 始终具有两者中较小 id 的约束和一个额外的列来表明获胜者:
CREATE TABLE matches (
p1 int REFERENCES players (id),
p2 int REFERENCES players (id),
p1winner boolean,
CONSTRAINT matches_pk PRIMARY KEY (p1, p2),
CONSTRAINT matches_players_order CHECK (p1 < p2)
);
创建唯一索引:
CREATE UNIQUE INDEX matches_uni_idx ON matches
(greatest(winner, loser), least(winner, loser));
不能是 UNIQUE
or PRIMARY KEY
constraint,因为它们只适用于列,不适用于表达式。
您可以添加一个 serial
列作为 PK,但只有两个整数列,您的原始 PK 也非常有效(请参阅评论)。它会自动使两列 NOT NULL
。 (否则,添加 NOT NULL
约束。)
您还可以添加一个 CHECK
约束来排除玩家与自己对战:
CHECK (winner <> loser)
提示:要搜索一对 ID(您不知道谁赢了),请在查询中构建相同的表达式,然后将使用索引:
SELECT * FROM matches
WHERE greatest(winner, loser) = 3 -- the greater value, obviously
AND least(winner, loser) = 1;
如果您处理未知参数并且您不知道提前哪个更大:
WITH input AS (SELECT $id1 AS _id1, $id2 AS _id2) -- input once
SELECT * FROM matches, input
WHERE greatest(winner, loser) = greatest(_id1, _id2)
AND least(winner, loser) = least(_id1, _id2);
CTE 包装器只是为了方便输入一次参数,在某些情况下不是必需的。