我应该如何规范这个数据库设计?
How should I normalize this database design?
我正在尝试设计一个数据库。我有一个在我看来已标准化为 Fourth Normal Form 的设计 - 但我仍然认为它已损坏,我无法终生想出如何修复它。
背景:我们有四种类型的测试,每种类型有几十种测试。我们 运行 分批进行测试,其中每批只包含一种类型的测试。所以一个测试结果属于一个batch,也属于一个test。它给出了这样的数据库计划:
问题在于此设计允许结果用于类型 A 的测试,但该结果位于类型 B 的批次中。
我不能做的一件事是将测试和批处理 table 合并为一个 table。每周都有一个新的批次,而测试会持续数月或数年。并且一个 Batch 可以包含许多 Test(尽管总是相同的 Type),并且一个 Test 通常在许多 Batch 中进行多次。
我可以在 Test 和 Batch 之间插入一个 many-to-many 连接,但我无法立即看出这有什么帮助。
有没有一种干净的方法 re-organise 这样我们就没有循环连接路径了?这是必要的吗?还是理想的?
还是我应该随心所欲,不再担心它? :-)
[编辑 1] 请注意,测试详细说明了它如何 运行s,谁修复了发现的问题等,这些细节在多个批次中保持不变,因此测试必须独立于它可能存在的任何批次而存在(或可能不会)运行 in.
[编辑 2] 已经指出最好有一个 TestBatch table,它给我们这样的结构:
我同意这是个好主意,但这并不能真正解决问题。它只是将问题从 Result 转移到 TestBatch。我们现在可以有一个用于 A 类测试的 TestBatch,但该 TestBatch 在 B 类的批处理中。
[Edit 3] 感谢@philip-kelley 的极好的建议,我相信我们已经有了答案。首先,我们linkTestBatch直接返回Type,因此:
这并不能立即解决问题。事实上,它使情况变得更糟——现在可能有一种类型用于测试,一种不同的类型用于批处理,第三种类型直接从 TestBatch 加入。
但第二步是将外键从 TestBatch 更改为 Test,以便它包括 Type 以及 TestID。并将外键更改为 Batch 以包括 Type 和 BatchID。
这样,我们就可以确定 TestBatch 与 Test 和 Batch 具有相同的类型。
创建一个 TestBatch table,其中包含与特定批次关联的测试。使用 table 的 PK 作为结果 table.
中的 FK
在任何情况下您都需要 TestBatch,因为与特定批次关联的测试是您需要捕获的历史时刻。每次构建新批次时,可能会添加新测试,但您不希望它们与较早的已完成批次相关联。
TestBatch 加入测试和批处理,包含BatchID、TestID 和它自己的ID。然后 Results table 包含来自 TestBatch 的 ID 作为其外键。
因此,要查看针对此的结果,您可以将结果加入 TestBatch,然后从测试和批处理 table 中获取描述性详细信息,如下所示:
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
Type 可能主要用于在创建批次时为 TestBatch 创建记录。
并加入上面以按类型过滤在这种情况下,您通常只想加入 Type to Batch 或 Test 但不能同时加入两者。
向您展示它如何处理数据(暂时忘记结果 table 以及您可以在@PhillipKelleys 优秀答案中看到的官方 FK 和 PK)代码是为 SQL 服务器,我使用了 temp tables,因此您可以在提交结构之前尝试一下,但如果您想创建真正的 tables,请删除 # 符号。 Identity 是 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
@HLGEM 的回答描述了逻辑模型,以及一些物理模型细节。支持 并执行 您的业务规则的物理实现看起来像这样。 (这是伪代码,只显示关键列——你想为名称、分数等属性添加列。实际实现细节取决于系统,可能会有点棘手,但任何 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>
我正在尝试设计一个数据库。我有一个在我看来已标准化为 Fourth Normal Form 的设计 - 但我仍然认为它已损坏,我无法终生想出如何修复它。
背景:我们有四种类型的测试,每种类型有几十种测试。我们 运行 分批进行测试,其中每批只包含一种类型的测试。所以一个测试结果属于一个batch,也属于一个test。它给出了这样的数据库计划:
问题在于此设计允许结果用于类型 A 的测试,但该结果位于类型 B 的批次中。
我不能做的一件事是将测试和批处理 table 合并为一个 table。每周都有一个新的批次,而测试会持续数月或数年。并且一个 Batch 可以包含许多 Test(尽管总是相同的 Type),并且一个 Test 通常在许多 Batch 中进行多次。
我可以在 Test 和 Batch 之间插入一个 many-to-many 连接,但我无法立即看出这有什么帮助。
有没有一种干净的方法 re-organise 这样我们就没有循环连接路径了?这是必要的吗?还是理想的?
还是我应该随心所欲,不再担心它? :-)
[编辑 1] 请注意,测试详细说明了它如何 运行s,谁修复了发现的问题等,这些细节在多个批次中保持不变,因此测试必须独立于它可能存在的任何批次而存在(或可能不会)运行 in.
[编辑 2] 已经指出最好有一个 TestBatch table,它给我们这样的结构:
我同意这是个好主意,但这并不能真正解决问题。它只是将问题从 Result 转移到 TestBatch。我们现在可以有一个用于 A 类测试的 TestBatch,但该 TestBatch 在 B 类的批处理中。
[Edit 3] 感谢@philip-kelley 的极好的建议,我相信我们已经有了答案。首先,我们linkTestBatch直接返回Type,因此:
这并不能立即解决问题。事实上,它使情况变得更糟——现在可能有一种类型用于测试,一种不同的类型用于批处理,第三种类型直接从 TestBatch 加入。
但第二步是将外键从 TestBatch 更改为 Test,以便它包括 Type 以及 TestID。并将外键更改为 Batch 以包括 Type 和 BatchID。
这样,我们就可以确定 TestBatch 与 Test 和 Batch 具有相同的类型。
创建一个 TestBatch table,其中包含与特定批次关联的测试。使用 table 的 PK 作为结果 table.
中的 FK在任何情况下您都需要 TestBatch,因为与特定批次关联的测试是您需要捕获的历史时刻。每次构建新批次时,可能会添加新测试,但您不希望它们与较早的已完成批次相关联。
TestBatch 加入测试和批处理,包含BatchID、TestID 和它自己的ID。然后 Results table 包含来自 TestBatch 的 ID 作为其外键。
因此,要查看针对此的结果,您可以将结果加入 TestBatch,然后从测试和批处理 table 中获取描述性详细信息,如下所示:
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
Type 可能主要用于在创建批次时为 TestBatch 创建记录。 并加入上面以按类型过滤在这种情况下,您通常只想加入 Type to Batch 或 Test 但不能同时加入两者。
向您展示它如何处理数据(暂时忘记结果 table 以及您可以在@PhillipKelleys 优秀答案中看到的官方 FK 和 PK)代码是为 SQL 服务器,我使用了 temp tables,因此您可以在提交结构之前尝试一下,但如果您想创建真正的 tables,请删除 # 符号。 Identity 是 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
@HLGEM 的回答描述了逻辑模型,以及一些物理模型细节。支持 并执行 您的业务规则的物理实现看起来像这样。 (这是伪代码,只显示关键列——你想为名称、分数等属性添加列。实际实现细节取决于系统,可能会有点棘手,但任何 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>