【问题标题】: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 设置两列,其中一列为空。

我希望我已经正确描述了这一点,因为这并不像乍看起来那么明显。

所以问题是:数据库的“正确”设计是什么?如何查询它以获取所需的信息?

【问题讨论】:

  • 客户和联系人似乎有些混淆。它们是单独的表吗?客户和联系人之间是否有任何关系?它们是您需要使用的现有表,还是新的或可重构的表?
  • 是的,客户和联系人是独立的实体。也许他们不应该是,但他们是。 ;-)

标签: sql database-design normalization


【解决方案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 添加到其中包含扩展的单例客户信息的客户表中。

如果您无法做到这一点(因为这是在现有系统之上构建的,而该系统的设计是固定的),那么您有多种选择。没有一个选择是好的,因为它们无法真正解决原来的缺陷,即分别存储客户和联系人。

  1. 使用两列,一列为 NULL,以允许参照完整性发挥作用。

  2. 使用自己的 PK 和两列(一列为 NULL)构建一个中间表 ActivityContacts,以指向客户或联系人。这允许您构建一个“干净”的 Activity 系统,但会将丑陋的东西推到那个中间表中。 (它确实提供了一个可能的好处,那就是它允许您将活动的目标限制为添加到中间表的人,如果这对您有利的话)。

  3. 将最初的设计缺陷带入活动系统,并且(我在这里咬我的舌头)具有并行的 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”来命名该基表。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2011-03-25
              • 1970-01-01
              • 1970-01-01
              • 2010-12-08
              • 1970-01-01
              • 2012-06-03
              • 1970-01-01
              相关资源
              最近更新 更多