这个键值的数据库模式有名称吗?

pro*_*ype 70 schema database-design ontology eav

我们处理来自客户的常规数据馈送,该客户刚刚将其数据库从一种看起来很熟悉的表单(每个实体一行,每个属性一列)重构为一个我似乎不熟悉的表单(每个实体每个属性一行):

之前:每个属性一列

ID   Ht_cm   wt_kg   Age_yr  ... 
1      190      82     43    ...
2      170      60     22    ...
3      205      90     51    ...
Run Code Online (Sandbox Code Playgroud)

之后:所有属性的一列

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     ...
Run Code Online (Sandbox Code Playgroud)

这个数据库结构有名字吗?有哪些相对优势?旧方法似乎更容易对特定属性(非空、非负等)设置有效性约束,并且更容易计算平均值。但是我可以看到在不重构数据库的情况下添加新属性可能会更容易。这是构建数据的标准/首选方式吗?

Sim*_*rts 96

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

以下是不应该使用 EAV的原因列表:

  • 您不能使用数据类型。值是日期、数字还是货币(十进制)都没有关系。它总是会被强制使用 varchar。这可以是从轻微的性能问题到巨大的胃痛(曾经不得不在月度汇总报告中追逐一美分的变化?)。
  • 您不能(轻松)强制执行约束。它需要大量的代码来强制执行“每个人的身高都需要在 0 到 3 米之间”或“年龄必须不为空且 >= 0”,而不是每个约束都需要 1-2 行在正确建模的系统中。
  • 与上述相关,您不能轻易保证获得每个客户所需的信息(一个客户可能缺少年龄,然后下一个可能缺少他们的身高等)。你可以做到,但它比SELECT height, weight, age FROM Client where height is null or weight is null.
  • 再次相关,重复数据更难检测(如果他们为一个客户端提供两个年龄会发生什么?对数据进行反EAV,如下所示,如果您将一个属性加倍,将为您提供两行结果。如果一个客户端有两个属性相互独立的条目,你会得到4个从下面的查询行)。
  • 你甚至不能保证属性名称是一致的。“Age_yr”可能会变成“AGE_IN_YEARS”或“age”。(诚​​然,当您收到提取物时,与人们插入数据时相比,这不是什么问题,但仍然如此。)
  • 任何类型的非平凡查询都是一场彻头彻尾的灾难。要关联一个三属性 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"
Run Code Online (Sandbox Code Playgroud)

到:

SELECT c.ID, c.Ht_cm, c.Wt_kg, c.Age_yr
FROM Client c
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • @JoelBrown 你现在不在乎,但如果在路上一位副总裁要求知道目录中有多少衬衫同时有棕色纽扣和纽扣领,那么写起来会很麻烦。EAV 本身通常表明缺乏计划或远见。 (10认同)
  • +1 有一个小注意:如果将不同类型的值放在不同的表中,则可以使用数据类型(好吧,不是经典的 EAV,而是一种改进)。(但接下来还有一个问题:你怎么知道一个新属性的类型?) (8认同)
  • 同意,但我想补充一点,当您保留与系统语义无关的事物列表(不仅仅是无模式)时,EAV 也是一种很好的使用方法。例如,需要存储和列出产品功能的在线产品目录。您有一个要反刍的键/值对列表,但系统实际上并不知道或关心这些键或值的含义。在这种情况下,EAV 的危险是无关紧要的。 (4认同)
  • @JoelBrown 如果您的业务需求或您存储的数据发生变化,**您的数据模型也应该发生变化**。您的数据模型不应该一成不变。此外,对于关系数据库,99% 的人使用 EAV 时,他们的推理归结为“我不想花时间考虑如何存储我的数据”而不是“考虑我知道的所有数据库模式和模型, EAV 最适合这个数据集”。重复一遍 - 在 * 有 * 情况下 EAV 是有用的(甚至可能是“正确”的答案),但它们很少而且相距甚远。 (4认同)
  • @JoelBrown 我并不反对它有(非常小非常窄)的用途。但是如果信息可能会以任何结构化的方式被查询,那么它可能不应该在 EAV 中 (2认同)
  • 最好能更好地平衡答案。可以根据属性列表创建和以编程方式维护视图,以减少连接的手工劳动。Sql_variant 可用于代替字符串并用于索引。一个简单的数字/字符串/日期/propertyid/objectId 视图可以提供给 C#,突然间一个巨大的动态管道可用于查询(永远不需要更新 edmx 模式)。C# 上的包装器,用于使用字典进行解码和访问。一个真实的索引可以为每个属性值提供一个中等准确度的虚拟索引。同意约束是一个巨大的缺点。 (2认同)

Nei*_*gan 19

实体属性值(EAV)

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

以下是您的替代方案:

  1. 使用数据库表继承

  2. 使用 XML 数据和SQLXML 函数

  3. 使用 nosql 数据库,如 HBase

  • 对于大多数用例来说绝对是一种反模式。如果您有一个非常小的数据集并且性能无关紧要,它可能适合您。 (3认同)

Erw*_*ter 16

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

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

需要额外的模块 hstore。看:

从 Postgres 9.2 开始,它还有json类型和大量的功能(其中大部分是在 9.3 中添加的)。

Postgres 9.4 添加了(很大程度上优越的)“二进制 JSON”数据类型jsonb。具有高级索引选项。


Mel*_*YRE 11

看到 EAV db 模型如何受到批评,甚至被某些人视为“反模式”,这很有趣。

就我而言,主要缺点是:

  • 如果您参与一个不久前已经开始使用 EAV 的项目,则学习曲线会更陡峭。实际上,查询很困难,因为您大大增加了连接(和表)的数量,因此需要更多时间来理解 . 看看 Magento 项目,看看项目外部的开发人员如何在 DB 上工作很艰难,但文档却得到了很好的维护。
  • 不适合报告,如果您需要获取姓名以“M”开头的人数等...

但是,您绝对不应该放弃此解决方案,原因如下:

  • 西蒙谈到了一个叫做“不断变化的需求”的怪物。我喜欢这个表达:)。恕我直言,这正是 EAV 可能是一个不错的候选者的原因,因为这非常适合“更改”,因为您可以很容易地添加任意数量的属性。当然,这取决于我们正在更改的要求。如果我们谈论的是一项全新的业务,当然您必须审查您的数据模型,但 EAV 提供了很大的灵活性。仅仅因为它要求更严格,并不意味着这不那么有趣。
  • 还有人说“你不能使用数据类型”。:这是错误的您可能有多个值表,每个数据类型一个。然后,您必须在属性表中指定哪种数据类型是您的属性。事实上,经典的关系/EAV 与类关系的混合在数据库设计中提供了很多有趣的潜力。

  • 第一个遇到的 EAV 设计的学习曲线更陡峭。之后,都长得一模一样。 (2认同)

Tar*_*ryn 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);
Run Code Online (Sandbox Code Playgroud)

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

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;
Run Code Online (Sandbox Code Playgroud)

参见SQL Fiddle with Demo

如果您无权访问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
Run Code Online (Sandbox Code Playgroud)

参见SQL Fiddle with Demo

这两个查询都会在结果中返回数据:

| ID | HT_CM | WT_KG | AGE_YR |
-------------------------------
|  1 |   190 |    82 |     43 |
|  2 |   170 |    60 |     22 |
|  3 |   205 |    90 |     51 |
Run Code Online (Sandbox Code Playgroud)