我该如何规范化这个数据库设计呢?

时间:2017-05-05 14:09:47

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

我正在尝试设计数据库。我有一个设计在我看来被标准化为Fourth Normal Form - 但我认为它仍然被打破了,我不能为我的生活找到解决方法。

背景:我们有四种类型的测试,每种类型都有几十种测试。我们分批运行测试,其中每个批次只包含一种类型的测试。因此测试结果属于批处理,也属于测试。这给出了类似这样的数据库计划:

Diagram with Result going Many-to-One to Test and Result also going Many-to-One to Batch, and both Batch and Test going Many-to-One to TypeOfTest

问题是此设计允许结果用于类型A的测试,但该结果是类型B的批处理。

我不能做的一件事是将Test和Batch表合并到一个表中。每周有一个新批次,而测试持续数月或数年。批处理可以包含许多测试(尽管总是具有相同的类型),并且测试通常在许多批次中进行多次。

我可以在测试和批处理之间插入多对多连接,但我无法立即看到这对任何事情都有帮助。

是否有一种干净的方式来重新组织这个,以便我们没有圆形连接路径?这有必要吗?还是可取的?

或者我应该选择我所拥有的东西,并且不再担心它? : - )

[编辑1]请注意,测试详细说明了它如何运行,谁修复了发现的问题等,这些问题在多个批次中保持不变,因此测试必须独立存在于它可能(或可能不)运行的任何批次中

[编辑2]有人指出,最好有一个TestBatch表,它给我们这个结构:

Now with TestBatch in the place where Result used to be, and Result going Many-to-One to TestBatch

我同意这是一个好主意,但这并没有解决问题。它只是将问题从Result移到TestBatch。我们现在可以有一个TestBatch用于A类测试,但TestBatch有一个B类批处理。

[编辑3]感谢@ philip-kelley的出色建议,我相信我们有一个答案。首先,我们将TestBatch直接链接回Type,因此:

enter image description here

这并不能立即解决问题。实际上它会变得更糟 - 现在可以有一个用于测试的类型,一个不同的批处理类型,以及一个直接从TestBatch加入的第三个类型。

但第二步是将外键从TestBatch更改为Test,以便它包含Type和TestID。并将外键更改为Batch以包括Type和BatchID。

这样,我们可以确定TestBatch具有与Test和Batch相同的Type。

2 个答案:

答案 0 :(得分:3)

@ HLGEM的答案描述了逻辑模型,其中包含一些物理模型细节。支持并强制执行业务规则的物理实现看起来像这样。 (这是伪代码,仅显示关键列 - 您希望为Name,Score等属性添加列。实际的实现细节是系统相关的,可能会有点棘手,但任何RDBMS都应该能够支持这一点。请注意,列出的所有列都不可为空。)

CREATE TABLE TestType
  TestType    int
  <primary key on TestType>


CREATE TABLE Test
  TestId       int
  TestType     int
  <primary key on TestId>
  <foreign key into TestType on column TestType>


CREATE TABLE Batch
  BatchId      int
  TestType     int
  <primary key on BatchId>
  <foreign key into TestType on column TestType>


CREATE TABLE TestInBatch
  TestInBatchId  int
  TestId         int
  BatchId        int
  TestType       int
  <primary key on TestInBatchId>
  <unique constraint on TestId, BatchId>
  <foreign key on (TestId, TestType) into Test, columns (TestId, TestType)>
  <foreign key on (BatchId, TestType) into Batch, columns (BatchId, TestType)>


CREATE TABLE Result
  ResultId       int
  TestInBatchId  int
  <primary key on ResultId>
  <foreign key into TestInBatch on column TestInBatchId>

答案 1 :(得分:1)

创建一个TestBatch表,其中包含与特定批次关联的测试。使用该表的PK作为结果表中的FK。

您在任何情况下都需要TestBatch,因为与特定批次关联的测试是您需要捕获的历史时刻。每次构建新批次时,可能会添加新测试,但您不希望它们与早期完成的批次相关联。

TestBatch连接到测试和批处理,包含BatchID,TestID和它自己的ID。然后结果表包含来自TestBatch的ID作为其外键。

因此,要查看针对此的结果,您可以将结果连接到TestBatch,然后从Test表和Batch表中获取描述性详细信息,如下所示:

Select r.ResultId, R.Col1, r.col2, b.BatchId, b.batchdate, t.testId, t.Test_description
From Results r
join TestBatch tb on r.TestBatchid = tb.TestBatchid
join Batch b on tb.batchid = b.batchid
join Test t on tb.testid = t.testid

类型可能主要用于在创建批处理时为TestBatch创建记录。 并加入到上面按类型过滤在这种情况下,您通常只想加入Type to Batch或Test,但不能同时加入。

向您展示一些如何使用数据(忘记现在的结果表以及您可以在@PhillipKelleys中看到的官方FK和PK的优秀答案)代码是为SQL服务器编写的,我使用了临时表,所以你在提交结构之前可以玩一下,但如果你想创建真正的表,请删除#符号。身份是SQl服务器用来创建自动生成的字段替换为数据库后端的代码来执行类似的操作。:

Create table #type (Typeid int identity, TypeDescription varchar(100))

Insert into #type (TypeDescription)
values ('Geography'),  ('History'),  ('Biology'),  ('Math')

Create table #Batch (BatchID int identity, TypeID int, BatchDate datetime)

insert into #Batch (TypeID, BatchDate)
values (1, getdate()-1), (1, getdate() +2) , (4, getdate())

Create table  #Test (testId int identity, TestDescription varchar(50), TypeId int)
Insert into #Test (TestDescription, TypeId )
values ('fall midterm', 1), ('fall final', 1),  ('fall midterm', 3), ('fall final', 3), ('fall final', 2), ('fall midterm', 4), ('fall final', 4)

Create  table #TESTBATCH (TestBatchID int identity, TestID int, BATCHID int )

Insert into #testBatch ( BATCHID, TestID)
values(1, 1), (1, 2), (2,1), (2,2), (3,6), (3, 7)

select * from #type
select * from #Batch
select * from #test
select * from #testBatch

这将显示所有当前批次的详细信息

select B.batchdate, t.TypeDescription, te.TestDescription, t2.TypeDescription
from #testBatch tb
join #batch b on b.batchid = tb.batchid
join #type t on t.typeid = b.typeid
join #test te on te.testid = tb.testid
join #type t2 on t2.typeid = te.typeid

这将显示所有当前测试,即使是没有当前批次的测试

select  te.TestDescription, t2.TypeDescription, B.batchdate, t.TypeDescription
from #test te 
join #type t2 on t2.typeid = te.typeid
left join #testBAtch tb on te.testID = tb.testId
left join #batch b on b.batchid = tb.batchid
left join #type t on t.typeid = b.typeid