这是一个非常基本的数据库设计/规范化问题。
假设我有一个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 out
状态due_date
添加批次字段,答案是否会更改borrowed_by
,{ {1}},checked_out_from
,...) - 如果状态不是NULL
,则将所有这些设为checked out
?
答案 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
,以确保当状态值不等于DueDate
时CHECKED 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} - 因此状态不属于此表。
基本上,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)
;