【问题标题】:Relational Design: Column Attributes关系设计:列属性
【发布时间】:2011-09-26 20:20:13
【问题描述】:

我有一个系统,允许人们从下拉框中选择他们想要填写的表单类型。由此,显示该特定表单的其余字段,用户填写它们并提交条目。

表格:

| form_id | age_enabled | profession_enabled | salary_enabled | name_enabled |

这描述了表单的元数据,以便系统知道如何绘制它。因此,如果表单应包含要为此列填写的字段,则每个 _enabled 列都是布尔值 true。

条目表:

| entry_id | form_id | age | profession | salary | name | country |

这会存储提交的表单。其中年龄、职业等存储表单中填写的实际值(如果表单中不存在,则为 null)

用户可以即时向系统添加新表单。

现在的主要问题是:我想为用户添加设计新表单的功能,以便能够包含属性的可能值列表(例如,职业是说 20 个职业的下拉列表,而不仅仅是填写表格时的文本框)。我不能简单地为每列存储一个可能值的全局列表,因为每个表单都有不同的值列表可供选择。

我能想出的唯一解决方案是在 Form 表中包含另一组列,例如professional_values,然后以字符分隔格式存储这些值。我担心某一天某一列可能有大量可能的值,而这一列会失控。

请注意,如果需要,可以稍后将新列添加到 Form(因此依次添加 Entry),但 90% 的表单具有相同的基本列集,因此我认为这种设计比 EAV 设计更好。想法?

我从未见过这样的系统(作为一个整体)的关系设计,而且我似乎无法找到一种体面的方法来做到这一点。

【问题讨论】:

  • 您需要更多相关表,而不是更多列。为什么在数据库中使用 CSV?这是你出错的一个很好的线索。
  • @Orbling 我同意,但是,我认为这个问题不存在一个好的关系模式。某个地方将不得不做出一些让步才能使其发挥作用。您知道可行的架构吗?
  • @Davis Dimitriov:这是一个相当标准的关系模式问题,只要想想数据中的列表中有一个列表,那么您需要一个链接到父表的新表。有关示例架构,请参见下面的 Alex Howansky 的回答。
  • @Orbling 这不是一个标准的关系模式问题,因为关系设计不能很好地实现这种灵活性。 Alex 的帖子遵循实体-属性-值 (EAV) 设计模式,但也存在一些缺陷。我不相信你能赢得所有人,但我很想被说服

标签: database database-design data-modeling


【解决方案1】:

我认为你完全从错误的地方开始。

| form_id | age_enabled | profession_enabled | salary_enabled | name_enabled |

您是否要继续为您可能拥有的每个 for 字段添加到此表中?通常,列表可能是无穷无尽的。

如果所有字段都在此表的列中,您的应用程序代码将如何显示表单?

这样的表格怎么样:

| form_id | form description |

然后是另一个表格,formAttributes,表格上每个条目一行:

| attribute_id | form_id | position | name | type | 

然后是第三个表 forAttributeValidValues,每个属性有效值一行:

| attribute_id | value_id | value |

这看起来似乎需要更多的工作,但实际上并非如此。想想在表单中添加或删除新属性或值是多么容易。还要考虑您的应用程序将如何呈现表单:

for form_element in (select name, attribute_id 
                     from formAttributes 
                     where form_id = :bind
                     order by position asc) loop
  render_form_element
  if form_element.type = 'list of values' then
     render_values with 'select ... from formAttributeValidValues'
  end if
end loop;

难题将变成如何存储表单结果。理想情况下,您会将它们存储在一个表格中,每个表单元素有 1 行,类似于:

| completed_form_id | form_id | attribute_id | value |

如果您一次只处理一个表单,那么此模型将运行良好。如果您想对大量表单进行聚合,那么生成的查询会变得更加困难,但是报告可以在与在线表单条目不同的过程中运行。您可以开始考虑使用透视查询将行转换为列或物化视图以将相同类型的表单组合在一起等方式。

【讨论】:

  • +1 这就是我所说的。它有模式名称或型号名称吗?我知道是“字典”
  • 我最近实际上正在研究这是否是一种已知模式,我认为它被称为 EAV - 实体属性变量。维基百科有一些有趣的见解 - en.wikipedia.org/wiki/Entity-attribute-value_model
【解决方案2】:

创建一个包含值组的新表:

CREATE TABLE values (
    id SERIAL,
    group INT NOT NULL,
    value TEXT NOT NULL,
    label TEXT NOT NULL,
    PRIMARY KEY (id),
    UNIQUE (group, value)
);

例如:

INSERT INTO values (group, value, label) VALUES (1, 'NY', 'New York');
INSERT INTO values (group, value, label) VALUES (1, 'CA', 'California');
INSERT INTO values (group, value, label) VALUES (1, 'FL', 'Florida');

因此,第 1 组包含三个可能的下拉选择器值。然后,您的表单表可以引用特定列使用的组。

还请注意,您应该通过行而不是列向表单添加字段。即,当您添加新表单时,您的应用程序不应该调整架构,它应该只创建新行。所以,让每个字段都有自己的行:

CREATE TABLE form (
    id SERIAL,
    name TEXT NOT NULL,
    PRIMARY KEY (id)
);

CREATE TABLE form_fields (
    id SERIAL,
    form_id INT NOT NULL REFERENCES form(id),
    field_label TEXT NOT NULL,
    field_type INT NOT NULL,
    field_select INT REFERENCES values(id),
    PRIMARY KEY (id)
);

INSERT INTO form (name) VALUES ('new form');
$id = last_insert_id()
INSERT INTO form_fields (form_id, field_label, field_type) VALUES ($id, 'age', 'text');
INSERT INTO form_fields (form_id, field_label, field_type) VALUES ($id, 'profession', 'text');
INSERT INTO form_fields (form_id, field_label, field_type) VALUES ($id, 'salary', 'text');
INSERT INTO form_fields (form_id, field_label, field_type, field_select) VALUES ($id, 'state', 'select', 1);

【讨论】:

  • 但是将列转换为行会使其他事情变得困难,例如稍后查询条目(因为条目列也必须转换为行)。然后所有内容都必须存储为字符串,不能有任何约束,并且很难验证。
  • 验证不应与数据库字段类型相关联。如果您想存储 LAT/LON、多选列表或电子邮件地址怎么办?这些只能通过代码进行验证。稍后查询条目实际上更容易,您只需循环选择的结果,您不需要知道架构是什么样的。
  • 但是您必须将所有结果选择到内存中,然后在代码中解析它们,而不是使用 SQL 进行报告。这就是为什么我不认为这是一个更具关联性的设计
  • 这是一个非常薄弱的​​论点。归结为这一点——使用依赖模式更新来实现其主要功能的基于列的设计完全违背了使用关系数据库的目的。这种类型的非规范化设计更适合像 MongoDB 这样的 NoSQL 存储。 (或者地狱,甚至是 CSV 文件,正如 Orbling 提到的那样。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-21
  • 2010-09-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多