外键可以作为主键吗?

160

我有两个表:

  • User(用户名,密码)
  • Profile(profileId,性别,出生日期,...)

目前我采用的方法是:每个Profile记录都有一个名为“userId”的字段,作为外键链接到User表。当用户注册时,他的Profile记录会自动创建。

我对我的朋友的建议感到困惑:将“userId”字段作为外键和主键,并删除“profileId”字段。哪种方法更好?


3
Entity Framework通过Code First方式为零对一(和一对一)关系自动生成了这个代码。所以...这是可能的。是否这样做是最好的方式...那是另一个问题。但它是有效的。在创建我的自己的数据库时,我从未这样做过(但我甚至从未想到过)。 - Raphaël Althaus
在你的情况下最好保留两个键,保留两个键没有任何害处。 - vidur punj
9个回答

206

外键几乎总是“允许重复”,这使它们不适合作为主键。

相反,找到一个可以唯一标识表中每个记录的字段,或者添加一个新字段(自增整数或GUID)作为主键。

唯一的例外是具有一对一关系的表,其中链接表的外键主键是相同的。


119
由两个外键组成的复合主键也非常适合实现多对多关系。 - user1804599
2
@rezadru:虽然我不想反驳rightfold,但是代理键几乎总是更好的选择。 - Robert Harvey
9
外键并不意味着它是一对多的(或者说“允许重复”),数据库中的键约束和唯一性是两个独立的概念,它们可以很容易地混合在一起,就像添加索引一样(这将是第三个独立的概念)。 - blindguy
我也同意@RobertHarvey的观点——在某些情况下,代理键也是更好的选择。例如,您可以拥有一个中间表,用于映射多对多关系,其主键作为另一个表的一部分使用。如果没有代理键,则所有标记为主键的外键将导致其他表膨胀。但是,如果您有代理键,则只传递一个键。在我看来,这更简单和高效。 - Pablo Alexis Domínguez Grau

66

主键始终需要是唯一的,如果表是一对多关系,则外键需要允许非唯一值。如果表通过一对一关系连接,则使用外键作为主键是完全可以的,而不是一对多关系。如果您希望相同的用户记录具有可能拥有多个相关配置文件记录,请选择单独的主键,否则请坚持现有方案。


23

是的,将主键设置为外键是合法的。这是一种罕见的结构,但适用于以下情况:

  • 1:1关系。由于不同的权限和特权仅适用于表级别(截至2017年,这样的数据库会很奇怪),因此两个表不能合并成一个。

  • 1:0..1关系。个人资料可能存在也可能不存在,这取决于用户类型。

  • 性能是一个问题,并且设计作为分区:个人资料表很少被访问,托管在单独的磁盘上或具有与用户表不同的分片策略。如果底层存储是列式存储,则没有意义。


如果经常连接表格,将会有负面影响,这就意味着通常建议使用一个表格更好。在某些情况下,数据总是单独访问而不是连接,在具有1:1关系的两个表格中有组织上的优势。 - blindguy
我正在寻找的另一个用途是具有公共键空间的模型继承。在我的情况下,我希望能够使用简单的外键在所有库存子类上注册事件。我希望在恰好一个表中使用Inventory中的每个键作为外键。 - dhill

8
是的,在一对一关系的情况下,外键可以是主键。

9
这对于超类型-子类型设计也很有用。子类型表的主键应该是对超类型表的外键引用。 - axelio

3
通常认为使用一对一关系是不良实践。这是因为您可以将数据表示为一个表,并获得相同的结果。
但是,有些情况下您可能无法对正在引用的表进行更改。在这种情况下,使用外键作为主键没有问题。最好是使用由自动递增独特主键和外键组成的复合键。
我目前正在开发一个系统,在该系统中用户可以登录并生成用于应用程序的注册码。基于某些原因,我不能简单地向用户表添加所需的列。因此,我采用了一个对一的方式与代码表互动。

