正确设计EAV数据库以获取历史数据

时间:2014-02-11 22:43:01

标签: sql foreign-keys key-value entity-attribute-value 6nf

简介

我一直在阅读EAV database,而且大多数缺点似乎都是 与真实,真实,糟糕的EAV设计或难度generating reports from the data相关。

通常当您看到有人抱怨EAV时,他们使用的表少于三个,以尝试在RDBMS中复制功能上的单独表+列。有时这意味着在单个TEXT值列中存储从小数到字符串的所有内容。如果你不小心,EAV也会对数据完整性的保护措施感到困惑。如果你不小心的话,这可能非常糟糕。

但是,EAV确实提供了一种跟踪历史数据的简便方法,并允许我们在SQL和键值存储系统之间来回移动系统的各个部分。

如果我们根据类型分隔不同的实体属性,该怎么办?这将使我们仍然可以处理 除了与特定属性和实体绑定的正确索引值之外,belongsTo,Has,HasMany和HasManyThrough关系。

考虑以下两个基本实体

products (price -> decimal, title -> string, desc -> text, etc...)
    attributes
        options
            [...]
        int
        datetime
        string
        text
        decimal
        relation
            [id,foreign_key]

users (gender -> options, age -> int, username -> string, etc...)
    attributes
        options
            [...]
        int
        datetime
        string
        text
        decimal
        relation
            [id,foreign_key]

RDBMS架构设计

众所周知,用户资料和产品是世界上最多样化的产品之一。每家公司都以不同的方式处理它们,并根据需要提供不同的“列”或“属性”。

以下是如何处理多个(嵌套和/或关系)实体的视图。

这个想法是,每个实体都有这个主属性表,然后指定如何查找和解释这些值。这允许我们处理特殊情况,例如其他实体的外键以及“选项”或十进制数字等。

