淹没在无海之中

时间:2010-07-01 12:33:24

标签: database-design refactoring null normalization

我继承的应用程序跟踪对材料样本执行的实验室测试结果。数据存储在单个表(tblSampleData)中,主键为SampleID,235列表示潜在的测试结果。问题是每个样本只执行少量测试,因此每行包含200多个空值。实际上,还有第二个类似的表(tblSampleData2),其中包含另外215个主要为空的列和一个SampleID的主键。这两个表具有一对一的关系,大多数SampleID在两个表中都有一些数据。 但是,对于每个SampleID,都有400个空列!

这是错误的数据库设计吗?如果是这样,哪个正常形式规则被打破?如何查询此表以确定哪些列通常与数据一起填充?我的目标是拥有45个表,10列,空值更少。我怎样才能做到这一点?如何避免破坏现有应用程序?

到目前为止,这些表有大约200,000个样本记录。用户要求我为更多测试添加更多列,但我宁愿构建一个新表。这是明智的吗?

10 个答案:

答案 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)

  1. 您可能甚至不需要此数据的RDBMS 。将数据存储在结构化二进制文件或DBM / ISAM表中。

  2. 它没有正常化。通常,缺乏正常化是所有问题的根源。但是在这种情况下,缺乏规范化并不是世界末日,因为这些数据是“只读”,只有一个密钥,而且它与其他任何东西无关。所以更新异常不应该是一个担心。您只需担心原始数据是一致的。

  3. 如果您将NULL视为整个应用中具有相同含义的“特殊值”,那么所有这些NULL 都没有太大的错误。未收集数据。数据不可用。对象拒绝回答问题。数据异常。数据尚待处理。众所周知,数据是未知的。对象说他们不知道......等等你明白了。没有定义的原因而没有定义的含义允许NULL是非常错误的。

  4. 我说它正常化了。定义特殊值并创建一个大型表。或者,为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的键值系统时很难做到这一点):

  1. 尝试发现代表任何单个测试所需的最大数量的测量。如果测试返回了不同的数据类型,则需要发现最大测试返回的每种数据类型的最大数量值。创建一个仅包含这些列的表,标记为Meas1,Meas2等。而不是您需要的400列,可能是10.或40.然后创建一组表,描述每个列对每个测试“意味着什么”。此信息可用于提供有意义的提示和报告列标题,具体取决于所存储的测试类型。这不会完全消除NULL,但会大大减少它们,并且只要任何新测试可以“适合”您指定的测量数量,就可以将新测试添加为数据而不是结构更改。

    < / LI>
  2. 发现每个测试的实际测量列表,并创建一个单独的表来保存每个测试的结果(基本信息,如测试ID,运行它的人,时间等仍然会进入单个表)。这是一个多表继承模式(我不知道它是否有真实姓名)。您仍然需要为每个新测试创建一个新的“数据”表,但现在您不会触及其他现有的生产表,您将能够获得完美的正常形式。

  3. 我希望这提供了一些入门的想法。