设计这个特定的数据库/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 TypeContactCustomer 之一

然后您将执行如下 SQL 选择语句:Select * from table where Recipient_Type = 'Contact';

我意识到需要更多信息。

我们将需要一个代表收件人(联系人和客户)的附加表:

此表应如下所示:

ID | Name| Recipient Type

Recipient Type 将是本文前面最初提到的表格的关键参考。当然,需要做一些工作来处理这些表之间的级联,主要是更新和删除。所以快速回顾一下:

Recipients.Recipient_TypeTable.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 工作的最佳方法是啥?

将数据从 rest api 推送到 sql 数据库的最佳方法是啥?

在数据库 django 中用特殊字符定义特定数字的最佳方法是啥