如何在实体-属性-值设计中处理不同的数据类型(例如,单个表格具有多个列或每种数据类型具有多个表格)?

29

我想使用实体-属性-值(EAV)方法创建一个患者/样本元数据表。

问题: 基于属性,如何处理的不同列类型(例如字符串、数字或外键到字典表)?

注意:我并没有问是否使用EAV方法。我已经查看了其他SO问题参考文献,并且认为这是我的用例的最佳方法(例如,我不想为每个属性创建单独的列或表,可能有数百个)。但是,如果给出全面的示例,我一定会重新考虑其他设计。

代表性数据

一个患者/样本(实体)可以有多个元数据属性(例如实验室位置、生存率、肿瘤类型),每个属性都有不同的类型(例如VARCHARNUMBERFOREIGN_KEY*)。

*FOREIGN_KEY表示这个类型是一个外键ID(INTEGER),指向一个值的字典表(例如,一个包含10种可能肿瘤类型的列表)。因此,实验室位置可以是VARCHAR,因为我不关心规范化这些值。但肿瘤类型应该具有一定程度的验证。

我的表布局可能如下所示:

CREATE TABLE patients (
  patient_id INTEGER CONSTRAINT pk_patients PRIMARY KEY,
  patient_name VARCHAR2(50) NOT NULL
);

CREATE TABLE metadata_attributes (
  attribute_id INTEGER CONSTRAINT pk_metadata_attributes PRIMARY KEY,
  attribute_name VARCHAR2(50) NOT NULL,
  attribute_value_type VARCHAR(50) NOT NULL -- e.g. VARCHAR, NUMBER, or ID
);

CREATE TABLE patient_metadata (
  patient_id CONSTRAINT fk_pm_patients REFERENCES patients(patient_id) NOT NULL,
  attribute_id CONSTRAINT fk_pm_attributes REFERENCES metadata_attributes(attribute_id) NOT NULL,
  attribute_value ???
);

我认为在metadata_attributes表中需要一个描述类型的列(attribute_value_type),以便知道要查看哪个列/表。

可能的方法

以下是我能想到的两种可能的方法。

方法1: 单个EAV表格,带有多个列

在patient_metadata表中创建三个不同的列 - 分别对应每个类型。

CREATE TABLE patient_metadata (
  patient_id CONSTRAINT fk_pm_patients REFERENCES patients(patient_id) NOT NULL,
  attribute_id CONSTRAINT fk_pm_attributes REFERENCES metadata_attributes(attribute_id) NOT NULL,
  attribute_varchar_value VARCHAR(50),
  attribute_number_value NUMBER,
  attribute_id_value CONSTRAINT fk_pm_values REFERENCES some_table_of_values(value_id)
);

方法2:多个EAV表

创建三个不同的patient_metadata表 - 分别为每种类型创建一个表。

CREATE TABLE patient_metadata_varchar (
  patient_id CONSTRAINT fk_pm_patients REFERENCES patients(patient_id) NOT NULL,
  attribute_id CONSTRAINT fk_pm_attributes REFERENCES metadata_attributes(attribute_id) NOT NULL,
  attribute_value VARCHAR(50) NOT NULL
);

CREATE TABLE patient_metadata_number (
  patient_id CONSTRAINT fk_pm_patients REFERENCES patients(patient_id) NOT NULL,
  attribute_id CONSTRAINT fk_pm_attributes REFERENCES metadata_attributes(attribute_id) NOT NULL,
  attribute_value NUMBER NOT NULL
);

CREATE TABLE patient_metadata_id (
  patient_id CONSTRAINT fk_pm_patients REFERENCES patients(patient_id) NOT NULL,
  attribute_id CONSTRAINT fk_pm_attributes REFERENCES metadata_attributes(attribute_id) NOT NULL,
  attribute_value CONSTRAINT fk_pm_values REFERENCES some_table_of_values(value_id) NOT NULL
);

还有其他方法吗?

是否还有其他方法可供选择?

简而言之,我想尽可能地尊重关系完整性,并允许数据库知道值的类型,以便进行基本验证。然而,我认为上述两种方法都需要某种类型的手动完整性检查(方法1需要检查仅填充了一个attribute_value列等)。

