具有多对多限制约束的外键

时间:2014-06-27 03:27:15

标签: sql database postgresql foreign-keys

我在搜索字词时遇到问题的快速提问:

假设我与球员和球队之间存在多对多的关系:

CREATE TABLE players (
  id bigserial primary key,
  name text not null
);

CREATE TABLE teams (
  id bigserial primary key,
  name text not null,
  team_captain_id bigint not null references players(id)
);

CREATE TABLE team_players (
  id bigserial primary key,
  player_id bigint not null,
  team_id bigint not null
);

ALTER TABLE team_players ADD CONSTRAINT uq_team_players UNIQUE (player_id,team_id);

现在,每支球队都需要有一名队长,他也是一名球员。但我想强制说团队队长也是该团队的成员(或者,在语义上相同,团队队长在连接表中不是冗余的)

有没有标准的方法对此进行建模?我可以想到几种实际完成工作的方法,但我想知道是否有一种标准的,优雅的方式。

谢谢!

编辑:虽然让队长成为必填项目会很好,但我也满足于以下条件:如果团队至少有1名成员,那么就为其定义了队长。

编辑2:好的,这是一个澄清的尝试。请原谅不必要的“id”栏目。

CREATE TABLE players (
  id bigserial primary key,
  name text not null
);

CREATE TABLE teams (
  id bigserial primary key,
  name text not null
);

CREATE TABLE leaderships (
  id bigserial primary key,
  team_id bigint not null references teams(id),
  captain_id bigint not null references players(id),

  -- Make a key.
  UNIQUE (team_id,captain_id),

  -- Only one leadership per team.
  UNIQUE (team_id)
);

CREATE TABLE team_players (
  id bigserial primary key,
  team_id bigint not null,
  captain_id bigint not null,
  player_id bigint not null,

  -- One entry per player.
  UNIQUE (team_id,captain_id,player_id),

  -- Valid reference to a leadership.
  FOREIGN KEY (team_id,captain_id) references leaderships(team_id,captain_id),

  -- Not the captain.
  CHECK (player_id <> captain_id)
);

2 个答案:

答案 0 :(得分:1)

您需要了解database design

查找描述您的应用程序的填充(命名)空白语句。每个语句都有一个表。表保存了生成真实语句的行。

// [player_id] is a player
player(player_id)

// [team_id] is a team
team(team_id)

// player [player_id] plays for team [team_id]
team_players(team_id,player_id)

原来你不需要一个player_team_id。 team_players(player_id,team_id)对与他们1:1对,所以你可以使用它们。另一方面,团队合作者与他们签订1:1合同,以便他们可以发挥作用。

每个team_players player_id都是玩家player_id(因为每个队员都是玩家)。我们说通过FOREIGN KEY delaration(以及DBMS强制执行):

FOREIGN KEY (team_id) REFERENCES team (team_id)
FOREIGN KEY (player_id) REFERENCES player (player_id)

team_players(player_id,team_id)确实是唯一的。但事实并非如此。没有包含的子行是唯一的。这对数据库设计很重要。

唯一的子行是“超级钥匙”。包含没有较小的唯一子行的唯一子行是“关键”。使用KEY。任何关键列的超集都是唯一的。但SQL要求明确声明FOREIGN KEY的目标。使用UNIQUE。传统上在SQL中,您选择一个键作为PRIMARY KEY。这对某些SQL功能很重要。 (从技术上讲,在SQL KEY中意味着UNIQUE和PRIMARY KEY意味着UNIQUE NON NULL。即SQL不会强制执行no-smaller-contained-unique-subrow。)

KEY (team_id,player_id)

(如果你在team_players中也有一个team_player_id它也是一个KEY,通常是PK。)

有些球员是队长。这是1:1。所以team_id和player_id都是唯一的。

// [player_id] captains [team_id]
team_captains(team_id,player_id)
FOREIGN KEY (team_id) REFERENCES team (team_id)
FOREIGN KEY (player_id) REFERENCES player (player_id)
KEY (team_id)
KEY (player_id)

队长对必须出现在队友对中。

FOREIGN KEY (team_id,player_id) REFERENCES team_players (team_id,player_id)

你对冗余队长的看法令人钦佩。确实存在这样一种感觉:拥有数据库记录是一个人是团队的队长并且他们在特定团队中是多余的。

-- instead of having team_players(team_id,player_id)
-- team_players team_players FK now to here
// player [player_id] is a non-captain on team [team_id]
team_non_captains(team_id,player_id)
FOREIGN KEY (team_id) REFERENCES team (team_id)
FOREIGN KEY (player_id) REFERENCES player (player_id)
KEY (team_id,player_id)

但是,每当你想要一支球队的球员时,你就不得不说:

-- now team_player =
//     player [player_id] is a non-captain on team [team_id]
// OR player [player_id] is captain of team [teamm_id]
select * from team_non_captains UNION select * from team_captains

事实证明,每个队长有一个“冗余”行可能比每个涉及整个团队的查询有一个“冗余”联合操作(以及“冗余”人类解析子表达式)更糟糕。只需做出最直接的陈述。

(避免在初始设计中使用空值。它们使表的含义和查询含义复杂化。特别是查询含义,因为SQL不会以某种方式计算涉及空值的表达式,这意味着在查询中表的含义方面具有特定意义,更别说“不知道”或“不适用”。一个人将它们用作工程权衡,你必须学会​​判断。)

答案 1 :(得分:0)

最简单的可能的设计,真实世界的解决方案可能会涉及更多的复杂性。

  • 玩家只是一个团队的一员,因此player.team_id在功能上依赖于player.id,并指向teams.id
  • 一支球队有一名队长应该出现在球员名单中
  • 队长应该是团队的一员,这需要一个额外的约束团队。{team_id,captain_id} - &gt;播放器。{TEAM_ID,ID}

CREATE TABLE players
  ( id bigserial primary key
  , team_id INTEGER NOT NULL
  , name text not null
  , UNIQUE (team_id, id) -- we need this because the FK requires it
);

CREATE TABLE teams
  ( id bigserial primary key
  , captain_id bigint not null references players(id)
  , name text not null
);


ALTER TABLE players
        ADD FOREIGN KEY (team_id )
        REFERENCES teams(id)
        ;

        -- captain must be part of the team
ALTER TABLE teams
        ADD FOREIGN KEY (id, captain_id )
        REFERENCES players( team_id, id)
        ;

更新似乎玩家可以参加多个团队,因此需要N:M联结表:

CREATE TABLE players
  ( player_id bigserial PRIMARY KEY
  , name text not null
);

CREATE TABLE teams
  ( team_id bigserial PRIMARY KEY
  , captain_id bigint
  , name text not null
);

CREATE TABLE players_teams
        (player_id INTEGER NOT NULL REFERENCES players(player_id)
        , team_id INTEGER NOT NULL REFERENCES teams(team_id)
        , PRIMARY KEY (player_id,team_id)
        );

ALTER TABLE teams
        ADD FOREIGN KEY (team_id,captain_id)
        REFERENCES players_teams(team_id,player_id)
        ;