28

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

问题:我应该如何根据属性处理的不同列类型(例如字符串、数字或字典表的外键) ?

注意:我不是在问是否使用 EAV 方法。我查看了其他 SO 问题参考资料,并相信这是我的用例的最佳方法(例如,我不想为每个属性创建单独的列或表- 可能有数百个)。但是,如果给出一个综合示例,我肯定会重新考虑其他设计。

代表性数据

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

*FOREIGN_KEY表示此值类型是INTEGER字典表(例如 10 种可能的肿瘤类型的列表)的外键 ID ( )。所以实验室的位置可以是因为我不关心规范化这些值。但肿瘤类型应该有一定程度的验证。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 列,等等)。

我将执行的查询类型将是典型的(例如,检索给定元数据属性的列表、检索给定患者(实体)和元数据属性的列表等)。我相信在大多数情况下我需要查询类型才能知道要查询的列或表。有没有其他办法解决这个问题?

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

第一次发帖,所以提前感谢,请随时对格式发表评论或进一步澄清!

4

2 回答 2

4

这是一个众所周知的问题。您提到的方法的问题是您需要在查询之前知道属性的类型。这不是世界末日,因为您管理元数据,但仍然......

两种可能的解决方案可能是

  1. 使用varchar2数据类型以已知格式表示所有数据类型。数字和字符都没有问题,日期值可以以预定义的方式写入(就像to_String()在任何 OO 设计中实现一样)。
  2. 使用 ANYDATA 数据类型。我个人玩过它,但决定不使用它。
于 2013-08-24T07:07:37.990 回答
4

最简单、最高效的方法等是将数据库中的所有值转换为字符串。所指出的问题通常很明显,即使是类型良好的列也会遇到完全相同的问题,通常表现为性能问题。

稍加注意,您可以维护整理顺序,如果这很重要(例如,通过将日期格式化为年/月/日),并且无论如何都不应由数据库完成类型验证,因为为时已晚。负数和浮点数一样令人痛苦,但是用可以为负数或浮点数的数字进行索引是非常不寻常的,并且内存中的排序通常很快。

如果数据的类型不明显,或者需要下游处理器知道,则添加类型列。

通常,可以在写入记录之前检查所有针对列值的完整性约束,无论是在代码中(好)还是在触发器中(不太好)。尝试使用具有不同类型的本机功能只会带您到此为止,并且无论如何可能都没有那么有用,因为值通常具有许多特定于业务的约束,例如出生日期必须是非空的并且在 1900 年之后。

为了提高性能,请使用包括实体和属性作为前缀的复合索引。索引可以通过实体属性前缀进行分区,减少索引额外深度的任何影响,并且它们压缩得非常好(前缀会压缩到一两个字节),因此大小差异很小。

从 EAV 表查询通常最好在视图中完成,该视图将为您解包实体,以便可以将结构返回到您期望的状态,尽管如果您正在处理不同的列,例如在具有特征的患者表格中,这可能无关紧要由大量不同的元素组成,具体取决于历史。那么在您的业务逻辑中处理可能会更容易。

最后,如今这种数据根本就不是以面向列的关系数据库样式存储的。它通常存储为 XML(或 JSON)文档(Oracle 中的 XML 类型),并且大多数数据库都提供了一些原生 XML 处理能力,以便搜索和操作此类数据。这对于正常的表单存储和检索来说是可以的,但往往会导致任意查询,例如“给我所有 60 岁以上在去年患有肺炎的患者”,或者因为需要标记反向索引而涉及更多。尽管如此,面向文档/面向文本的方法是否是更好的解决方案还是值得一看的。

祝你好运!

于 2013-09-04T15:54:41.513 回答