我将执行的查询类型将是典型的(例如,检索给定元数据属性的值列表,检索给定患者(实体)和元数据属性的值列表,等等)。我相信大多数情况下我需要查询值类型才能知道要查询哪个列或表。还有其他任何方式可以解决这个问题吗?

所有方法的优缺点是什么(性能、查询结构等)?

第一次发帖,提前感谢您的回复,请随时评论格式或进一步澄清!


1
嗨,我认为你正在面对所谓的多态键现象。 - kisp
2
我认为这个维基百科页面涵盖了很多内容:http://en.wikipedia.org/wiki/Entity%E2%80%93attribute%E2%80%93value_model - Rene
2个回答

5
这是一个众所周知的问题。你提到的方法存在的问题是你需要在查询之前知道属性的类型。虽然你可以管理元数据,但这并不是世界末日...
两种可能的解决方案如下:
  1. 使用 varchar2 数据类型来表示所有已知格式的数据类型。数字和字符没有问题,日期值可以以预定义的方式编写(就像在任何面向对象设计中实现 to_String() 一样)。
  2. 使用ANYDATA 数据类型。我个人曾经尝试过它,但最终决定不使用它。

10
永远不要将日期和数字存储为字符串,这样会引起很多问题。例如,某人不可避免地会编写像这样的谓词:“where attribute ='DOB' and to_date(value,'YYYY-MM-DD') < date '2000-01-01'”。它看起来合理,但非常危险,即使所有数据都是干净的。使用不同的列来存储不同的类型不会增加额外的工作量。你必须知道数据的类型才能做任何有趣的事情。 - Jon Heller
4
将数字以字符串形式存储显然并不是最佳选择,但我不同意你的说法——这是一个设计问题,“风险”的存在取决于你的数据库访问层。 - haki
谢谢,我不知道ANYDATA类型 - 看起来非常有趣!你决定不使用它的原因是什么? - lebolo
4
@lebolo 我最近使用了ANYDATA构建了一个系统,但由于几个原因不得不替换它。1)管道函数存在严重的性能问题。2)API太过频繁地要求使用PL/SQL,缺乏足够的access函数。3)类型不匹配不会引发异常,需要额外的检查。4)任何数据库工具均不支持ANYDATA,包括SQL*Plus、SQL Developer或PL/SQL调试器。5)存在未解决的错误,例如在混合32位和64位客户端和服务器时出现的问题。 - Jon Heller

4
最简单、最高效的方法是将数据库中的所有值转换为字符串。这样的问题通常很明显,即使是类型良好的列也会遇到同样的问题,通常表现为性能问题。
只需稍加注意,就可以保持排序顺序(例如,通过将日期格式化为年/月/日),而且类型的验证不应该由数据库完成,因为太晚了。负数和浮点数都很麻烦,但通常不会按可为负或浮点数的数字进行索引,并且在内存中进行排序通常很快。
如果数据类型不明显或需要下游处理器知道,则添加一个类型列。
通常,在记录写入之前,可以通过代码(好)或触发器(不太好)检查所有针对列值的完整性约束。尝试使用具有不同类型的本机功能只能让您走得更远,而且通常也不是很有用,因为值通常具有许多业务特定的约束条件,例如出生日期需要非空且在1900年之后。
为了提高性能,使用包括实体和属性作为前缀的复合索引。索引可以按实体-属性前缀进行分区,从而减少索引的额外深度带来的任何影响,它们可以很好地压缩(前缀将压缩为一到两个字节),因此大小差异很小。
从EAV表查询通常最好在视图中完成,这样可以为您解压实体,以便将结构返回到类似于预期的内容,尽管如果您处理不同的列(例如,在受大量变化的元素控制下的患者表单中),这可能是无关紧要的。然后在业务逻辑中处理可能更容易。
最后,现在这种类型的数据根本不以列导向的关系数据库方式存储。它通常作为XML(或JSON)文档存储(Oracle中的XML类型),大多数数据库提供某些本机XML处理功能,以便搜索和操作此类数据。这对于正常形式的存储和检索来说是可以的,但往往使任意查询(例如,“给我所有在过去一年中患有肺炎的60岁以上患者”)变得很慢,或需要更多的标记反向索引。尽管如此,看看面向文档/文本的方法是否是更好的解决方案还是值得的。
祝你好运!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接