entity_type {     ID,     type,//“blog”,“user”,“product”等。     created_at }

entity {
    id,
    entity_type_id, 
    created_at
}

    attr {
        id,
        entity_id,
        type,
        name,
        created_at
    }

        option {
            id,
            attr_id,
            entity_id,
            multiple, // multiple values allowed?
            name,
            created_at
        }

        attr_option {
            id
            attr_id,
            entity_id,
            option_id
            option,
            created_at
        }

        attr_int {
            attr_id,
            entity_id,
            int,
            created_at
        }

        attr_relation {
            attr_id,
            entity_id,
            entity_fk_id,
            created_at
        }

        attr_datetime {
            attr_id,
            entity_id,
            datetime,
            created_at
        }

        attr_string {
            attr_id,
            entity_id,
            var_char,
            created_at
        }

        attr_text {
            attr_id,
            entity_id,
            text,
            created_at
        }

        attr_decimal {
            attr_id,
            entity_id,
            decimal,
            created_at
        }

这样的表格允许我们永远不必UPDATE ...,因为我们可以INSERT INTO ...为每个更改值的新属性添加created_at以了解最近的值是。这非常适合保存历史数据的记录(当然仍可以例外)。

示例查询

首先,它是什么类型的实体? (用户,帖子,评论等..)

SELECT * FROM entity_type et LEFT JOIN entity e ON e.entity_type_id = et.id WHERE e.id = ?

接下来,这个实体的属性是什么? (表attr)

SELECT * FROM attr WHERE entity_id = ?

接下来,此实体的属性中存在哪些值? (attr _ ### tables)

SELECT * FROM attr_option, attr_int, attr_relation, attr_text, ... WHERE entity_id = ?
vs
SELECT * FROM attr_option WHERE entity_id = ? if( ! multiple) ORDER BY created_at DESC LIMIT 1
SELECT * FROM attr_int WHERE entity_id = ? ORDER BY created_at DESC LIMIT 1
SELECT * FROM attr_relation WHERE entity_id = ? ORDER BY created_at DESC LIMIT 1
SELECT * FROM attr_text WHERE entity_id = ? ORDER BY created_at DESC LIMIT 1
...

这个实体有什么关系?

假设我们有一个ID为34的“post”实体,我们想要它的“评论”(entity_type = 2),这可以让我们在产品实体上获取评论实体ID:

SELECT * FROM entity AS e
LEFT JOIN attr_relation AS ar ON ar.entity_id = e.id
WHERE ar.entity_id = 34 AND e.entity_type = 2;

除了多个查询(无论如何都需要键值存储),这种方法会出现什么问题?

3 个答案:

答案 0 :(得分:5)

EAV'数据库'[原文如此] 功能,以表征关系,查询关系,查询元数据,或键入检查,或维护完整性,或优化,或原子地交易,或控制并发。

软件工程原则规定,声音EAV数据库[sic]的使用完全包括定义适当的抽象(类型,运算符,进程,解释器,模块),以重建DBMS的功能。

从一个人的EAV三元组及其含义到一个(碎片化的)数据库描述的映射的机械性质使这很容易显示。

要解释Greenspun,任何足够复杂的EAV项目都包含一个特殊的,非正式指定的,错误缠身的,一半DBMS的缓慢实现。

我再说一遍:EAV是数据库及其元数据三元组中未记录的描述,没有DBMS。仅将EAV用于已证明DDL解决方案无法满足性能要求且EAV解决方案能够并且值得的数据库部分。

答案 1 :(得分:3)

以下是此设计的一些问题。

  • 您如何查询a的所有整数属性的当前值 给定实体?

  • 您如何为属于NOT NULL的属性建模?那是, 确保给定属性对于其实体是必需的,并且 如果没有该属性的值,则无法创建实体。

  • 您如何为UNIQUE列建模?假设您可以更改 属性的值,然后将其更改回原始值。

  • 如何支持引用实体的外键 除了整数主键之外的东西吗?

  • 如何将给定属性限制为查找表中的值集?

解决大部分问题的唯一方法是使用应用程序代码。这就是EAV的问题:你最终重新发明了我们认为理所当然的SQL约束。这是Inner Platform Effect反模式的一个例子:

  

内部平台效应是软件架构师倾向于创建一个可自定义的系统,以便成为他们正在使用的软件开发平台的副本,通常是一个糟糕的副本。

第六种普通形式不是EAV。在第六种普通形式中,每个属性需要一个单独的表,而不是每种数据类型。您使用具有适当名称和数据类型的常规列。将此属性存储在不同的表中使您能够存储历史修订。

这意味着您仍然无法在6NF中对NOT NULL进行建模,但至少您可以用相当传统的方式对UNIQUEFOREIGN KEY进行建模。

答案 2 :(得分:2)

"我一直在阅读有关EAV数据库的信息,大多数缺点似乎都与真实,真实,糟糕的EAV设计或从数据生成报告有关。"

生成报告的困难本质上并且不可避免地来自EAV DB所代表的事实:"人XYZ的属性BIRTHDATE的值是......" "人XYZ的属性DECEASEDATE的值是......"等等。

这不是最终用户认为数据结构用于携带关于人XYZ(或任何其他)的信息的典型形式,最终用户和数据库之间的某个地方,另外的转换(非常类似于转动,尽管不完全是100%)是必要的。每个额外的转换都是错误和性能损失的潜在来源。

"通常当你看到有人抱怨EAV时,他们使用的表少于三个,试图在RDBMS中复制功能上单独的表+列。有时这意味着在单个TEXT值列中存储从小数到字符串的所有内容。"

这只是EAV的一个缺点。属性级类型约束变得更难或不可能定义。但除此之外还有其他人。

" EAV也对数据完整性的保护措施感到困惑,如果你不小心的话可能会非常糟糕。"

这个与报告生成的难度完全相关,这与表达有意义的查询的难度完全相同,这与表达构成违反某些给定规则的场景的难度完全相同

"但是,EAV确实提供了一种跟踪历史数据的简便方法,并允许我们在SQL和键值存储系统之间来回移动系统的各个部分。"

BS&胡扯。严格应用的EAV会将时间信息移动到与其他任何常规信息相同的地方。属性。如果你不这样做,那么你不再(严格地)申请EAV。见Bill Karwin的回答:EAV!= 6NF !!!!!!!!! 6NF仍然拥有所有"结构"任何其他"常规" DB也有,EAV就是这样(参见philip的答案和Bill'内部平台'评论)从DB中有效地删除了这个结构。