PostgreSQL:限制行中没有重复的单元格

时间:2018-12-27 19:29:18

标签: sql postgresql

我有一张桌子lineup

CREATE TABLE IF NOT EXISTS lineup (
  match_id    INTEGER REFERENCES matches,
  pos_1       INTEGER REFERENCES players,
  pos_2       INTEGER REFERENCES players,
  pos_3       INTEGER REFERENCES players,
  pos_4       INTEGER REFERENCES players,
  pos_5       INTEGER REFERENCES players,
  pos_6       INTEGER REFERENCES players
);

用于存储排球比赛的阵容。您在球场上有六个职位。每个职位必须有一名球员。但是,所有玩家ID都必须不同。玩家不能同时在多个位置上。如何使用约束对此建模?

match_id, pos_1, pos_2, pos_3, pos_4, pos_5, pos_6
1, 10, 11, 12, 13, 14, 15 // ok
1, 10, 10, 12, 13, 14, 15 // not ok, pos_1 == pos_2
1, 10, 11, 12, 13, 14, 10 // not ok, pos_1 == pos_6
1, 10, 11, 12, 13, 14, 14 // not ok, pos_5 == pos_6

我想到了类似的东西

CONSTRAINT no_duplicate_players CHECK (pos_1 != pos_2 AND pos_1 != pos_3 ... AND pos_5 != pos_6)

那将是一个很长的约束,我想知道是否有更简单的事情。

谢谢, 米尔科

4 个答案:

答案 0 :(得分:3)

有效执行此操作的一种方法是通过对位置列进行标准化来进一步对表格进行标准化。

归一化表

CREATE TABLE IF NOT EXISTS lineup (
    match_id    INTEGER NOT NULL REFERENCES matches,
    position_id INTEGER NOT NULL REFERENCES positions,
    player_id   INTEGER NOT NULL REFERENCES players
    CONSTRAINT lineups_pk PRIMARY KEY (match_id, position_id, player_id)
);

位置表

您还需要一个职位表,例如:

CREATE TABLE IF NOT EXISTS positions (
    position_id INTEGER,
    position TEXT, -- or whatever your character type of choice is
    CONSTRAINT positions_pk PRIMARY KEY (position_id)
);

这种方法的优点是可以强制执行您要强制执行的约束,如果您确定需要添加有关位置,球员或比赛的更多信息,则将来可以灵活使用。 / p>

查看数据

然后您可以通过执行以下操作以所需的格式显示数据:

SELECT
      match_id
    , a.player_id AS pos_1
    , b.player_id AS pos_2
    , c.player_id AS pos_3
    , d.player_id AS pos_4
    , e.player_id AS pos_5
    , f.player_id AS pos_6
FROM (SELECT match_id, player_id FROM lineup WHERE position_id = 1) a
LEFT JOIN (SELECT match_id, player_id FROM lineup WHERE position_id = 2) b USING (match_id)
LEFT JOIN (SELECT match_id, player_id FROM lineup WHERE position_id = 3) c USING (match_id)
LEFT JOIN (SELECT match_id, player_id FROM lineup WHERE position_id = 4) d USING (match_id)
LEFT JOIN (SELECT match_id, player_id FROM lineup WHERE position_id = 5) e USING (match_id)
LEFT JOIN (SELECT match_id, player_id FROM lineup WHERE position_id = 6) f USING (match_id);

以这种方式设置它需要做更多的工作,但是一旦获得查询,就可以将其创建为视图,然后总会得到它,并且还从附加的规范化中受益。这种方法还使回答“ player_id 1进行了多少场比赛?”之类的问题变得更加容易。和“他们总是在同一位置玩吗?”


确保所有六个职位都已填补

最初,此答案未满足我们必须确保所有六个职位都得到填补的要求。不添加任何内容,这可以确保每个已枚举位置均已填补,但可以避免插入某个位置。有两种方法可以解决此问题:

  • 确保通过应用程序/插入技术插入

    • 优点:更快,更轻
    • 缺点:不会在数据库级别强制执行,可能会导致数据丢失
  • 确保通过数据库触发器插入/删除

    • 优点:在数据库级别执行,因此不可能(如果实施正确)缺少内容
    • 缺点:这里是触发器

DBA StackExchange具有questionanswer方面的出色知识,说明如何使用触发器来实现这些约束。

答案 1 :(得分:1)

要缩短查询长度,请使用NOT IN

    CONSTRAINT no_duplicate_players
    CHECK (pos_1 NOT IN(pos_2,pos_3,pos_4,pos_5)) -- n --similar for each column

或者更好的方法是将每个pos1..n都使用外键(类似于数据仓库模式中的事实表) 这样它们将引用您的主表。

答案 2 :(得分:1)

Impaler的想法正确,但您也想保证所有六个位置都有球员。为此,您还需要NOT NULL约束:

CREATE TABLE IF NOT EXISTS lineup (
  match_id    INTEGER REFERENCES matches,
  pos_1       INTEGER NOT NULL REFERENCES players,
  pos_2       INTEGER NOT NULL REFERENCES players,
  pos_3       INTEGER NOT NULL REFERENCES players,
  pos_4       INTEGER NOT NULL REFERENCES players,
  pos_5       INTEGER NOT NULL REFERENCES players,
  pos_6       INTEGER NOT NULL REFERENCES players,
  constraint chk_linup_pos
    check (pos_2 not in (pos_1) and
           pos_3 not in (pos_1, pos_2) and
           pos_4 not in (pos_1, pos_2, pos_3) and
           pos_5 not in (pos_1, pos_2, pos_3, pos_4) and
           pos_6 not in (pos_1, pos_2, pos_3, pos_4, pos_5) 
          );
);

尽管您可以规范化数据结构,但是当值跨越多行时,确实很难固定约束,例如“必须填充所有六个位置”。如果您真的想实现这一点,那么这种结构可能是最简单的机制。

答案 3 :(得分:0)

一个简单的约束即可:

CREATE TABLE IF NOT EXISTS lineup (
  match_id    INTEGER REFERENCES matches,
  pos_1       INTEGER REFERENCES players,
  pos_2       INTEGER REFERENCES players,
  pos_3       INTEGER REFERENCES players,
  pos_4       INTEGER REFERENCES players,
  pos_5       INTEGER REFERENCES players,
  pos_6       INTEGER REFERENCES players,
  constraint chk1
    check (pos_1 <> pos_2
       and pos_1 <> pos_3
       and pos_1 <> pos_4
       and pos_1 <> pos_5
       and pos_1 <> pos_6
       and pos_2 <> pos_3
       and pos_2 <> pos_4
       and pos_2 <> pos_5
       and pos_2 <> pos_6
       and pos_3 <> pos_4
       and pos_3 <> pos_5
       and pos_3 <> pos_6
       and pos_4 <> pos_5
       and pos_4 <> pos_6
       and pos_5 <> pos_6             
    )
);