设置共享通用类型的多对多表

时间:2013-01-13 19:28:16

标签: sql sql-server database-design

我正在准备一个传统的Microsoft SQL Server数据库,以便我可以通过ORM(如实体框架)进行交互,我的问题围绕着处理我分享的一些多对多关联的设置一种常见的类型。具体来说,应该在主类型之间共享一个公共类型,还是每个主类型都有自己的链表?

例如,这是我编制的一个简单示例,它显示了当前如何设置感兴趣的表:

legacy many-to-many

请注意,有两种类型TeachersStudents,两者都可以包含零个,一个或多个PhoneNumbers。这两个表TeachersStudents实际上共享一个关联表(PeoplePhoneNumbers)。字段FKID可以是TeacherIdStudentId

我认为应该设置的方式是这样的:

better many-to-many?

这样,Teachers表和Students表都有自己的PhoneNumbers表。

我的直觉告诉我第二种方式是正确的方法。这是真的?即使PhoneNumbers表包含多个字段呢?我的面向对象的程序员大脑告诉我,如果这些表之间的唯一区别是它们链接到哪个主表,那么拥有几个相同的表是不对的,每个表包含十几个字段?例如:

redundant tables

这里我们有两个包含相同信息的表,但唯一的区别是一个表是Teachers的地址,另一个表是Students的地址。这些对我来说是多余的,并且它们应该真的是一个表 - 但是我失去了数据库限制它们的能力(对吗?)并且当我尝试将ORM应用于此时也使我自己变得更加混乱。

是否应该合并这种类型的公共类型,还是应该为每种主类型保持分离?

更新

以下答案指导我使用以下解决方案,该解决方案基于数据库中的子类化。我最初的一个问题是我有一个在多个其他表之间共享的公共表,因为该实体类型对于其他表都是通用的。处理这种情况的正确方法是对共享表进行子类化,并基本上将它们从公共父级中删除,并将公共数据类型链接到此新父级。这是一个例子(请记住我的实际数据库与教师和学生无关,所以这个例子是高度制造但概念是有效的):

solution

由于TeachersStudents都需要PhoneNumbers,因此解决方案是为Party创建超类PhoneNumbers和FK Party }表。另请注意,您仍然可以使用仅与Teachers有关的FK表,或仅与Students有关。在这个例子中,我还将StudentsPartTimeStudents分为另外一个级别,并从Learners下降。

这个解决方案非常令人满意的是当我在ORM中实现它时,例如Entity Framework。

查询很简单。我可以使用特定的电话号码查询所有教师和学生:

var partiesWithPhoneNumber = from p in dbContext.Parties
     where p.PhoneNumbers.Where(x => x.PhoneNumber1.Contains(phoneNumber)).Any()
     select p;

同样容易进行类似的查询但仅适用于仅属于教师的PhoneNumbers:

var teachersWithPhoneNumber = from t in dbContext.Teachers
  where t.Party.PhoneNumbers.Where(x => x.PhoneNumber1.Contains(phoneNumber)).Any()
  select t;

4 个答案:

答案 0 :(得分:2)

我认为你应该研究超类型/子类型模式。添加一个PartyPerson表,每个教师或学生都有一行。然后,使用PartyIDTeacher表中的Student作为PK和FK返回Party(但将其命名为TeacherID和{{1} })。这在超类型表和每个子类型表之间建立了“一对一或一”关系。

请注意,如果您在子类型表中有标识列,则需要删除它们。在创建这些实体时,您首先必须插入超类型,然后在任一子类型中使用该行的ID。

为了保持一致性,您还必须重新编号其中一个子类型表,以使其ID与其他子表不冲突。在此之后,您可以使用StudentID创建缺少的超类型行。

所有这一切的美妙之处在于,如果你的桌子必须只允许一种类型,例如SET IDENTITY_INSERT ON你可以FK到那张桌子,但当你需要一个FK可以是 - 或者你的Student表 - 您将FK改为Address表。

最后一点是将所有公共列移动到超类型表中,并且只在子类型中放置必须在它们之间不同的列。

您的单个​​Party表现在也很容易与Phone相关联。

有关更详细的说明,请参阅this answer to a similar question

答案 1 :(得分:2)

教师和学生都是更一般概念(人)的子类。如果您创建一个Person表,其中包含为数据库中的所有人共享的常规数据,然后创建链接到Person并包含任何其他详细信息的Student和Teacher表,您将发现您有适当的链接点在任何其他表。

如果所有人都有共同的数据(例如零到多个电话号码),那么您可以链接到人员表。如果您的数据仅适用于学生,则将其链接到学生ID。您将获得额外的优势,即学生辅导员只是一个具有学生和教师记录的人。

一些ORM直接支持子类表的概念。 LLBLGen以我描述的方式这样做,因此您可以使您的数据访问代码与更高级别的概念(教师和学生)一起工作,并且将在低级数据访问代码中代表您管理Person表。

修改

关于当前图表的一些评论(这可能与源字段中的相关性无关,因此建议使用少量盐)。

派对,教师和学习者看起来很好。如果您为费率添加开始日期和结束日期,工资看起来不错,这样您就可以跟踪工资历史记录。另外请记住,如果最终有多个具有薪水的entites,则使用PartyID(而不是TeacherID)可能是有意义的。

PartyPhoneNumbers看起来可能会直接挂断电话号码。这取决于您是否希望立即更改多人(n:m)的电话号码,或者每个方独立拥有电话号码。 (我希望后者是因为你可能有一个学生是教师的(现实世界)孩子,因此他们共用一个电话号码。我不希望更新学生的电话号码来影响老师,所以连接表在这里看起来很奇怪。)

学习者支付历史似乎是正确的,但学生与PartTimeStudents的差异似乎是人为的。 (似乎PartTimeStudents更多的是AttendenceDays,而这又是LearnerClasses连接的结果)。

答案 2 :(得分:1)

你遇到的问题是一个“一个”关系的例子。一个人是教师或学生(或可能是两者)。

我认为现有结构最能抓住这些信息。

此人有一个电话号码。然后,有些人是老师,有些人是学生。有关每个实体的附加信息存储在教师或学生表中。常用信息(如姓名)位于电话表格中。

将电话号码拆分为两个单独的表格相当混乱。毕竟,电话号码不知道是给学生还是老师。此外,您没有其他电话号码的空间,例如行政人员。对于有时可能教授或帮助教授课程的学生,您也面临挑战。

答案 3 :(得分:0)

阅读您的问题,看起来您正在为您的情况要求一个通用的数据库架构。我过去见过几个,有些比其他人更容易使用。

一个选项是拥有一个Student_Address表和一个使用相同Address表的Teacher_Address表。这样,如果您要存储实体特定字段,则具有该功能。但这可能会稍微(但不是很明显)查询更难。

另一个选择是你上面的建议 - 我可能只是在桌面上添加一个主键。但是,您希望将PersonTypeId字段添加到该表(PersonTypeId链接到PersonType表)。通过这种方式,您可以了解每条记录中的实体。

我不建议使用两个PhoneNumber表。我想你会发现在同一张桌子里维护所有内容要容易得多。我更喜欢将相同的实体保持在一起,这意味着学生是一个单一的实体,教师是一个单一的实体,而PhoneNumbers是同一个实体。

祝你好运。