此键值数据库模式有名称吗?


68

我们处理来自客户的例行数据馈送,该客户只是将其数据库从一种看起来很熟悉的形式(每个实体一行,每个属性一列)重构为一个我不熟悉的形式(每个实体每个属性一行,):

之前:每个属性一列

ID   Ht_cm   wt_kg   Age_yr  ... 
1      190      82     43    ...
2      170      60     22    ...
3      205      90     51    ...

之后:所有属性的一列

ID    Metric   Value
 1     Ht_cm     190
 1     Wt_kg     82
 1     Age_yr    43
 1      ...
 2     Ht_cm     170
 2     Wt_kg     60
 2     Age_yr    22
 2     ...
 3     Ht_cm     205
 3     Wt_kg     90
 3     Age_yr    51
 3     ...

这个数据库结构有名称吗?相对优势是什么?使用旧方法似乎更容易将有效性约束置于特定属性(非null,非负等)上,并且更容易计算平均值。但是我可以看到在不重构数据库的情况下添加新属性可能会更容易。这是结构化数据的标准/首选方法吗?

Answers:


91

它被称为Entity-Attribute-Value(有时也称为“名称-值对”),当人们在关系数据库中使用EAV模式时,这是“方孔中的圆钉”的经典案例。

这是为什么不应该使用EAV 的列表:

  • 您不能使用数据类型。值是日期,数字还是金钱(十进制)都没有关系。总是要强制使用varchar。从轻微的性能问题到严重的痛楚,这可能是什么(曾经不得不在每月汇总报告中追逐1美分的费用吗?)。
  • 您不能(轻松)实施约束。它需要大量的代码来执行“每个人的身高必须在0到3米之间”或“年龄必须不为null且> = 0”,而不是每一个约束都会有1-2行在正确建模的系统中。
  • 与上述相关,您不能轻易保证您获得每个客户所需的信息(一个客户可能缺少年龄,而另一个客户可能缺少其身高等)。您可以做到,但是要比这困难得多SELECT height, weight, age FROM Client where height is null or weight is null
  • 再次相关,重复数据很难检测到(如果一个客户有两个年龄,会发生什么情况?如果您将一个属性加倍,则对数据进行如下EA去除操作将获得两行结果。如果一个客户有两个属性相互独立的条目,你会得到4个从下面的查询行)。
  • 您甚至不能保证属性名称是一致的。“ Age_yr”可能会变成“ AGE_IN_YEARS”或“年龄”。(诚​​然,与人们正在插入数据相比,当您接收摘录时,这不是一个大问题。)
  • 任何类型的非平凡查询都是完全的灾难。为了使三个属性的EAV系统相关联,以便您可以以合理的方式对其进行查询,需要对EAV表进行三个联接。

相比:

SELECT cID.ID AS [ID], cH.Value AS [Height], cW.Value AS [Weight], cA.Value AS [Age]
FROM (SELECT DISTINCT ID FROM Client) cID 
      LEFT OUTER JOIN 
    Client cW ON cID.ID = cW.ID AND cW.Metric = "Wt_kg" 
      LEFT OUTER JOIN 
    Client cH ON cID.ID = cH.ID AND cW.Metric = "Ht_cm" 
      LEFT OUTER JOIN 
    Client cA ON cID.ID = cA.ID AND cW.Metric = "Age_yr"

至:

SELECT c.ID, c.Ht_cm, c.Wt_kg, c.Age_yr
FROM Client c

这是您何时应该使用EAV的(很简短)列表:

  • 绝对无法解决时,您必须在数据库中支持无模式的数据。
  • 当您只需要存储“东西”而不必期望以更结构化的形式使用它时。但是要当心,这个怪物称“变化的需求”。

我知道我只是用了整篇文章来详细说明为什么在大多数情况下EAV是一个糟糕的主意-但是在某些情况下需要/不可避免。但是,在大多数情况下(包括上面的示例),麻烦将远远超过其价值。如果您需要广泛支持EAV类型的数据输入,则应考虑将它们存储在键值系统中,例如Hadoop / HBase,CouchDB,MongoDB,Cassandra,BerkeleyDB。


7
+1,但有一点注意:如果将不同类型的值放在不同的表中(可以,不是传统的EAV,而是某种改进),则可以使用数据类型。(但是接下来还有一个问题:您如何知道新属性的类型?)
dezso 2012年

4
同意,但是我要补充一点,当您保留与系统在语义上不相关的事物列表(不仅仅是无模式的事物)时,EAV也是一种很好的使用方法。例如,需要存储和列出产品功能的在线产品目录。您有一个要重新设定键/值对的列表,但是系统实际上并不知道或不在乎这些键或值是关于什么的。在那种情况下,EAV的危险是无关紧要的。
乔尔·布朗

10
@JoelBrown您现在不在乎,但是如果在路上一位副总裁要求知道目录中有多少件衬衫既有棕色纽扣又有纽扣领子,那将是写查询的麻烦。EAV本身通常表示缺乏计划或远见。
JNK 2012年

2
@JoelBrown我不同意它的用法(非常小,非常狭窄)。但是,如果可能以任何结构化方式查询该信息,则可能不应该使用EAV
JNK

