具有N-M关系的复合主键

时间:2012-08-08 11:33:08

标签: mysql sql database database-design

Previous Question我了解了在n-m关系中使用复合主键。我的情况非常相似,但有点不同。

[Person]

    PERSON_ID: int, primary key
    CLIENT_ID: int, primary key
    Name: nvarchar(xx)
    primary_key( PERSON_ID, CLIENT_ID);

[Group]

    GROUP_ID: int
    CLIENT_ID: int
    Name: nvarchar(xx)
    primary_key( GROUP_ID, CLIENT_ID);

现在,当我使用mysql-workbench工具生成关系表时,它会创建一个表:

  

选项1:

    [PersonHasGroup]

    PERSON_ID: int 
    CLIENT_ID: int 
    GROUP_ID: int
    CLIENT1_ID: int
primary_key( PERSON_ID, CLIENT_ID, GROUP_ID, CLIENT1_ID);

在我的情况下,两个client_id通常都具有相同的值,因此我将表编辑为

  

选项2:

    [PersonHasGroup]

    PERSON_ID: int
    CLIENT_ID: int
    GROUP_ID: int
primary_key( PERSON_ID, CLIENT_ID, GROUP_ID);

那些是好的做法吗?我的其他同事喜欢的是有点不同。他们使用:

  

选项3:

   [PersonHasGroup]
    PERSON_HAS_GROUP_ID: int, auto-increment
    CLIENT_ID: int        
    PERSON_ID: int, foreign key
    GROUP_ID: int, foreign key
primary_key( PERSON_HAS_GROUP_ID, CLIENT_ID);

当我的关系通过一个例子进行多对多的解释时,哪种做法是合适的:)

5 个答案:

答案 0 :(得分:3)

提出的选项之间不应该有任何争论。有一个正确答案,但答案取决于您的业务规则。

您的陈述“在我的情况下,两个client_id通常都具有相同的值关注我。通常的术语通常在设计系统时没有帮助。你需要用绝对话来谈谈。

如果两个关系的CLIENT_ID必须相同,则选项2(单个CLIENT_ID)是正确的设计。

但是如果可能(可能很少但可能)每个关系可以基于不同的CLIENT_ID,那么当然你需要选项1和两个唯一命名的CLIENT_ID列。 (我无法想象这个商业案例,但你的模型已经很奇怪,所以我不太确定)

除非您完全解释您的业务要求,否则没有人可以帮您确定哪个是正确的,但您表明这是不可能的。

选项3不应被视为一种选择 - 如果您要引入代理键,则它不应该是复合代理键。

我个人永远不会为联结表(解析多对多关系的表)创建代理键,除非联结表PK可以是另一个表中的外键。在这种情况下,出于性能原因,我可能创建一个代理键,但这不是一个简单的选择。如果创建了代理键,则应该声明主键和备用键:一个具有自然复合键,另一个具有代理键。您希望确保只输入一次给定的组/人对,并且您希望代理也是唯一的。

因此,如果出于性能原因,您可以修改选项1和2以包含代理键:

选项1A

[PersonHasGroup]
  PERSON_ID: int
  PERSON_CLIENT_ID: int
  GROUP_ID: int
  GROUP_CLIENT_ID: int
  PERSON_HAS_GROUP_ID: int
primary_key( PERSON_ID, PERSON_CLIENT_ID, GROUP_ID, GROUP_CLIENT_ID);
alternate_key( PERSON_HAS_GROUP_ID )

选项2A

[PersonHasGroup]
  PERSON_ID: int
  CLIENT_ID: int
  GROUP_ID: int
  PERSON_HAS_GROUP_ID: int
primary_key( PERSON_ID, CLIENT_ID, GROUP_ID);
alternate_key( PERSON_HAS_GROUP_ID )

当然你可以反转哪个是主要的,哪个是替代。

答案 1 :(得分:2)

我更喜欢有一个id用于简单引用,另一个唯一的密钥用于唯一对,从未使用过工作台,所以这里首先是普通的sql:

