设计这个特定的数据库/SQL 问题的最佳方法是啥?
Posted
技术标签:
【中文标题】设计这个特定的数据库/SQL 问题的最佳方法是啥?【英文标题】:What is the best way to design this particular database/SQL issue?设计这个特定的数据库/SQL 问题的最佳方法是什么? 【发布时间】:2011-04-28 07:01:17 【问题描述】:这是一个棘手的规范化/SQL/数据库设计问题,一直困扰着我们。我希望我能正确地陈述它。
您有一组活动。它们是需要完成的事情——一个美化的 TODO 列表。任何给定的活动都可以分配给员工。
每个活动也都有一个要为其执行活动的实体。这些活动是联系人(个人)或客户(企业)。然后,每个活动都将有一个联系人或一个为其完成活动的客户。例如,活动可能是“向 Spacely Sprockets(客户)发送感谢卡”或“向 Tony Almeida(联系人)发送营销资料”。
从该结构中,我们需要能够查询以查找给定员工必须执行的所有活动,并将它们列在一个关系中,最简单的形式如下所示:
-----------------------------------------------------
| Activity | Description | Recipient of Activity |
-----------------------------------------------------
这里的想法是避免为 Contact 和 Customer 设置两列,其中一列为空。
我希望我已经正确描述了这一点,因为这并不像乍看起来那么明显。
所以问题是:数据库的“正确”设计是什么?如何查询它以获取所需的信息?
【问题讨论】:
客户和联系人似乎有些混淆。它们是单独的表吗?客户和联系人之间是否有任何关系?它们是您需要使用的现有表,还是新的或可重构的表? 是的,客户和联系人是独立的实体。也许他们不应该是,但他们是。 ;-) 【参考方案1】:这听起来像是基本的多对多关系,我会这样建模。
【讨论】:
在维护方面,随着接收者类型的增加,这将如何扩展? 我在很多方面都喜欢这个,但它增加了两个额外的表格......和额外的功能。除非一个活动需要有多个“收件人”,否则我认为在 Activity 表上有两个外键会更简单。 @Woot4Moo:显然它通过需要额外的表来扩展,但它避免了您的解决方案中提到的级联问题。 @jamietre:OP 的要求明确指出:“...避免为 Contact 和 Customer 设置两列,其中一列为空。” 很公平......我会在门萨问题上失败,但我认为 OP 可能会努力避免这种情况。除非您需要额外的功能,否则我想不出这个解决方案与有时为空的列相比有什么优势。无论哪种方式,获得他想要的输出在逻辑上都是相同的,而且 p.o.v 肯定没有任何优势。空间使用。【参考方案2】:此数据库的“正确”设计是每个列都有一列,您说您要避免这种情况。这允许在这两个列及其各自的表之间定义适当的外键关系。对引用两个不同表的键使用同一列会使查询变得丑陋,并且您无法强制执行引用完整性。
Activity 表应该有外键 ContactID, CustomerID
显示员工的活动:
SELECT ActivityName, ActivityDescription, CASE WHEN a.ContactID IS NOT NULL THEN cn.ContactName ELSE cu.CustomerName END AS Recipient
FROM activity a
LEFT JOIN contacts cn ON a.ContactID=cn.ContactID
LEFT JOIN customers cu ON a.CustomerID=cu.CustomerID
【讨论】:
我认为对于您身边的关系数据库而言,外键是什么存在根本性的误解。 不确定你的意思。我可能对 OP 的提议做出了假设。 我注意到的问题与帖子中的漂亮图表相同。随着更多类型的收件人的添加,这在未来如何不会成为维护问题。最合适的方法(参见可维护性)是拥有一个 Recipients 表,该表允许添加/修改 Recipient_Type 列,而不会严重损害维护数据库的任何人的心理。 @Woot4Moo 可以说 1) YAGNI 2) 你的应用需要支持它,所以你的解决方案需要维护 如果预计可能会有更多不同类型的收件人都需要以这种方式链接,那么问题就完全不同了。【参考方案3】:我不清楚为什么将客户和联系人定义为单独的实体,而它们似乎是同一实体的版本。在我看来,客户是具有附加信息的联系人。如果可能的话,我会创建一个联系人表,然后使用该表中的一个字段来标记那些是客户的,或者通过将他们的 id 添加到其中包含扩展的单例客户信息的客户表中。
如果您无法做到这一点(因为这是在现有系统之上构建的,而该系统的设计是固定的),那么您有多种选择。没有一个选择是好的,因为它们无法真正解决原来的缺陷,即分别存储客户和联系人。
使用两列,一列为 NULL,以允许参照完整性发挥作用。
使用自己的 PK 和两列(一列为 NULL)构建一个中间表 ActivityContacts,以指向客户或联系人。这允许您构建一个“干净”的 Activity 系统,但会将丑陋的东西推到那个中间表中。 (它确实提供了一个可能的好处,那就是它允许您将活动的目标限制为添加到中间表的人,如果这对您有利的话)。
将最初的设计缺陷带入活动系统,并且(我在这里咬我的舌头)具有并行的 ContactActivity 和 CustomerActivity 表。要查找员工分配的所有任务,请在 VIEW 中将这两个表合并为一个。这使您可以保持参照完整性,不需要 NULL 列,并为您提供获取报告的来源。
【讨论】:
【参考方案4】:这是我的尝试:
基本上,您需要将活动与 1 位(联系人或客户)和 1 位负责该活动的员工相关联。请注意,您可以在这样的模型中处理引用约束。
还请注意,我添加了一个连接所有人员和地点的 businessEntity 表。 (有时有用但不是必需的)。放置businessEntity 表的原因是您可以简单地将活动中的ResponsiblePerson 和Recipient 引用到businessEntity,现在您可以让任何人和所有人员或地点执行和接收活动。
【讨论】:
【参考方案5】:如果我没看错的话,收件人是客户和联系人的概括。 gen-spec 设计模式很好理解。
Data modeling question
【讨论】:
【参考方案6】:你会得到如下内容:
Activity | Description | Recipient Type
其中Recipient Type
是Contact
或Customer
之一
然后您将执行如下 SQL 选择语句:Select * from table where Recipient_Type = 'Contact';
我意识到需要更多信息。
我们将需要一个代表收件人(联系人和客户)的附加表:
此表应如下所示:
ID | Name| Recipient Type
Recipient Type
将是本文前面最初提到的表格的关键参考。当然,需要做一些工作来处理这些表之间的级联,主要是更新和删除。所以快速回顾一下:
Recipients.Recipient_Type
是 Table.Recipient_Type
的 FK
【讨论】:
在这个模型中,收件人是谁?您将如何管理Contacts
/Customers
的外键?你会区分这些吗?
不太确定我是否听懂了你的问题,马丁,我现在明白了
这应该更流畅并回答问题。如果需要更多解释,请告诉我【参考方案7】:
[ActivityRecipientRecipientType]
ActivityId
RecipientId
RecipientTypeCode
||| ||| |||_____________________________
| | |
| -------------------- |
| | |
[Activity] [Recipient] [RecipientType]
ActivityId RecipientId RecipientTypeCode
ActivityDescription RecipientName RecipeintTypeName
select
[Activity].ActivityDescription
, [Recipient].RecipientName
from
[Activity]
join [ActivityRecipientRecipientType] on [Activity].ActivityId = [ActivityRecipientRecipientType].ActivityId
join [Recipient] on [ActivityRecipientRecipientType].RecipientId = [Recipient].RecipientId
join [RecipientType] on [ActivityRecipientRecipientType].RecipientTypeCode = [RecipientType].RecipientTypeCode
where [RecipientType].RecipientTypeName = 'Contact'
【讨论】:
想解释一下对多个连接的需求,以及这如何不成为 DBA 的噩梦?【参考方案8】:Actions
Activity_ID | Description | Recipient ID
-------------------------------------
11 | Don't ask questions | 0
12 | Be cool | 1
Activities
ID | Description
----------------
11 | Shoot
12 | Ask out
People
ID | Type | email | phone | GPS |....
-------------------------------------
0 | Troll | troll@hotmail.com | 232323 | null | ...
1 | hottie | hottie@hotmail.com | 2341241 | null | ...
select at.description,a.description, p.* from Activities at, Actions a, People p
where a."Recipient ID" = p.ID
and at.ID=a.activity_id
result:
Shoot | Don't ask questions | 0 | Troll | troll@hotmail.com | 232323 | null | ...
Ask out | Be cool | 1 | hottie | hottie@hotmail.com | 2341241 |null | ...
【讨论】:
【参考方案9】:为另一个实体建模:ActivityRecipient,它将由 ActivityRecipientContact 和 ActivityRecipientCustomer 继承,它将持有正确的客户/联系人 ID。
对应的表格是:
Table: Activities(...., RecipientID)
Table: ActivityRecipients(RecipientID, RecipientType)
Table: ActivityRecipientContacts(RecipientID, ContactId, ...,ExtraContactInfo...)
Table: ActivityRecipientCustomers(RecipentID, CustomerId, ...,ExtraCustomerInfo...)
这样,您还可以为每个收件人类型设置不同的其他列
【讨论】:
【参考方案10】:我会修改客户和联系人的定义。客户可以是个人也可以是企业,对吗?在巴西,有“pessoa jurídica”和“pessoa física”这两个术语——直接(和无意识地)翻译成“法人”(企业)和“自然人”(个人)。谷歌提出了更好的翻译:“法人实体”和“个人”。
所以,我们得到一个 person 表,并有一个 'LegalEntity' 和 'Individual' 表(如果有足够的属性来证明它 - 这里有很多)。并且接收器成为一个 FK 到 Person 表。
联系人去哪儿了?它们成为与人链接的表格。由于联系人是另一个人的联系人(例如:我的妻子是我在某些公司的注册联系人,我是客户)。人们可以有联系方式。
注意:我使用了“Person”一词,但您可以将其称为“Customer”来命名该基表。
【讨论】:
以上是关于设计这个特定的数据库/SQL 问题的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章
在 SQL Server 中对大型表进行分区的最佳方法是啥?
将 UITapGestureRecognizer 添加到 UILabel 的特定部分的最佳方法是啥?
SQL - 为这个 SELECT CASE 工作的最佳方法是啥?