在没有太多列名的情况下存储有关模型的额外信息的策略(数据库规范化和模型子类化的替代方案)

Posted

技术标签:

【中文标题】在没有太多列名的情况下存储有关模型的额外信息的策略(数据库规范化和模型子类化的替代方案)【英文标题】:Strategies to store extra information about models without too many column names (alternatives to DB normalization and model subclassing) 【发布时间】:2014-03-02 06:14:09 【问题描述】:

假设您有一个名为 Forest 的模型。每个对象代表您大陆上的一片森林。有一组所有这些森林共有的数据,如森林类型、面积等,这些数据可以很容易地用 SQL 表中的列来表示,forest

但是,假设这些森林有关于它们的额外数据,这些数据可能并不总是可重复的。例如,20 个针叶林有一个pine-fir 分裂比率编号,而落叶林有一个autumn-duration 编号。一种方法是将所有这些列存储在主表本身上,但是每行上的列太多,并且根据定义还有许多列未填充。

解决这个问题最明显的方法是创建 Forest 模型的子类,并为每个子类设置单独的表。我觉得这是一种我宁愿不遵循的严厉方法。如果我需要一些关于通用森林的数据,我将不得不查阅另一张表。

有解决这个问题的模式吗?您通常更喜欢哪种解决方案?

注意:我已经看到有关此的其他问题。提出的解决方案是:

子类型化,与我上面提出的相同。 将所有列放在同一个表中。 对每种森林都有单独的表格,其中包含重复的数据,例如面积和降雨量...重复。

有我不知道的创造性解决方案吗?

更新:我遇到了EAV 模型,还有一个修改版本,其中不可预测的字段存储在 NoSQL/JSON 存储中,其 id 保存在 RDB 中。两者我都喜欢,但欢迎在这个方向提出建议。

【问题讨论】:

【参考方案1】:

在数据库方面,最好的方法通常是将所有森林共有的属性存储在一个表中,并将唯一属性存储在其他表中。构建可供客户使用的可更新视图。

create table forests (
  forest_id integer primary key,
  -- Assumes forest names are not unique on a continent.
  forest_name varchar(45) not null,
  forest_type char(1) not null 
    check (forest_type in ('c', 'd')),
  area_sq_km integer not null
    check (area_sq_km > 0),
  -- Other columns common to all forests go here.
  --
  -- This constraint lets foreign keys target the pair
  -- of columns, guaranteeing that a row in each subtype
  -- table references a row here having the same subtype.
  unique (forest_id, forest_type)
);

create table coniferous_forests_subtype (
  forest_id integer primary key,
  forest_type char(1) not null
    default 'c'
    check (forest_type = 'c'),
  pine_fir_ratio float not null
    check (pine_fir_ratio >= 0),
  foreign key (forest_id, forest_type)
    references forests (forest_id, forest_type)
);

create table deciduous_forests_subtype (
  forest_id integer primary key,
  forest_type char(1) not null
    default 'd'
    check (forest_type = 'd'),
  autumn_duration_days integer not null
    check (autumn_duration_days between 20 and 100),
  foreign key (forest_id, forest_type)
    references forests (forest_id, forest_type)
);

客户端通常使用可更新视图,每个子类型一个,而不是使用基表。 (您可以撤销对基本子类型表的权限以保证这一点。)您可能希望省略“forest_type”列。

create view coniferous_forests as 
select t1.forest_id, t1.forest_type, t1.area_sq_km,
       t2.pine_fir_ratio
from forests t1
inner join coniferous_forests_subtype t2
        on t1.forest_id = t2.forest_id;

create view deciduous_forests as 
select t1.forest_id, t1.forest_type, t1.area_sq_km,
       t2.autumn_duration_days
from forests t1
inner join deciduous_forests_subtype t2
        on t1.forest_id = t2.forest_id;

要使这些视图可更新,您必须做的事情随 dbms 的不同而有所不同,但希望编写一些触发器(未显示)。您需要触发器来处理所有 DML 操作——插入、更新和删除。

如果您只需要报告“森林”中出现的列,则只需查询“森林”表即可。

【讨论】:

感谢您的详细回答。我以前没有听说过可更新的视图。但是有了这个解决方案,我不仅需要每个子类型都有一个表(我试图避免这种情况),而且还必须自己开始维护视图。不太了解它的好处。 主要优势在于数据完整性。这些视图不是绝对必要的,但它们简化了客户端访问。你为什么要避免每个子类型都有一个表? 我对数据库上有这么多表和代码方面的子类不满意,仅仅是因为某些实体“可能”存在一些额外的属性。我的问题中的“森林”示例并不是最好的,因为它立即似乎是应用继承的好地方。但我说的是不是……【参考方案2】:

嗯,最简单的方法是将所有列放入一个表中,然后使用“类型”字段来决定使用哪些列。这适用于较小的表,但对于更复杂的情况,它可能会导致表变得凌乱并出现数据库约束问题(例如 NULL)。

我的首选方法是这样的:

A generic "Forests" table with:  id, type, [generic_columns, ...]
"Coniferous_Forests" table with: id, forest_id (FK to Forests), ...

因此,为了获取 id 为 1 的针叶林的所有数据,您需要这样的查询:

SELECT * FROM Coniferous_Forests INNER JOIN Forests 
ON Coniferous_Forests.forest_id = Forests.id
AND Coniferous_Forests.id = 1

至于创造性的解决方案,有 OODBMS(面向对象的数据库管理系统)之类的东西。

最流行的关系 SQL 数据库替代品是面向文档的 NoSQL 数据库,例如 MongoDB。这类似于使用 JSON 对象来存储数据,并允许您更灵活地使用数据库字段。

【讨论】:

谢谢,我已经介绍了您在问题中提到的第一个解决方案。所以本质上你是说它不是那个就是 NoSQL... @adityamenon 我个人不知道有什么更好的方法。如果有帮助,这就是 Stack Overflow 组织表格的方式:meta.stackexchange.com/questions/2677/…

以上是关于在没有太多列名的情况下存储有关模型的额外信息的策略(数据库规范化和模型子类化的替代方案)的主要内容,如果未能解决你的问题,请参考以下文章

模型表单集 - 默认情况下,模型表单集呈现一个额外的字段(总共 2 个字段)

Asp.net 身份额外的用户信息,如电子邮件等

如何在没有额外查询的情况下删除续集中的关联?

Linux下安全审计audit 系统审计 记录root操作

防止 sqlplus 截断列名,没有单独的列格式

WebSocket 发送有关连接的额外信息