CREATE TABLE person_has_group (
    person_has_group_id SERIAL,
    client_id BIGINT UNSIGNED NOT NULL REFERENCES clients (client_id),
    person_id BIGINT UNSIGNED NOT NULL,
    group_id BIGINT UNSIGNED NOT NULL,
    FOREIGN KEY (client_id, person_id) REFERENCES persons (client_id, person_id),
    FOREIGN KEY (client_id, group_id) REFERENCES groups (client_id, group_id),
    UNIQUE KEY (client_id, person_id, group_id)
);

并在“工作台”中发现了类似的事情:

[PersonHasGroup]
    PERSON_HAS_GROUP_ID: int, auto-increment
    CLIENT_ID: int        
    PERSON_ID: int, foreign key
    GROUP_ID: int, foreign key
    primary_key( PERSON_HAS_GROUP_ID)
    unique_key( CLIENT_ID, PERSON_ID, GROUP_ID);

答案 2 :(得分:1)

如果我理解正确,则应该如下:

  • 您有多个服务器(DB),每个客户端一个。

  • 每个数据库都有自己的PersonIDGroupID自动增量。

  • 您正在尝试简单地(直接)将所有内容导入一个多租户数据库;因此ClientIDPerson表的主键中的Group

如果这些陈述不正确,请忽略其余的陈述。

enter image description here

create table PersonGroup (
    PersonID integer
  , GroupID  integer
  , ClientID integer
);

alter table PersonGroup add constraint  pk_PersonGroup
            primary key (PersonID, GroupID, ClientID);

alter table PersonGroup add constraint fk1_PersonGroup
            foreign key (PersonID, ClientID) references Person(PersonID, ClientID);

alter table PersonGroup add constraint fk2_PersonGroup
            foreign key (GroupID, ClientID) references Group(GroupID, ClientID);

答案 3 :(得分:0)

我认为option 2会更好,因为它还会在UNIQUE CONSTRAINT列上添加( PERSON_ID, CLIENT_ID, GROUP_ID),这样就不会在表格中插入重复项。

答案 4 :(得分:0)

好问题。但我们首先快速查看您的表。

考虑到表格的名称,中的每一行似乎都在重新列出中的每一行似乎都代表一个。因此,从概念上讲,要识别您不需要 Client_ID 的个人或群组,因此您不需要将其作为主键的一部分。如果您的模型要求每个人都拥有唯一的 Client_ID (除 Person_ID 之外),只需将其设为唯一字段,但不要将其包含在主键中。< / p>

对于表,情况稍微有些混乱。问题是为什么单靠 Group_ID 无法识别群组? ( Client_ID 字段将一个组与客户端相关联。但这种关系的本质是什么?每个组是由客户创建的吗?在这种情况下,您不需要 Client_ID 作为密钥的一部分,您只需要将其作为外键。它是成员关系吗?在这种情况下,您需要一个由另一个表表示的N到M关系。)

然后我们了解群组之间的关系。 N到M的关系总是被翻译成一个表,其中包含两个表的PK + N到M 关系的任何属性(例如成员资格的日期) 。在这种情况下,您只需要一个包含两个 Person_ID Group_ID 字段的表,并且它们都是复合键的一部分。

您还需要注意命名.. PersonH​​asGroup 代表什么?该人是否拥有该团体?管理吗?或者它只是一个会员。如果是会员资格,就像“ PersonGroup ”一样​​简单就足够了。

确保您在概念上正确地看到实体,否则您将陷入向主键添加字段和链接不能完全满足您需求的表的陷阱。保持封装在其中的实体的属性,不要混合它们。问问自己“我如何识别现实世界中的实体?”......并且您可以获得主键应该是什么的答案。诸如“在我的情况下,client_id通常具有相同的值”之类的事情表明您没有正确地看到概念并将它们混合起来导致冗余和不正确的关系。

最后,据我所知,有时设计糟糕的数据库会传递给您,并要求您引入新的链接和实体。我的建议是尽量做出更正。从长远来看,它会拯救你。

因此,在我看来,与此问题相比,您的Previous Design设计要好得多。