合法的?两个外键引用相同的主键

时间:2010-04-20 00:09:28

标签: sql database-design

就像Cliffs的更新一样,感谢ChaosPandion的模板。


    PersonID Int PK

网络
    PersonID Int PK FK
    OtherPersonID Int PK FK

OR


    PersonID Int PK

网络
    PersonID Int PK FK
    FriendID Int PK FK

朋友
    FriendID Int PK
    OtherPersonID Int FK

++++++ 原帖在下面 ++++++

大家好,

我是一名网络开发人员,最近与一家公司合作开展了一个项目。目前,我正在与他们的DBA一起制定网站的架构,我们对几张桌子上的设计产生了分歧,我想就此问题提出一些意见。

基本上,我们正在开发一个实现“朋友”网络的网站。该站点的所有用户都将包含在具有(PersonID int identity PK等)的表tblUsers中。

我想要做的是创建第二个表tblNetwork,它将保存用户之间的所有关系,具有(NetworkID int identity PK,Owners_PersonID int FK,Friends_PersonID int FK等)。或者相反,删除NetworkID,并将Owners_PersonID和Friends_PersonID共享为主键。

这是DBA遇到问题的地方。他说“他只会在数据仓库架构中实现这种架构,而不是在网站上实现这种架构,这只是网络开发人员试图采取简单方法的另一个例子。”

现在显然,他的评论有点煽动性,这有助于激励我找到合适的答案,但更重要的是,我只想知道如何正确行事。我已经开发了数十年的数据库和编程,与一些顶尖的人一起工作,从未听过这种论点。

DBA想要做的不是将Owners_PersonId和Friends_PersonId存储在同一个表中,而是创建第三个表tblFriends来存储Friends_PersonId,并让tblNetwork拥有(NetworkID int identity PK,Owner_PersonID int FK) ,FriendsID int FK(来自TBLFriends))。所有那些tblFriends将会是(FriendsID int identity PK,Friends_PersonID(与人相关))。

对我而言,创建第三个表格本质上是过分的,除了为Friends_PersonID创建别名之外什么都没做,并且导致我必须添加(我认为不需要的东西)加入我的所有查询,更不用说了在每个查询上执行连接所需的额外周期。

我从技术上理解,他想要的是可能的,但它是否符合最佳实践?什么是最佳做法?

感谢阅读,感谢评论。

赖安

5 个答案:

答案 0 :(得分:7)

如果我理解你,你提议:

Person              PersonID PK
FriendList          FriendListID, OwnerID, PersonID 

DBA建议:

Person              PersonID PK
FriendList          FriendListID, OwnerID
FriendListEntry     FriendListID, PersonID

您的方法需要为列表中的每个朋友添加多行。这将多次重复OwnerID,违反正常形式。 DBA的解决方案更加规范化,只有值依赖于FriendList表中的FriendListID。

这里最好的做法是成为DBA的好朋友。我会选择他的解决方案,因为它并不重要,你以后肯定需要他。

答案 1 :(得分:3)

对我来说唯一有意义的架构是:

Person
    PersonID Int PK

Friend
    PersonID Int PK FK
    OtherPersonID Int PK FK

所以你可能有一个名为FriendList的过程来执行这个很好的干净查询:

Select Person.*
From Friend
    Inner Join Person On Friend.OtherPersonID = Person.PersonID
Where Friend.PersonID = @PersonID;

我不会宽恕选择所有列。

答案 2 :(得分:2)

如果Network.Owners_PersonID以冗余方式存储在网络中,则您的设计会违反Third Normal Form

但我不明白DBA的设计实际上有何帮助。我希望Friends成为UsersNetworks之间的多对多表:

CREATE TABLE tblUsers (
  PersonID INT IDENTITY PRIMARY KEY
);

CREATE TABLE tblNetworks (
  NetworkID INT IDENTITY PRIMARY KEY,
  Owner_PersonID INT NOT NULL REFERENCES tblUsers
);

CREATE TABLE tblFriends (
  NetworkID INT NOT NULL REFERENCES tblNetworks,
  FriendID INT NOT NULL REFERENCES tblUsers,
  PRIMARY KEY(NetworkID, FriendID)
);

换句话说,你有一个简单的多对多关系:

Users ----<- Friends ->---- Networks

另外,Networks引用Users只是为了识别给定网络的所有者。这样,给定网络只有一行,因此您无法通过更改某些行上的网络所有者来创建更新异常。

我认为这不会过分地将实体分成单独的表。您仍然可以获得给定网络的朋友列表:

SELECT ... FROM Networks n JOIN Friends f ON (n.NetworkID=f.NetworkID)

您可以通过这种方式从所有网络获取所有用户的朋友(传递给定用户的?参数ID:

SELECT ... FROM Friends u 
JOIN Friends f ON (u.NetworkID=f.NetworkID)
WHERE u.UserID = ?

在您的原始设计中,它几乎相同:

SELECT ... FROM Networks u
JOIN Networks f ON (u.Owner_UserID=f.Owner_UserID)
WHERE u.FriendID = ?

但优点是你已经消除了可能的更新异常。

答案 3 :(得分:1)

  

我想要做的是创建一个   第二个表,tblNetwork,那将   掌握所有关系   用户,具有(NetworkID int identity   PK,Owners_PersonID int FK,   Friends_PersonID int FK等)。要么   相反,删除NetworkID,和   拥有Owners_PersonID和   Friends_PersonID作为主要人员共享   键。

我认为没有任何问题。我同意NetworkID是多余的 - 两个FK是表的自然键,所以你应该只使用它们作为主键,除非你有一些性能原因需要引用特定的通过代理人ID(在这种情况下你似乎没有)的关系。

答案 4 :(得分:0)

我说按你自己的方式去做。拥有第三个表格使编程部分更加痛苦。