3

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

假设我有一个Books包含以下列的表格:

isbn|title|author|status

并且status可以是checked out, available, overdue,之一lost(存储为整数)。

添加行时,我决​​定“实际上,当状态为 时checked out,我想存储另一个字段due_date”。我只想将此字段存储为状态为已签出的书籍,否则它没有任何意义。

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

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

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

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

编辑:如果我决定只为状态添加许多字段( 、、、 ...) ,答案是否会改变- 并且所有这些都好像状态不是?checked outdue_dateborrowed_bychecked_out_fromNULLchecked out

4

6 回答 6

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)

注意AND 比 OR 具有更高的优先级,因此在这种情况下不需要括号,但为了清楚起见,您可能希望添加它们。

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

于 2012-10-02T08:22:06.657 回答
3

正如您所说,问题基本上是类型和子类型之一。“签出的书”是一种书。“可用书籍”是一种不同类型的书籍。一本书可以随着时间的推移从一个状态发展到另一个状态,因此随着时间的推移可以属于一个或另一个子类型。

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

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

在关系建模和关系数据库设计中,有几种标准的建表方式来实现专业化。

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

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

还有其他方法。

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

于 2012-10-05T10:15:46.273 回答
1

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

您在第一个示例中的真正问题是您的状态列正在复制其他地方保存的信息,并且在到期日期之前更详细。

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

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

于 2012-10-02T08:48:21.943 回答
1

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

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

这并不能避免您的状态为“checked_out”和到期日期的问题 - 当状态可用时,在到期日期表中有一个条目,就像在到期日期字段中有一个条目一样。

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

于 2012-10-02T08:15:54.240 回答
1

我仍然会将该列添加到基表并定义 aCHECK CONSTRAINT以确保DueDate当 status 中的值不等于时为 NULL CHECKED OUT

规范化和存储 ISBN -> 到期日映射在不同的表中需要应用层代码以确保其状态未签出的 ISBN 不会最终出现在该表中。

于 2012-10-02T08:17:57.310 回答
0

问题似乎出在您的初始模型(定义)中。实体书(媒体上的书)和普通书是有区别的{ISBN, Title, Author}。当您查看示例中的表时,存在依赖项FD{ISBN} --> {Title, Author},但{Status}不依赖于 {ISBN} - 因此状态不属于此表。

在此处输入图像描述

本质上,状态是一个派生属性

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)
;
于 2012-10-02T16:39:37.093 回答