在网格中存储分层对象

Posted

技术标签:

【中文标题】在网格中存储分层对象【英文标题】:Storing layered objects in a grid 【发布时间】:2021-12-31 16:09:15 【问题描述】:

假设我有一个画布,其中可以添加各种对象,例如 a/an:

绘图 图片 图表 注意 表

对于每个对象,我需要存储尺寸和层顺序,例如:

对象ID 图层索引 尺寸((x1,y1),(x2,y2))

每个对象都有非常不同的属性,因此存储在不同的表(或类或其他)中。是否可以将其存储到关系数据库中,如果可以,怎么做?在 JSON 中会是这样的:

// LayerIndex is the ArrayIndex
// No need to store ObjectID, since the object is stored within the array itself
Layers = [
    Type: Drawing, Props: <DrawingPropertyObj>, Dimensions: [(1,2), (3,4)],
    Type: Chart,   Props: <ChartPropertyObj>,   Dimensions: [(3,4), (10,4)],
    Type: Table,   Props: <TablePropertyObj>,   Dimensions: [(10,20), (30,44)],
    ...
]

我想到的一个选项是将 FK 存储到每个表中,但在这种情况下,我可能会将其加入到每个对象类型的 N 个不同表中,因此如果有 100 个对象类型,...

【问题讨论】:

entity–attribute–value model 可以为您的网格对象存储不同的属性。 这在this线程和其中的链接线程中得到了充分的回答 需要通过对象属性查询db吗? 这是一个常见的副本。 How can you represent inheritance in a database?等How much research effort is expected of Stack Overflow users? @philipxy 明白了。我认为您看到该问题具有标签sql-server(以及您从该问题中标记的重复项),是吗?除了一个完整的字面意思是Alternatively, consider using a document databases (such as MongoDB) which natively support rich data structures and nesting.的单句答案之外,没有任何答案涉及潜在的非关系性方式。 【参考方案1】:

您有很多选择,如下所示。

你选择哪一个没有太大区别,但我会避免你所说的多表设计。具有 100 个属性的对象类型将分散在 101 个表中而没有任何好处。每种正在读取的对象类型有 101 次磁盘页面访问。这是不必要的(如果这些页面被缓存,那么这个问题会比其他问题少,但仍然是浪费)。

如果您不希望过滤诸如“所有颜色为红色的对象”之类的内容,即使双表也不是必需的,但我想性能并不是那么迫切需要达到这一点,其他事情更重要,或者其他瓶颈对性能的影响更大,因此请选择最适合您的单表。

单表 - 每个对象类型的灵活架构

objlayerindex type props x0 y0 x1 y1
0 drawing color:#00FF00,background-color:#00FFFF 1 2 3 4
1 chart title:2021_sales,values:[[0,0],[3,4]] 11 22 33 44
在 props 中键是为了灵活使用,同一类型的不同对象可能有不同的键,例如没有字幕的图表可以省略此键。

单表 - 每个对象类型的固定架构

objlayerindex type props x0 y0 x1 y1
0 drawing #00FF00,#00FFFF 1 2 3 4
1 chart 2021_sales,"[[0,0],[3,4]]" 11 22 33 44
这个模式是固定的——绘图总是有颜色+背景色;图表总是有标题+值;等 - 使用的空间更少,但更改架构需要对现有数据进行一些工作。

双桌

主要
objlayerindex type x0 y0 x1 y1
0 drawing 1 2 3 4
1 chart 11 22 33 44
属性
objlayerindex propertyname propertyvalue
0 color #00FF00
0 background-color #00FFFF
1 title 2021_sales
1 values [[0,0],[3,4]]
这里我们假设属性排序并不重要。如果是,则需要一个额外的列propertyindex。对于喜欢标准化的人,也可以将propertyname 从此表中取出到propertykey-propertydescription 并通过其propertykey 引用。

多表

主要
objlayerindex type x0 y0 x1 y1
0 drawing 1 2 3 4
1 chart 11 22 33 44
颜色
objlayerindex colorcode
0 #00FF00
背景色
objlayerindex colorcode
0 #00FFFF
标题
objlayerindex title
1 2021_sales
价值观
objilayerindex chart
1 [[0,0],[3,4]]
具体来说,这类数据可以再归一化一级:
价值观
objlayerindex datapoint x y
1 0 0 0
1 1 3 4

您也可以使用非关系格式。

文档(Json)存储

