【问题标题】:How can I handle different data types in an Entity-Attribute-Value design (e.g. single table with multiple columns or multiple tables per data type)?如何在实体-属性-值设计中处理不同的数据类型(例如,具有多列的单个表或每种数据类型的多个表)?
【发布时间】:2013-08-08 23:06:32
【问题描述】:

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

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

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

代表性数据

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

*FOREIGN_KEY 表示此 value 类型是 values 字典表的外键 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 表中需要一个 value 类型标识列(attribute_value_type)来知道要查看哪个列/表。

可能的方法

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

方法 1:具有多列的单个 EAV 表

为 patient_metadata 表创建三个不同的列 - 每个 value 类型一个。

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 表 - 每个 value 类型一个。

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
);

其他方法?

还有其他方法吗?

简而言之,我想尽可能地尊重关系完整性,让数据库知道value类型,这样它就可以执行基本的验证。但是,我相信上述两种方法都需要某种类型的手动完整性检查(方法 1 需要检查是否只填充了一个 attribute_value 列,等等)。

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

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

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

【问题讨论】:

  • 您好,我认为您遇到了一种叫做多态键现象的问题。
  • 我认为这个维基百科页面涵盖了很多领域:en.wikipedia.org/wiki/…

标签: database oracle plsql database-schema entity-attribute-value


【解决方案1】:

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

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

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

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

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

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

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

祝你好运!

【讨论】:

    【解决方案2】:

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

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

    1. 使用varchar2 数据类型来表示已知的所有数据类型 格式。数字和字符都没有问题,可以写日期值 以预定义的方式(就像在任何 OO 中实现 to_String() 设计)。
    2. 使用 ANYDATA 数据类型。我个人玩过它,但决定不使用 它。

    【讨论】:

    • -1 永远不要将日期和数字存储为字符串。这会导致很多问题。例如,有人不可避免地会写出这样的谓词:where attribute = 'DOB' and to_date(value, 'YYYY-MM-DD') < date '2000-01-01'。它看起来很合理,但它非常危险,即使您的所有数据都是干净的。对不同类型使用不同的列不会导致额外的工作。你必须知道类型才能对数据做任何有趣的事情。
    • 将数字存储为字符串显然不是最佳选择,但我不同意您的声明 - 这是一个设计问题,您在这里承担的“风险”取决于您的数据库访问层。
    • 谢谢,不知道ANYDATA 类型——看起来很有趣!您决定不使用它的任何原因?
    • @lebolo 我最近使用 ANYDATA 构建了一个系统,但由于几个原因不得不替换 ANYDATA。 1) 流水线函数存在严重的性能问题。 2) API 需要 PL/SQL 太频繁,没有足够的access 函数。 3) 类型不匹配不会引发异常,需要额外检查。 4) 没有数据库工具支持 ANYDATA,包括 SQL*Plus、SQL Developer 或 PL/SQL 调试器。 5) 未解决的错误,例如混合 32 位和 64 位客户端和服务器时的问题。
    猜你喜欢
    • 2016-05-30
    • 2021-12-27
    • 1970-01-01
    • 1970-01-01
    • 2013-02-22
    • 1970-01-01
    • 1970-01-01
    • 2022-01-04
    • 2018-02-17
    相关资源
    最近更新 更多