关系数据库设计 - 我想为某些行类型添加额外的字段,但不是全部

时间:2012-10-02 08:12:39

标签: database-design relational-database normalization database-schema

这是一个非常基本的数据库设计/规范化问题。

假设我有一个Books表,其中包含以下列:

isbn|title|author|status

status可以是checked outavailableoverduelost之一(存储为整数)。

添加行时,我决​​定“实际上,当状态为checked out时,我想存储另一个字段due_date”。 我只想将此字段存储为状态已检出的图书,因为它没有其他意义。

执行此操作的标准,正确,规范方法是什么?

一种方法是添加列并将其设置为NULL,如果状态不是checked out,但这听起来对我来说是一个坏主意(对于其他事情的完整性,例如,如果状态为available,我们还有due_date?)

另一个明显的答案是创建一个DueDates表并在其中存储isbn|due_date对。这是我通常采用的方法,但很容易最终得到表格和JOIN s。

我不是在寻找如何专门存储书籍,这只是问题的一个例子,我想知道标准的解决方案。

修改:如果我决定仅为checked out状态due_date添加批次字段,答案是否会更改borrowed_by,{ {1}},checked_out_from,...) - 如果状态不是NULL,则将所有这些设为checked out

6 个答案:

答案 0 :(得分:3)

我在这些情况下使用的方法是添加一个可选的列和一个检查约束,以确保当另一列具有特定值并且为空(即为空)时,该列已填充​​(即不为空)当另一列有其他值时。

在您的情况下,约束可以写为

CHECK ((due_date IS NOT NULL) = (status = 'CHECKED OUT'))

如果需要检查多个列,可以添加多个约束,每列一个,或者通过列出所有有效组合将它们组合成一个检查约束:

CHECK (status = 'A' AND due_date IS NULL OR status = 'B' AND due_date is NULL
OR status = 'C' AND due_date IS NOT NULL OR status = 'D' AND due_date IS NOT NULL)

NB 并且优先级高于OR,因此在这种情况下不需要括号,但为了清楚起见,您可能希望添加它们。

在大多数DBMS产品中添加单独的表会使这更加困难(如果不是不可能),因为检查约束可能不会查询另一个表。

答案 1 :(得分:3)

你所说的问题基本上是打字和子类型之一。 “签出的书”是一种书。 “可用书”是一种不同类型的书。随着时间的推移,一本书可以从一个州进展到另一个州,因此随着时间的推移可以属于一种或另一种亚型。

在对象建模中,这类问题是通过类,子类和继承来处理的。

在ER建模中,这种问题称为“专业化”。您可以在网上找到有关ER专业化的文章。我还没有看到处理时变专业化的例子。更多的例子是时间邀请,如宠物案例。

在关系建模和关系数据库设计中,有几种标准方法可用于构建表以实现特化。

第一种标准方式称为“单表继承”。这基本上就是你设计的。对于与给定行的子类型无关的数据,您最终会得到很多NULL。但是您不必进行任何连接。

第二种标准方式称为“类表继承”。通过这种方式,每个类和子类都有一个单独的表,并且它们具有共享主键。您可以在SO和Web上查找“类表继承”和“共享主键”。你做了更多的加入,但你的NULLS更少。

还有其他方法。

哪种方式最好取决于手头的情况。

答案 2 :(得分:1)

如果您需要严格的规范化数据结构,那么使用duedates表会更加规范化。

具有取决于状态的截止日期是多值依赖性,因此违反了第4范式。

这并不能避免您的状态为" checked_out"和due_date - 当状态可用时,同样可以在duedates表中有一个条目,因为它在duedate字段中有一个条目。

(顺便说一句,在这个图书馆的例子中,我会将贷款与“失去的"书籍状态”分开)

答案 3 :(得分:1)

我仍然会将列添加到基表并定义CHECK CONSTRAINT,以确保当状态值不等于DueDateCHECKED OUT为NULL。

规范化并存储ISBN - >在另一个表中的截止日期映射需要应用程序层代码,以确保其STATUS未检出的ISBN不会在该表中结束。

答案 4 :(得分:1)

删除冗余数据比尝试执行检查以保持同步更好

您在第一个示例中的真正问题是,您的状态列正在复制其他地方的信息,并且更详细地说明了due_date。

具体而言,状态“On Loan”,“Available”和“Overdue”都基于due_date,因此您尝试强制保持数据同步。最好将系统简化为不再存储需要同步的重复数据。

对于评论中给出的第二个例子,我认为Colin的答案是正确的方法,但它总是我设计数据库以减少这些约束的例子的第二选择。

答案 5 :(得分:0)

问题似乎出现在您的初始模型(定义)中。实体书(媒体上的书)和通用书{ISBN, Title, Author}之间存在差异。当您查看示例中的表时,存在依赖关系FD{ISBN} --> {Title, Author},但{Status}不依赖于{ISBN} - 因此状态不属于此表。

enter image description here

基本上,status是派生属性

select
      c.ISBN
    , b.BookTitle
    , c.BookCopyNo
    , case
      when  ReturnDate is not null                              then 'available'
      when (ReturnDate is null) and (current_date < DueDate)    then 'checked out'
      when (ReturnDate is null) and ((current_date - DueDate) >= 100 ) then 'lost'
      when (ReturnDate is null) and (current_date > DueDate)    then 'overdue'   
      end as BookStatus

from CheckOut as c
join Book     as b on b.ISBN = c.ISBN
where c.CheckOutDate = (select max(xx.CheckOutDate)
                          from CheckOut as xx
                         where xx.ISBN       = c.ISBN
                           and xx.BookCopyNo = c.BookCopyNo)
;