4
我大致同意你的看法,将所有数据放在同一张表格的附加列中有很多优点。但是关于这个问题,“你可以只在一张表格中表示数据并达到同样的效果”:单独使用一个表格也是有用的,例如如果配置文件表项是可选的。例如,每个银行客户可能没有网上银行注册。在这种情况下,可以使用IB注册表来限制其他表格不再具有子记录。同样,在这里,也可以使用新的主键在IB注册表中完成此操作。 - Teddy
1
@Teddy 同样,我大多数同意你所说的。然而,在原始问题中,他们指出“...他的个人资料记录是自动创建的...”,这意味着个人资料表不是可选的。在个人资料表是可选的情况下,将其作为单独的表是可行的。但是再一次地,他们可以在同一张表中使用可空列。 - Tshsmith
1
通过使用单独的第二个表,我们可以防止在第三个表中输入,该表仅允许那些在第二个表中有条目的人进入。 - Teddy
我考虑这个的主要原因是,在数据仓库中,将事实表和维度表分开是一个好的做法。当使用PowerPivot、PowerBI和Tableau等软件时,分开的事实表和维度表是有用的提示。 - Marco Rosas
@MarcoRosas,通常情况下,您会将用于实时系统的数据和用于仓储的数据分开。您用于从_live_系统迁移数据到_warehouse_系统的脚本只需要考虑到这一点即可。 - Tshsmith
显示剩余2条评论

2
我不会这样做。我会将profileID作为表Profile的主键。
外键只是两个表之间的引用约束。
有人可能会认为,必须有一个主键作为其他表所引用的任何外键的目标。外键是任何表中的一组或多组列(不一定是该表的候选键,更不是该表的主键),其中可能包含另一张表的主键列中找到的值。因此,我们必须有一个主键来匹配外键。 但是,我们真的需要吗?主键在主键/外键对中的唯一目的是提供一个明确的联接 - 以维护与保存所引用的主键的“外部”表相对应的引用完整性。这确保了外键引用的值始终有效(如果允许,则为null)。 http://www.aisintl.com/case/primary_and_foreign_key.html

2
如果在User.UserID和Profile.UserID之间设置了FK约束,则强烈建议在Profile.UserID上建立索引。为什么不将其作为表Profile的主聚集索引,从而节省第二个索引和数据库引擎的大量不必要工作呢? - Reversed Engineer

1

这取决于业务和系统。

如果您的userId是唯一的,并且始终是唯一的,那么您可以使用userId作为主键。但是,如果您想要扩展您的系统,这将使事情变得困难。我建议您在用户表中添加一个外键,与档案表建立关系,而不是在档案表中添加一个外键。


0

简短回答:取决于情况……在这种特定情况下,可能是可以的。然而,专家们几乎每次都会建议不要这样做,包括你的情况。

为什么呢?

当键是外键(来自另一个表)时,在表中很少是唯一的。例如,一个项目ID在ITEMS表中可能是唯一的,但在ORDERS表中可能不是,因为同一类型的项目很可能存在于另一个订单中。同样,订单ID在ORDERS表中可能是唯一的(可能),但在其他表中如ORDER_DETAILS中却不是,因为一个具有多个行项目的订单可以存在,并且要查询特定订单中的特定项目,您需要将两个FK(order_id和item_id)连接起来作为此表的PK。

我不是数据库专家,但如果您可以逻辑上证明自动生成值作为您的PK,那么我会这样做。如果这不可行,则两个(或更多)FK的串联可以作为您的PK。但是,我无法想到任何情况下单个FK值可以被证明为PK的情况。


0

虽然这并不完全适用于问题的情况,但由于我在寻找其他信息时进入了这个问题,并阅读了一些评论,我可以说,在一个表中只有一个FK并获得唯一值是可能的。 您可以使用具有类别的列,该列只能分配1次,它几乎像ID一样工作,但是如果您想要使用唯一的分类值来区分每个记录,则可以在此情况下完成。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接