数据库表中的动态列与EAV

Ale*_*lex 8 database sqlite database-design entity-attribute-value

如果我有一个应用程序需要能够根据用户输入更改数据库模式,我正在尝试决定走哪条路.

例如,如果我有一个包含汽车属性的"汽车"对象,例如年份,型号,门等,那么如何以这种方式将其存储在数据库中,用户应该能够添加新属性?

我读到了有关EAV表的内容,他们看起来似乎是对的,但问题是,当我尝试获取由一组属性过滤的汽车列表时,查询会变得相当复杂.

我可以动态生成表吗?我看到Sqlite支持ADD COLUMN,但是当表达到许多记录时它有多快?看起来似乎没有办法删除列.我必须创建一个没有要删除的列的新表,并从旧表中复制数据.这在大桌子上肯定很慢:(

ant*_*nio 12

我将假设SQLite(或其他关系DBMS)是一个要求.

EAVS

我使用过EAV和通用数据模型,我可以说数据模型非常混乱,从长远来看很难处理.

假设您设计了一个包含三个表的数据模型:实体,属性和_entities_attributes _:

CREATE TABLE entities
(entity_id INTEGER PRIMARY KEY, name TEXT);

CREATE TABLE attributes 
(attribute_id INTEGER PRIMARY KEY, name TEXT, type TEXT);

CREATE TABLE entity_attributes 
(entity_id INTEGER, attribute_id INTEGER, value TEXT, 
PRIMARY KEY(entity_id, attribute_id));
Run Code Online (Sandbox Code Playgroud)

在这个模型中,实体表将保存您的汽车,属性表将包含您可以关联到您的汽车的属性(品牌,型号,颜色......)及其类型(文本,数字,日期,... ),_entity_attributes_将保存给定实体的属性值(例如"red").

考虑到使用此模型,您可以存储任意数量的实体,它们可以是汽车,房屋,计算机,狗或其他任何东西(好吧,也许您需要在实体上使用新字段,但这对于示例来说已经足够了).

INSERTs很简单.您只需要插入一个新对象,一堆属性及其关系.例如,要插入具有3个属性的新实体,您需要执行7个插入(一个用于实体,三个用于属性,另外三个用于关系).

当您想要执行某个操作时UPDATE,您需要知道要更新的实体是什么,并更新所需的属性以及实体及其属性之间的关系.

如果要执行a DELETE,还需要知道要删除的实体是什么,删除其属性,删除实体与其属性之间的关系,然后删除实体.

但是当你想要执行SELECT一件事情变得讨厌时(你需要编写非常困难的查询)并且性能下降可怕.

想象一下,如您的示例所示,存储汽车实体及其属性的数据模型(假设我们要存储品牌和型号).A SELECT将查询您的所有记录

SELECT brand, model FROM cars;
Run Code Online (Sandbox Code Playgroud)

如果您在示例中设计通用数据模型,则SELECT查询所有存储的汽车将非常难以编写,并且将涉及3个表连接.查询将执行非常糟糕.

另外,请考虑属性的定义.您的所有属性都存储为TEXT,这可能是一个问题.如果有人犯了错误并将"红色"存储为价格怎么办?

索引是你无法受益的另一件事(或者至少没有它想要的那么多),并且随着存储的数据的增长它们是非常必要的.

正如你所说,作为开发人员的主要担忧是查询真的难以编写,难以测试和难以维护(客户需要支付多少才能购买所有红色,1980年,你拥有的庞蒂亚克火鸟?) ,当数据量增加时,性能会很差.

使用EAV的唯一优势是,您可以使用相同的型号存储几乎所有东西,但就像有一个装满了你需要找到一个具体的小物品的东西的盒子.

另外,为了使用来自权威的论证,我会说Tom Kyte强烈反对通用数据模型:http : //tkyte.blogspot.com.es/2009/01/this-should-be-fun-to-watch. html https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:10678084117056

数据库表中的动态列

另一方面,正如您所说,您可以动态生成表,在需要时添加(和删除)列.在这种情况下,您可以创建一个包含您将使用的基本属性的汽车表,然后在需要时动态添加列(例如排气次数).

缺点是您需要向现有表添加列,并(可能)构建新索引.

正如您所说,这个模型在使用SQLite时还有另一个问题,因为没有直接的方法来删除列,您需要按照http://www.sqlite.org/faq.html#q11上的说明执行此操作.

BEGIN TRANSACTION;
CREATE TEMPORARY TABLE t1_backup(a,b);
INSERT INTO t1_backup SELECT a,b FROM t1;
DROP TABLE t1;
CREATE TABLE t1(a,b);
INSERT INTO t1 SELECT a,b FROM t1_backup;
DROP TABLE t1_backup;
COMMIT;
Run Code Online (Sandbox Code Playgroud)

无论如何,我真的不认为你需要删除列(或者至少它将是一个非常罕见的场景).也许有人将数量添加为列,并存储具有此属性的汽车.您需要确保您的任何汽车都具有此属性,以防止在删除列之前丢失数据.但这当然取决于你的具体情况.

此解决方案的另一个缺点是,您需要为要存储的每个实体使用一个表(一个表用于存储汽车,另一个用于存储房屋,等等......).

另一种选择(伪通用模型)

第三种选择可以是具有伪通用模型,其中表具有用于存储实体的id,名称类型的列,以及用于存储实体的属性的给定(足够)通用列数.

让我们说你创建一个这样的表:

CREATE TABLE entities
(entity_id INTEGER PRIMARY KEY,
 name TEXT,
 type TEXT,
 attribute1 TEXT,
 attribute1 TEXT,
 ...
 attributeN TEXT
 );
Run Code Online (Sandbox Code Playgroud)

在此表中,您可以存储任何实体(汽车,房屋,狗),因为您有一个类型字段,您可以根据需要为每个实体存储任意数量的属性(在本例中为N).

如果您需要知道当type为"red" 时属性 37代表什么,则需要添加另一个表,该表将类型和属性与属性描述相关联.

如果您发现某个实体需要更多属性,该怎么办?然后只需将新列添加到实体表(attributeN + 1,...).

在这种情况下,属性总是存储为TEXT(如在EAV中),但有缺点.

但是你可以使用索引,查询非常简单,模型对于你的情况足够通用,而且一般来说,我认为这个模型的好处大于缺点.

希望能帮助到你.


跟进评论:

使用伪通用模型,您的实体表将包含许多列.从文档(https://www.sqlite.org/limits.html),SQLITE_MAX_COLUMN的默认设置是2000.我使用了超过100列的SQLite表,性能很好,所以40列不应该很大处理SQLite.

正如您所说,对于大多数记录,您的大多数列都将为空,并且您需要为所有列建立索引以获得性能,但您可以使用部分索引(https://www.sqlite.org/partialindex.html).这样,即使行数很多,索引也会很小,每个索引的选择性都会很高.

如果实现只有两个表的EAV,表之间的连接数将少于我的示例,但查询仍然很难编写和维护,并且您需要执行多个(外部)连接来提取数据,当你存储大量数据时,即使有很好的索引,也会降低性能.例如,假设您想要获得汽车的品牌,型号和颜色.你SELECT会看起来像这样:

SELECT e.name, a1.value brand, a2.value model, a3.value color
FROM entities e
LEFT JOIN entity_attributes a1 ON (e.entity_id = a1.entity_id and a1.attribute_id = 'brand')
LEFT JOIN entity_attributes a2 ON (e.entity_id = a2.entity_id and a2.attribute_id = 'model')
LEFT JOIN entity_attributes a3 ON (e.entity_id = a3.entity_id and a3.attribute_id = 'color');
Run Code Online (Sandbox Code Playgroud)

如您所见,对于要查询(或过滤)的每个属性,您需要一个(左)外连接.使用伪通用模型,查询将如下所示:

SELECT name, attribute1 brand, attribute7 model, attribute35 color
FROM entities;
Run Code Online (Sandbox Code Playgroud)

另外,请考虑_entity_attributes_表的潜在大小.如果每个实体可能有40个属性,可以说每个实体都有20个非空.如果你有10,000个实体,你的_entity_attributes_表将有200,000行,你将使用一个巨大的索引来查询它.使用伪通用模型,每列将有10,000行和一个小索引.


Nev*_*uyt 5

这一切都取决于您的应用程序需要对数据进行推理的方式.

如果您需要运行需要进行复杂比较的查询或者事先不知道其架构的数据的连接,那么SQL和关系模型很少适合.

例如,如果您的用户可以设置任意数据实体(例如您的示例中的"汽车"),然后想要找到发动机容量大于2000cc的车辆,至少有3门,在2010年之后制造,其当前所有者是作为"小老太太"表的一部分,我不知道在SQL中这样做的优雅方式.

但是,您可以使用XML,XPath等实现类似的功能.

如果您的应用程序在具有已知属性的数据实体上设置了一组,但用户可以扩展这些属性(对错误跟踪器等产品的常见要求),则"添加列"是一个很好的解决方案.但是,您可能需要发明一种自定义查询语言,以允许用户查询这些列.例如,Atlassian Jira的错误跟踪解决方案具有JQL,一种用于查询错误的类似SQL的语言.

如果您的任务是存储然后显示数据,则EAV非常棒.然而,即使是中等复杂的查询在EAV模式中变得非常困难 - 想象一下如何执行上面的组成示例.