我继承的应用程序跟踪对材料样本执行的实验室测试结果。数据存储在单个表(tblSampleData)中,主键为SampleID,235列表示潜在的测试结果。问题是每个样本只执行少量测试,因此每行包含200多个空值。实际上,还有第二个类似的表(tblSampleData2),其中包含另外215个主要为空的列和一个SampleID的主键。这两个表具有一对一的关系,大多数SampleID在两个表中都有一些数据。 但是,对于每个SampleID,都有400个空列!
这是错误的数据库设计吗?如果是这样,哪个正常形式规则被打破?如何查询此表以确定哪些列通常与数据一起填充?我的目标是拥有45个表,10列,空值更少。我怎样才能做到这一点?如何避免破坏现有应用程序?
到目前为止,这些表有大约200,000个样本记录。用户要求我为更多测试添加更多列,但我宁愿构建一个新表。这是明智的吗?
答案 0 :(得分:9)
我看过文章/论文表明在数据库中简单地使用NULL会破坏第一个普通形式。
从我从数据库描述中收集的内容来看,更好的设计可能如下:
包含始终与样本关联的字段的样本表。例如,
Sample
------
SampleID
SampleDate
SampleSource
然后,测试类型表,每种类型的测试都有一个条目可以执行。
TestType
--------
TestTypeID
TestName
MaximumAllowedValue
最后,有一个中间表,表示上述两个表之间的多对多关系,并保存测试结果。
TestResult
----------
SampleID
TestTypeID
TestResult
这将消除空值,因为TestResult表只包含对每个样本实际执行的测试的条目。我曾经设计过一个数据库,其目的与我相信你正在做的几乎相同,这就是我采用的方法。
答案 1 :(得分:4)
您可以使用众所周知的Entity Attribute Value model(EAV)。关于何时适合使用EAV的描述非常适合您的使用案例:
此数据表示类似于存储稀疏矩阵的节省空间的方法,其中仅存储非空值。
生产数据库中的EAV建模的一个示例可以看作可以应用于患者的临床发现(过去的历史,现在的抱怨,身体检查,实验室测试,特殊调查,诊断)。在所有医学专业中,这些可以达到数十万(每月开发新的测试)。然而,大多数去看医生的人的发现相对较少。
在您的具体案例中:
EAV有一些严重的缺点并且会产生许多困难,因此只有在适当时才应用。如果您需要在一行中返回特定样本的所有测试结果,则不应使用它。
在不破坏现有应用程序的情况下,很难修改数据库以使用此结构。
答案 2 :(得分:1)
仅仅因为没有正常形式的规则被破坏并不意味着数据库设计并不坏。通常情况下,使用较小行更紧密的设计会更好,因为这样可以在页面中放入更多行,因此数据库的工作量就会减少。使用当前设计,数据库服务器必须投入大量空间来保存空值。
避免破坏现有应用程序是困难的部分,如果其他应用程序只需要读访问权限,您可以编写一个看起来与旧表相同的视图。
答案 3 :(得分:1)
如果您确实更改了表结构,我建议使用名为tblSampleData的视图,该视图返回与现在表相同的数据。这将保留一些兼容性。
答案 4 :(得分:1)
我不确定设计真的那么糟糕。实际上,NULL值应该相对便宜存储。在SQL Server中,每行都有一个内部位字段(或多个字段),用于指示哪些列值为NULL。
如果不需要改进应用程序的性能,并且由于更改表模式而导致重构的成本效益不是正面的,为什么要更改它呢?
答案 5 :(得分:1)
您可能甚至不需要此数据的RDBMS 。将数据存储在结构化二进制文件或DBM / ISAM表中。
它没有正常化。通常,缺乏正常化是所有问题的根源。但是在这种情况下,缺乏规范化并不是世界末日,因为这些数据是“只读”,只有一个密钥,而且它与其他任何东西无关。所以更新异常不应该是一个担心。您只需担心原始数据是一致的。
如果您将NULL视为整个应用中具有相同含义的“特殊值”,那么所有这些NULL 都没有太大的错误。未收集数据。数据不可用。对象拒绝回答问题。数据异常。数据尚待处理。众所周知,数据是未知的。对象说他们不知道......等等你明白了。没有定义的原因而没有定义的含义允许NULL是非常错误的。
我说它正常化了。定义特殊值并创建一个大型表。或者,为VB和PHP程序员留下NULL,并正确地拆分数据。如果需要支持旧代码,请创建一个VIEW以备份数据。根据你的描述,你谈论的是几个小时的工作才能使这件事变得正确。这不是一件坏事。
答案 6 :(得分:0)
我选择1个主表,每个样本有1行,它将包含每个样本应该具有的所有列:
Sample
-------
SampleID int auto increment PK
SampleComment
SampleDate
SampleOrigin
....
然后我为每个不同的测试或类似测试的“类”添加一个表,并包括与那些相关的所有列(使用实际测试名称而不是XYZ):
TestMethod_XYZ
---------------
SampleID int FK Sample.SampleID
MeltTemp
BurnTemp
TestPersonID
DateTested
...
TestMethod_ABC
---------------
SampleID int FK Sample.SampleID
MinImpactForce
TestPersonID
DateTested
....
TestMethod_MNO
---------------
SampleID int FK Sample.SampleID
ReactionYN
TimeToReact
ReactionType
TestPersonID
DateTested
...
搜索结果时,您将搜索应用的测试方法表并加入实际样本表。
答案 7 :(得分:0)
假设您拥有40个测量通道的测试机器X.如果您知道在每次测试中测试人员只使用几个频道,您可以将设计更改为:
tblTest:testId,testDate tblResult:testId,machineId,channelId,Result
您始终可以使用交叉表检索透明布局。
答案 8 :(得分:0)
EAV是一个选项,但查询会杀了你。
是否可以将数据迁移到像MongoDB这样的NoSQL DB?我相信这将是解决您问题的最有效和最简单的方法。既然你提到你基本上在做CRUD查询,那么NoSQL应该非常高效。
答案 9 :(得分:0)
目前的设计很差。通常,具有大量NULL值的数据库表示设计不良,违反了第4范式。但是设计的最大问题不是违反正常原则,而是添加新的测试类型需要更改数据库结构,而不是简单地添加一些数据到几个“定义”测试的表。更糟糕的是,它需要对现有表进行结构更改,而不是添加新表。
您可以通过调整其他人所描述的键值系统来实现完美的第四范式。但是,您可以通过执行以下任一操作来大幅改进数据库的设计并保持理智(在使用没有ORM的键值系统时很难做到这一点):
尝试发现代表任何单个测试所需的最大数量的测量。如果测试返回了不同的数据类型,则需要发现最大测试返回的每种数据类型的最大数量值。创建一个仅包含这些列的表,标记为Meas1,Meas2等。而不是您需要的400列,可能是10.或40.然后创建一组表,描述每个列对每个测试“意味着什么”。此信息可用于提供有意义的提示和报告列标题,具体取决于所存储的测试类型。这不会完全消除NULL,但会大大减少它们,并且只要任何新测试可以“适合”您指定的测量数量,就可以将新测试添加为数据而不是结构更改。
< / LI>发现每个测试的实际测量列表,并创建一个单独的表来保存每个测试的结果(基本信息,如测试ID,运行它的人,时间等仍然会进入单个表)。这是一个多表继承模式(我不知道它是否有真实姓名)。您仍然需要为每个新测试创建一个新的“数据”表,但现在您不会触及其他现有的生产表,您将能够获得完美的正常形式。
我希望这提供了一些入门的想法。