4
@JoelBrown如果您的业务需求或要存储的数据发生更改,那么数据模型也应更改。您的数据模型不应一成不变。另外,对于关系数据库,人们有99%的时间使用EAV时将其推理归结为“我不想花时间思考如何存储数据”,而不是“考虑所有我知道的数据库模式和模型, EAV最适合此数据集”。重复一遍-在某些情况下,EAV很有用(甚至可能是“正确”的答案),但它们之间相距甚远。
Simon Righarts 2012年

18

实体属性值(EAV)

包括我在内的许多人都认为这是一种反模式。

这是您的选择:

  1. 使用数据库表继承

  2. 使用XML数据和SQLXML函数

  3. 使用Nosql数据库,例如HBase


3
绝对是大多数用例的反模式。如果您的数据集非常少,而性能无关紧要,则可能对您有用。
JNK 2012年

16

在PostgreSQL中,处理EAV结构的一种非常好的方法是附加模块hstore,该模块可用于8.4版或更高版本。我引用该手册:

该模块实现了hstore用于在单个PostgreSQL值中存储键/值对集合的数据类型。这在各种情况下很有用,例如具有许多很少检查的属性的行或半结构化数据。键和值只是文本字符串。

从Postgres 9.2开始,它还具​​有json类型和大量功能(大多数都在9.3中添加)。

Postgres 9.4在jsonb选项列表中添加了(大大更好!)“二进制JSON”数据类型。具有高级索引选项。


10

如果您有一个使用EAV结构的数据库,则可以通过多种方式查询数据。

@Simon的答案已经显示了如何使用多个联接执行查询。

使用的样本数据:

CREATE TABLE yourtable ([ID] int, [Metric] varchar(6), [Value] int);

INSERT INTO yourtable ([ID], [Metric], [Value])
VALUES (1, 'Ht_cm', 190),
    (1, 'Wt_kg', 82),
    (1, 'Age_yr', 43),
    (2, 'Ht_cm', 170),
    (2, 'Wt_kg', 60),
    (2, 'Age_yr', 22),
    (3, 'Ht_cm', 205),
    (3, 'Wt_kg', 90),
    (3, 'Age_yr', 51);

如果您使用的是具有PIVOT功能(SQL Server 2005 + / Oracle 11g +)的RDBMS,则可以通过以下方式查询数据:

select id, Ht_cm, Wt_kg, Age_yr
from
(
  select id, metric, value
  from yourtable
) src
pivot
(
  max(value)
  for metric in (Ht_cm, Wt_kg, Age_yr)
) piv;

参见带有演示的SQL Fiddle

如果您无权访问某个PIVOT函数,则可以将聚合函数与CASE语句一起使用以返回数据:

select id,
  max(case when metric ='Ht_cm' then value else null end) Ht_cm,
  max(case when metric ='Wt_kg' then value else null end) Wt_kg,
  max(case when metric ='Age_yr' then value else null end) Age_yr
from yourtable
group by id

参见带有演示的SQL Fiddle

这两个查询都将返回结果中的数据:

| ID | HT_CM | WT_KG | AGE_YR |
-------------------------------
|  1 |   190 |    82 |     43 |
|  2 |   170 |    60 |     22 |
|  3 |   205 |    90 |     51 |

10

很高兴看到EAV db模型是如何被批评甚至被某些人视为“反模式”的。

就我而言,主要缺点是:

  • 如果您上前已经开始使用EAV的项目,则学习曲线会更加陡峭。确实,随着您大大增加联接(和表)的数量,查询变得很困难,因此将需要更多的时间来理解。只需看一下Magento项目,看看该项目外部的开发人员如何在DB上度过艰难的时光,而文档却能得到很好的维护。
  • 如果您需要以“ M”等开头的姓名,则不适合报告

但是,您绝对不应丢弃此解决方案,这就是原因:

  • 西蒙谈到了被称为“需求变化”的怪物。我喜欢这个表情:)。恕我直言,这正是EAV可能是一个不错的选择的原因,因为它非常适合“更改”,因为您可以轻松地添加任意数量的属性。当然,这取决于我们正在更改的要求。如果我们正在谈论一个全新的业务,那么您当然必须检查一下dataModel,但是EAV提供了很多灵活性。仅仅因为它要求更严格,并不意味着它就没有那么有趣了。
  • 也有人说“您不能使用数据类型”。:这是错误的您可能有几个值表,每个dataType一个。然后,您必须在属性表中指定哪种dataType是您的属性。实际上,经典关系/ EAV与类关系的混合在数据库设计中提供了许多有趣的潜力。

2
对于第一次遇到的EAV设计,学习曲线更陡峭。在那之后,一切看起来都一样。
ypercubeᵀᴹ

1
临时评论:我不明白为什么“不适合举报”这一说法。EAV似乎很适合报告。从eav.values中选择ObjectId,其中propertyId =名称和值,例如'm%'。对虚拟模式的更改(例如,添加属性)可以包含在任何动态报告界面(如下拉列表)中,而无需重新编译。
crokusek 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.