仿制药的数据库设计

Database design for generics

我想知道在数据库中存储通用 classes 的最佳方法是什么。为了简单说明,假设以下 classes:

Interface IShape
  Center: Point
  Size: Point
  Color: Int

Class Line : IShape
  (IShape Members...)
  LineWidth: Int
  Orientation: Int

Class Circle: IShape
  (IShape Members...)
  Fill: Bool
  FillColor: Int

Class Drawing
  Name: String
  Elements: List<IShape>

我看到以下选项:

  1. 将所有对象存储在一个 table 中,其中包含所有可能的 IShape 对象的列。然后从数据库中获取行后,检查类型并创建特定 class 的对象。 这将导致 "messy table",类型之间没有明确的分隔。

  2. 将 IShape 成员存储在一个 table 中,将其他字段存储在相应的 table 中。获取 IShape 元素并检查需要为每个对象额外加载哪些字段。 这将导致大量的数据库流量。

  3. 与第 2 项类似,但不是加载每个元素并查看需要加载哪些字段,而是对每种类型执行一个查询,returns 具有正确字段的连接元素。 这也会导致比No.1更多的数据库流量(或至少查询),也可能需要求助。

您建议如何将它们存储在数据库中?有一些最佳实践吗?有趣的是我找不到任何推荐。

你的第 2 个选项对我来说最突出。

在我工作的地方,我们有一个数据库系统,用于存储有关工作流程的元信息;有一个中央 table 存储基本工作流程项目信息(ID、项目名称、默认负责人等),然后每种类型的项目都有自己的 table 存储额外信息(文档有a table 包含模板位置和文档类型,流程包含 table 及其工作流成员等)。

它有点像编程中的继承线;基础 table(类型),然后是继承自基础 table.

的其他类型的 table

所以你最终可能会得到:

shapeland.BaseShape ( ShapeId INT, ShapeType INT, ShapeDescription VARCHAR )
shapeland.Circle (ShapeId INT, Fill BIT, FillColor INT) 
etc etc

或者当然,如果您更像是代码而不是 sql 人;您始终可以存储 class 的扁平化 xml 版本;尽管这在数据库中变得不那么可读了。

泛型 List<IShape> 在这里有点转移注意力,因为它只不过是一对多 link 的表示,或者可能是多对的一部分许多 link table;您的问题中没有独特的与泛型相关的映射复杂性。

由于 object-relational impedance mismatch 的深度,这里确实没有单一的最佳实践。有 3 种成熟的映射继承层次结构的技术,其中 none 可以完全正确地表示约束。

  1. Table 每个类型,你的选项 2,子类型和基本类型之间有 1 对 1 主键引用 table秒。表达相互排斥的子类型需要一些非规范化(包括作为主键一部分的判别列,并在每个子类型 table 中对其合并 CHECK 约束),每个查询可能有大量连接,但总体上引用完整性最强.

  2. Table 每个层级 ,有时称为 "parking lot" table,您的选项 1 使用列来区分对象类型。由于大量未初始化的列非常混乱,由于无法表达对子类型的引用而导致引用完整性有问题,但可以通过 CHECK 约束强制执行列适用性。

  3. Table 每个具体类型 ,你没有提到:让 LineCircle 是 tables,让 IShape 成为两个 tables 的公共列的联合视图。由于无法表达对基类型的引用,引用完整性存在问题,但在不引入可空列的情况下减少了连接数。

为了在参照完整性方面获得最佳保真度,您需要对已区分的 table-per-type 结构进行一些变体,也许像这样:

create table Drawing (
    DrawingId int not null primary key,
    Name varchar not null
)

create table ShapeKind (
    ShapeKindId int primary key,
    ShapeKindName varchar
)
insert into ShapeKind values (1, 'Line')
insert into ShapeKind values (2, 'Circle')

create table Shape (
    ShapeId int not null,
    ShapeKindId int not null,
    DrawingId int not null,
    CenterX numeric not null,
    CenterY numeric not null,
    SizeX numeric not null,
    SizeY numeric not null,
    Color int not null,
    primary key (ShapeId, ShapeKindId),
    foreign key (ShapeKindId) references ShapeKind (ShapeKindId),
    foreign key (DrawingId) references Drawing (DrawingId)
)

create table Line (
    ShapeId int not null,
    ShapeKindId int not null check (ShapeKindId = 1),
    LineWidth int not null,
    Orientation int not null,
    primary key (ShapeId, ShapeKindId),
    foreign key (ShapeId, ShapeKindId) references Shape (ShapeId, ShapeKindId)
)

create table Circle (
    ShapeId int not null,
    ShapeKindId int not null check (ShapeKindId = 2),
    FillColor int, -- null if not filled
    primary key (ShapeId, ShapeKindId),
    foreign key (ShapeId, ShapeKindId) references Shape (ShapeId, ShapeKindId)
)

我最近回答了几个这样的问题,最近的是

简而言之,您有一个母版或基础 table(形状),其中包含所有形状共有的数据。它有一个自动生成的键值,但也有一个指示符,指示每行代表的特定形状(圆形、方形等)。

每个子table都包含形状特定的数据。将有一个用于 Circles,一个用于 Squares/Rectangles,一个用于 Arcs,等等。

一个明显的改进是每个形状都有一个视图,将特定于形状的数据与公共数据连接起来。因此,如果应用程序具有三角形形状的 ID,它会查询三角形视图以获取它的所有数据。如果它有一个 id 值,但不知道它是什么形状,它可以查询 master table(或调用一个函数)来获取形状的种类,然后它可以查询适当的视图。

视图上的触发器将允许创建新的三角形、正方形等,并允许编辑或删除现有的三角形、正方形等。应用程序(和应用程序开发人员)甚至不需要知道 split-table 设计细节。如果设计得当,一切都可以通过视图发生。 app/developers 甚至不必知道它们是视图。

添加一种新的形状(八角形)很容易:只需创建一个新的八角形 table 和视图对即可。现有应用程序代码不会受到影响。