如何构建实体模型以存储具有不同数据类型的任意键/值数据?

Nat*_*ley 10 database-design

我不断遇到这样的场景:使用每行键/值模型而不是刚性列/场模型将一组任意数据存储在表中是有用的.问题是,我想用正确的数据类型存储值,而不是将所有内容转换为字符串.这意味着我必须选择具有多个可空列的单个表,每个数据类型一个,或一组值表,每个数据类型一个.我也不确定是否应该使用完整的第三范式并将密钥分隔成一个单独的表,通过值表中的外键引用它们,或者是否更好地保持简单和存储值表中的字符串键并接受字符串的重复.

旧/坏:

此解决方案使得在流体环境中添加额外值成为痛苦,因为需要定期修改表.

MyTable
============================
ID    Key1    Key2    Key3
int   int     string  date
----------------------------
1     Value1  Value2  Value3
2     Value4  Value5  Value6

单表解决方案

该解决方案通过单个表提供简单性.查询代码仍然需要检查空值以确定字段存储的数据类型.可能还需要检查约束来确保只有一个值字段包含非空数据.

DataValues
=============================================================
ID    RecordID    Key    IntValue    StringValue    DateValue
int   int         string int         string         date
-------------------------------------------------------------
1     1           Key1   Value1      NULL           NULL
2     1           Key2   NULL        Value2         NULL
3     1           Key3   NULL        NULL           Value3
4     2           Key1   Value4      NULL           NULL
5     2           Key2   NULL        Value5         NULL
6     2           Key3   NULL        NULL           Value6

多表解决方案

该解决方案允许每个表更简洁的用途,尽管代码需要提前知道数据类型,因为它需要为每种数据类型查询不同的表.索引可能更简单,更有效,因为需要索引的列更少.

IntegerValues
===============================
ID    RecordID    Key    Value
int   int         string int
-------------------------------
1     1           Key1   Value1
2     2           Key1   Value4

StringValues
===============================
ID    RecordID    Key    Value
int   int         string string
-------------------------------
1     1           Key2   Value2
2     2           Key2   Value5

DateValues
===============================
ID    RecordID    Key    Value
int   int         string date
-------------------------------
1     1           Key3   Value3
2     2           Key3   Value6

你怎么解决这个问题?哪种解决方案更好?

此外,是否应将键列分隔为单独的表并通过外键引用,或者是否应将其保留在值表中并在出于某种原因更改时批量更新?

Tho*_*mas 10

首先,关系数据库不是为存储任意数据而设计的.关系模型的基本原理围绕着获取将要存储的数据性质的规范.

其次,您所建议的是实体 - 属性 - 值(EAV)的变体.EAV的问题在于数据完整性,报告,性能和维护.它们有它们的位置,但它们类似于药物:在有限的数量和狭窄的环境中使用它们可以是有益的; 太多会杀了你.

针对EAV编写查询是一个熊.因此,如果您要使用EAV,我看到它们成功的唯一情况是限制它们的使用,以便不允许任何人编写过滤特定属性的查询.也就是说,没有人被允许编写类似于的查询Where AttributeName = 'Foo'.这意味着您永远不能过滤,排序,计算,也不能将特定属性放在报表的特定位置.EAV数据只是一大堆分类数据,可以在报告中大量涌现,但就是这样.我甚至看到人们将EAV实现为Xml blob.

现在,如果您在这种情况下使用EAV,并且因为它只是一团数据,我会使用单表方法.单表方法的一个显着优点是您可以添加一个检查约束,以确保您在IntValue,StringValue或DateValue列中只有一个且只有一个值.空值不会花费太多,如果这只是大量的数据,它将不会对性能产生任何影响.此外,它将使您的查询更简单,因为您可以使用简单的case语句来返回String,Integer或DateValue.

我可以看到多表方法存在许多问题,其中最重要的是没有什么可以阻止同一属性具有多种类型的值(例如IntegerValues中的行和StringValues中的行).此外,要获取数据,您将始终必须使用三个左连接,这将使您的查询更加麻烦.

EAV的成本是纪律和警惕.它要求您的开发团队遵守纪律,在任何情况下都不会针对特定属性编写报告或查询.开发人员将从管理层那里得到很大的压力,"只是这一次"写一些过滤特定属性的东西.一旦你沿着黑暗的道路前进,它就会主导你的开发和维护.EAV必须保持一堆数据,仅此而已.如果你不能在开发团队中保持这种纪律,那么我就不会实现EAV.为了避免以后的维护噩梦,我需要为任何新专栏制定规范.一旦用户想要对报表上的特殊位置进行过滤,排序,计算或放置属性,该属性必须成为第一类列.