如何创建具有唯一组合主键的postgres表?

时间:2015-11-14 05:05:35

标签: sql postgresql database-design ddl unique-constraint

我有两张名为players&的表格。 Postgres DB中的matches如下:

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表不允许插入的:

INSERT INTO matches VALUES (2, 1);

如果已有包含VALUES (1, 2)的行,请执行以下操作:

 winner | loser
--------+-------
      1 |     2

目标是避免在同一玩家之间输入比赛。

2 个答案:

答案 0 :(得分:4)

创建一个唯一索引:

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包装器只是为了方便一次输入参数,在某些情况下不是必需的。

答案 1 :(得分:1)

Postgres不支持对表达式的约束,因此我无法想到将此要求表达为约束的直接方式。但是,您可以做的一件事是更改表的结构,因此它有两列匹配的玩家(主键),一个约束,确保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)
);