[
  type:drawing,props:color:#00FF0,background-color:#00FF0,position:[1,2,3,4],
  type:chart,props:title:2021_sales,values:[[0,0],[3,4]],position:[11,22,33,44]
]
我们在这里引用它是因为它是一种流行且简单的格式,但可以使用不同的编码来代替 JSON(CSV、protocolbuffers、avro 等)

【讨论】:

【参考方案2】:

“严格”的关系数据库不适合这项任务,因为您必须选择:

    每种对象类型都有不同的表,其中每个属性的列适用于该特定对象类型 所有对象类型的单个表,每个属性都有列,其中大部分不用于任何给定的对象类型 一个子表,每个属性一行

在转向一个好的通用解决方案之前,让我们讨论一下:

1。每种对象类型都有不同的表

这是一个非首发。问题是:

维护成本高:每次向应用添加新对象类型时都必须创建一个新表 痛苦的查询:您必须连接到每个表,或者水平连接 - 每个表连接到一个非常长的行中,或者垂直连接到一系列联合连接中,导致 sparse array(参见选项 2)

2。所有对象类型的单个表

虽然您正在处理sparse array,但如果大多数对象类型使用大多数属性(即,它不是 稀疏的),这是一个不错的选择。但是,如果您的域中不同属性的数量很多,并且/或者大多数属性并非被所有类型使用,那么您必须在引入新类型时添加列,这虽然比添加表更好,但仍然需要更改架构对于新类型 = 高维护率

3。子表

这是经典的方法,但使用起来更糟,因为您要么必须运行单独的查询来收集每个对象的所有属性(慢,高维护),要么单独编写针对每种对象类型进行查询,为每个属性加入一次子表,以将多个行扁平化为每个对象的一行,有效地导致选项 1,但编写查询的维护成本更高

这些都不是很好的选择。你想要的是:

每个对象一行 简单查询 简单架构 维护成本低

document database,例如Elasticsearch 可以为您提供所有这些开箱即用的功能,但是您可以通过放宽“严格性”并将整个对象作为 json 保存在单个列中来实现与关系数据库相同的效果:

create table object (
  id int, -- typically auto incrementing
  -- FK to parent - see below
  json text -- store object as json
);

顺便说一句,postgres 将是一个不错的选择,因为它通过 json 数据类型具有 native support for json。

在我的职业生涯中,我曾多次使用它,总是成功。我为对象类类型添加了一个列(在 java 上下文中):

create table object (
  id int,
  -- FK to parent - see below
  class_name text,
  json text
);

并使用 json 库将使用指定类的 json 反序列化为该类的对象。无论您使用哪种语言,都可以实现这一想法。

至于层次结构,关系数据库可以很好地做到这一点。从画布:

create table canvas (
  id int,
  -- various attributes
);

如果对象没有被重用:

create table object (
  id int,
  canvas_id int not null references canvas,
  class_name text,
  json text,
  layer int not null
);   

如果对象被重复使用:

如果对象没有被重用:

create table object (
  id int,
  class_name text,
  json text
);

create table canvas_object (
  canvas_id int not null references canvas,
  object_id int not null references object,
  layer int not null
);

【讨论】:

说到 postgres,这是一个简洁的小包/脚本,它利用了您提到的许多概念:github.com/solidsnack/pg-sql-variants @David542 你可以使用类层次结构,但我在尝试时总是束手无策。恕我直言,愚蠢的东西越容易使用,最终解决方案就越简单,即更好。如果它易于理解,则易于使用和维护。 您在开头提到A "strict" relational database doesn't suit this task well,如果您的任务是存储这些数据,您会使用关系数据库还是非关系数据库?而且,如果是后者,您是否也希望将其作为一个部分包含在您的答案中? 我会使用关系,因为有额外的花里胡哨(例如事务边界、参照完整性、视图等),但是在“文档”样式中 - 这意味着它会违反 first normal form,因为存储整个实体的单个列。我已经使用 postgres、Lucene(实际上是 Solr)和 Elasticsearch 完成了它——一切正常。请注意,RDBMS 是 ACID,但文档数据库不是(它们是 eventually consistent,这可能不适合您的目的。

以上是关于在网格中存储分层对象的主要内容,如果未能解决你的问题,请参考以下文章

如何以表格形式/数据网格显示分层数据

无法将事件网格系统分配的托管标识添加到存储帐户角色

将对象复制作为事件网格源的 Azure Blob

剑道网格 - 如何在添加/编辑子行时访问父行模型(详细网格)

网格布局之相关特性

基于对象值渲染时创建引导卡网格的问题