为 data-store 浏览器建模

Modeling a data-store browser

我有一个 connection-object 浏览器,我想让用户查看他们连接的各种数据源 to.The objects 查看器看起来像这样:

换句话说,'Source' 是已知级别数和每个级别的已知 'name' 的层次结构。虽然整个层次结构是可变的,但任何给定源的层次结构将始终具有相同的级别数和名称。什么可能是建立关系模型的好方法?我的想法是:

连接:

来源类型:

SourceTypeLevelMapping:

ThreeLevelSource_Level1: # 例如,数据库

ThreeLevelSource_Level2:#例如,Table

ThreeLevelSource_Level3: # 例如,Field

然后对其他 level-ed 层次结构执行相同的操作:


因此,基本上定义已知的层次结构,对于我们添加的每个新源,我们会将其附加到已知的层次结构级别之一。我想做的另一种方法是为每个新源创建一个新的层次结构,但是如果我们允许访问 25-50 个源,那么我们将查看数百个表。

对这种类型的分层数据建模的好方法是什么?

(此外,是的,我熟悉此处描述的分层数据建模的现有通用方法 -- What are the options for storing hierarchical data in a relational database?, How can you represent inheritance in a database? -- 下面不是重复的。)

这里是三个有效的 sqlite 风格的实现(一旦使用 sqlite,不强制执行的列类型是 acceptable,仅使用整数主键来充当 rowid):

在所有情况下,sqlite 外键 PRAGMA 设置为 true:PRAGMA foreign_keys = 1;

实现简单 - 每个 source/level 一个固定的 table(受外键约束)

下面的design/implementation对每种类型的数据库和级别使用一个table。表之间通过外键相互引用以确保正确性。例如,mongo collection 不能是 mysql database 的 child。仅在 connection 级别,所有数据库类型共享相同的 table,但如果预期每种 connection.

的属性不同,则可能会有所不同
create table databasetype(name primary key) without rowid;
insert into databasetype values ('mysql'),('elasticsearch'),('mongo'),('sqlserver');

create table datatype(name primary key) without rowid;
insert into datatype values ('int'),('str'); -- you can differentiate varchar if you will

create table connection(id integer, hostname, databasetype, primary key(id), foreign key(databasetype) references databasetype(name));

create table mysqldatabase(id integer, connectionid, name, primary key(id), foreign key(connectionid) references connection(id));
create table mysqltable(id integer, databaseid, name, primary key(id), foreign key(databaseid) references mysqldatabase(id));
create table mysqlfield(id integer, tableid, name, datatype, datalength, primary key(id), foreign key(tableid) references mysqltable(id), foreign key(datatype) references datatype(name));

create table elasticsearchindex(id integer, connectionid, name, primary key(id), foreign key(connectionid) references connection(id));
create table elasticsearchfield(id integer, indexid, name, datatype, datalength, primary key(id), foreign key(indexid) references mysqltable(id), foreign key(datatype) references datatype(name));

create table mongodatabase(id integer, connectionid, name, primary key(id), foreign key(connectionid) references connection(id));
create table mongocollection(id integer, databaseid, name, primary key(id), foreign key(databaseid) references mongodatabase(id));
create table mongofield(id integer, collectionid, name, datatype, datalength, primary key(id), foreign key(collectionid) references mongocollection(id), foreign key(datatype) references datatype(name));

create table sqlserverdatabase(id integer, connectionid, name, primary key(id), foreign key(connectionid) references connection(id));
create table sqlserverschema(id integer, databaseid, name, primary key(id), foreign key(databaseid) references sqlserverdatabase(id));
create table sqlservertable(id integer, schemaid, name, primary key(id), foreign key(schemaid) references sqlserverschema(id));
create table sqlserverfield(id integer, tableid, name, datatype, datalength, primary key(id), foreign key(tableid) references sqlservertable(id), foreign key(datatype) references datatype(name));

加载代表第一个的数据table:

insert into connection(hostname, databasetype) values ('remote:1234', 'mysql');
insert into mysqldatabase(connectionid, name) select id, 'sales' from connection where hostname='remote:1234';
insert into mysqltable(databaseid, name) select id, 'user' from mysqltable where name='sales';
insert into mysqlfield(tableid, name, datatype, datalength) select id, 'name', 'str', 80 from mysqldatabase where name='product';
insert into mysqlfield(tableid, name, datatype) select id, 'age', 'i32' from mysqldatabase where name='product';

尝试对数据进行无效操作:

insert into mysqlfield(tableid, name, datatype) values (2, 'newfield', 'qubit');

-- Error: FOREIGN KEY constraint failed

为了 pretty-print 整个树,有必要对所有涉及的 table 进行手动连接。

类似图的实现 - 一个 table 代表树,另一个代表层次结构(受触发器约束)

这里的element table用来表示树中的每一个element/node。它的 level 列明确地将每个元素分类为 databasetable 等。这里使用 sqlite 的 rowid 作为主键,但很容易将其更改为常规 id。

在之前的实现中,使用了外键来保证模型的正确性。现在触发器用于此作业。他们决定哪个 parent 级别接受哪个 child 级别,因为它被允许用于相应的 dbtype - 这些规则在 element_type table.

上指定

最后,exra table element_properties 用于允许附加属性附加到任何元素,例如字段类型。

create table db_type(name primary key) without rowid;
insert into db_type values ('mysql'),('elasticsearch'),('mongo'),('sqlserver');

create table element_type(parentlevel, childlevel, dbtype, primary key(parentlevel, childlevel, dbtype), foreign key(dbtype) references db_type(name)); --not using without rowid to be able to have null parent level
insert into element_type values
  (null, 'connection', 'mysql'),
  ('connection', 'database', 'mysql'),
  ('database', 'table', 'mysql'),
  ('table', 'field', 'mysql'),

  (null, 'connection', 'elasticsearch'),
  ('connection', 'index', 'elasticsearch'),
  ('index','field', 'elasticsearch'),

  (null, 'connection', 'mongo'),
  ('connection', 'database', 'mongo'),
  ('database', 'collection', 'mongo'),
  ('collection', 'field', 'mongo'),

  (null, 'connection', 'sqlserver'),
  ('connection', 'database', 'sqlserver'),
  ('database', 'schema', 'sqlserver'),
  ('schema', 'table', 'sqlserver'),
  ('table', 'field', 'sqlserver');

create table element(id integer, parentid, name, level, dbtype, primary key(id), foreign key(parentid) references element(id), foreign key(dbtype) references db_type(name));

create table element_property(parentid, name, value, primary key(parentid, name), foreign key(parentid) references element(id)) without rowid;

-- trigger to guarantee that new elements will conform hierarchy
create trigger element_insert before insert on element
begin
  select iif(count(*)>0, 'ok', raise(abort,'invalid parent-child insertion')) from element_type etc join element_type etp on (etp.childlevel, etp.dbtype)=(etc.parentlevel, etc.dbtype) where (etc.dbtype, etc.parentlevel, etc.childlevel)=(new.dbtype, (select level from element ei where ei.rowid=new.parentid), new.level);
end;

-- trigger to guarantee that updated elements will conform hierarchy
create trigger element_update before update on element
begin
  select iif(count(*)>0, 'ok', raise(abort,'invalid parent-child update')) from element_type etc join element_type etp on (etp.childlevel, etp.dbtype)=(etc.parentlevel, etc.dbtype) where (etc.dbtype, etc.parentlevel, etc.childlevel)=(new.dbtype, (select level from element ei where ei.rowid=new.parentid), new.level);
end;

-- trigger to guarantee that hierarchy removal must respect existing elements (no delete cascade used)
create trigger element_type_delete before delete on element_type
begin
  select iif(count(*)>0, raise(abort,'can''t remove, entries found in the element table using this relationship'), 'ok') from element etc join element etp on etp.rowid=etc.parentid and etp.dbtype=etp.dbtype where etc.dbtype=old.dbtype and (etp.level,etc.level)=(old.parentlevel, old.childlevel);
end;

-- trigger to guarantee that hierarchy changes must respect existing elements
create trigger element_type_update before update on element_type
begin
  select iif(count(*)>0, raise(abort,'can''t change, entries found in the element table using this relationship'), 'ok') from element etc join element etp on etp.rowid=etc.parentid and etp.dbtype=etp.dbtype where etc.dbtype=old.dbtype and (etp.level,etc.level)=(old.parentlevel, old.childlevel) and (etp.level,etc.level)!=(new.parentlevel, new.childlevel);
end;

加载代表第一个的数据table:

insert into element(name, level, dbtype) values ('remote:1234', 'connection', 'mysql');
insert into element(name, level, dbtype, parentid) values ('sales', 'database', 'mysql', (select id from element where (level, name, dbtype)=('connection', 'remote:1234', 'mysql')));
insert into element(name, level, dbtype, parentid) values ('user', 'table', 'mysql', (select id from element where (level, name, dbtype)=('database', 'sales', 'mysql')));
insert into element(name, level, dbtype, parentid) values ('name', 'field', 'mysql', (select id from element where (level, name, dbtype)=('table', 'user', 'mysql')));
insert into element(name, level, dbtype, parentid) values ('age', 'field', 'mysql', (select id from element where (level, name, dbtype)=('table', 'user', 'mysql')));
insert into element_property(name, value, parentid) values ('fieldtype', 'varchar', (select id from element where (level, name, dbtype)=('field', 'name', 'mysql')));
insert into element_property(name, value, parentid) values ('fieldlength', 80, (select id from element where (level, name, dbtype)=('field', 'name', 'mysql')));
insert into element_property(name, value, parentid) values ('fieldtype', 'integer', (select id from element where (level, name, dbtype)=('field', 'age', 'mysql')));

尝试对数据进行无效操作:

insert into element(name, level, dbtype, parentid) values ('documents', 'collection', 'mysql', (select id from element where (level, name, dbtype)=('database', 'sales', 'mysql')));

-- Error: invalid parent-child insertion

update element_type set childlevel='specialfield' where dbtype='mysql' and (parentlevel, childlevel)=('table','field');

-- Error: can't change, entries found in the element table using this relationship

Pretty-printing 树:

create view elementree(path) as
with recursive cte(id, name, depth, dbtype, level) as (
  select id, name, 0 as depth, dbtype, level from element where parentid is null
  union all
  select el.id, el.name, cte.depth+1 as depth, el.dbtype, el.level from element el join cte on el.parentid=cte.id
  order by depth desc
)
select substring('     ',0,2*depth)||name||' ('||dbtype||'-'||level||')' from cte;

select * from elementree;
-- remote:1234 (mysql-connection)
--  sales (mysql-database)
--    user (mysql-table)
--    documents (mysql-table)
--      name (mysql-field)
--      age (mysql-field)

极简主义的 DRY 图类似实现 - 一个 table 只有名称代表树,只有一个辅助 table

这里再次使用 element table 来表示树中的每个元素。与前一种情况不同的是,table 的信息较少,每个元素的类型 - 无论是 database 还是 table 都是隐式推断的,而不是由列显式确定的。通过简单地添加一个user作为sales的child,推断user是一个mysqltable,一旦它是[= mysql database - sales 的 115=] 是 database 因为它是 mysql connection 的 child ,这是 mysql 根元素的 child。 dbtypes 是此树中的根元素,它们的所有 children 都被推断为此 dbtype。

这里的hierarchypath table 用于告诉element 树中已遵循的层次结构。为了让用户感到舒适,他只需插入一个(> 分隔的)表示层次结构路径的字符串,从 dbtype 开始。层次结构视图会将此字符串解构为层次结构。雇佣路径的一个例子是:mysql>connection>database>table>field.

再次注意,sqlite 的 rowid 用作 table id。请记住,仅通过 select * from table; 无法查看 rowid,它默认隐藏,需要显式 select 它:select rowid,* from table;.

create table element(name, parentrowid, foreign key(parentrowid) references element(rowid));
-- dbtypes are the root elements
insert into element(name) values ('mysql'),('elasticsearch'),('mongo'),('sqlserver');

create table hierarchypath(path);

insert into hierarchypath values
  ('mysql>connection>database>table>field'),
  ('elasticsearch>connection>index>field'),
  ('mongo>connection>database>collection>field'),
  ('sqlserver>connection>schema>database>table>field');

正在加载数据:

insert into element select 'remote:1234',rowid from element where (name,coalesce(parentrowid,-1))=('mysql',-1); --returning rowid; -- returning only works for sqlite 3.35+
insert into element select 'sales',rowid from element where rowid=5;
insert into element select 'user',rowid from element where rowid=6;
insert into element select 'name',rowid from element where rowid=7;
insert into element select 'age',rowid from element where rowid=7;

Pretty-printing:

create view hierarchy(root, depth, name) as
with recursive hierarchycte(root, depth, name, remaining) as (
  select substr(path, 0, instr(path, '>')) as root, 0 as depth, substr(path, 0, instr(path, '>')) as name, substr(path, instr(path, '>')+1)||'>' as remaining from hierarchypath
  union all
  select root, depth+1 as depth, substr(remaining, 0, instr(remaining, '>')) as name, substr(remaining, instr(remaining, '>')+1) as remaining from hierarchycte where instr(remaining, '>') > 0
)
select root, depth, name from hierarchycte where depth>=0;

create view elementhierarchy(root, depth, name) as
with recursive elementcte(root, depth, name, rowid, parentrowid) as (
  select name as root, 0 as depth, name, rowid, parentrowid from element where parentrowid is null
  union all
  select elcte.root, elcte.depth+1, el.name, el.rowid, el.parentrowid from elementcte elcte join element el on el.parentrowid=elcte.rowid
  order by depth desc
)
select root, depth, name from elementcte;

create view elementree as
with recursive elementcte(root, depth, name, rowid, parentrowid) as (
  select name as root, 0 as depth, name, rowid, parentrowid from element where parentrowid is null
  union all
  select elcte.root, elcte.depth+1, el.name, el.rowid, el.parentrowid from elementcte elcte join element el on el.parentrowid=elcte.rowid
  order by depth desc
)
select substring('     ',0,2*h.depth-2)||eh.name||' ('||h.root||'-'||h.name||')' from (select *,row_number() over () as originalorder from elementhierarchy) eh join hierarchy h on (eh.root,eh.depth)=(h.root,h.depth) where h.depth>0 order by originalorder;

select * from elementree;
-- remote:1234 (mysql-connection)
--  sales (mysql-database)
--    user (mysql-table)
--      age (mysql-field)
--      name (mysql-field)

这里没有实现触发器,但是这样做会很好。一个例子是避免插入比允许的更多的级别。

以在视图中看到的解构形式存储层次结构会更明智 层次结构,通过在插入时间而不是每个 select 查询中进行解构来避免 cpu 消耗。在这里保留这种方式是为了将其与其他实现区分开来。

这里是最后一级实体,field 没有以前实现中显示的属性。在这个模型中,有必要向层次结构添加一个或两个额外的级别:...table>field>fieldpropertyandvalue...table>field>fieldproperty>fieldpropertyvalue,在第一种情况下,fieldpropertyandvalue 的示例将是 datatype=integer 和分隔的 属性 和值的示例分别为 datatypeinteger。这种任何属性都是图中新节点的方法更接近于 RDF 存储使用的方法。


总而言之,必须声明可以使用专门的图形数据库,使用它们自己的查询语言,如 neo4j 中的 cypher 和其他语言中的 sparql,但由于图形设计总体上很简单,a关系数据库可以满足我们的需求。

关系解决方案

响应relational-databasehierarchic-data标签,后者在前者中是行人。

1.1 初步

由于要求和区别:

  • 真正的SQL平台(符合标准;服务器架构、统一语言等)和
  • 假装的“SQL”程序(没有体系结构;这些程序中散布着一些语言;没有事务;没有 ACID;等等)不符合标准,因此使用该术语不正确,和
  • 非SQL

因此我使用 RecordField 来涵盖所有可能性,而不是关系术语,后者将传达关系定义。

所有的可能性都得到了满足,但是关系型和 SQL 兼容的方法(例如 MS SQL 服务器)被认为是最好的方法,因为它有 40 年的建立和成熟, 并且没有替代品。

  • SQL 个平台的集合;假装“SQL”应用程序;和非 SQL 套件,标记为 DataSource.

1.2 合规性

此解决方案是 100% 关系型的:Codd 的 Relational Model,而不是学术界标榜为“关系型”的不合标准的替代方案:

  • 它可以在任何 SQL 兼容的平台上实现

  • 它具有关系完整性(这是逻辑的,超越了参照完整性,这是SQL和物理的);关系力量;和关系速度。

  • 所有更新交互都很简单,通过 SQL ACID 事务。

  • 不对伪造的“SQL”和非SQL提供任何保证。


2 解

2.1 概念

作为开发人员,我很感激您关注数据值以及如何检索它。但是需要先定义两层,才能支持第三层数据:

  1. 目录潜力
    蓝色(参考集群)。
    组织可能使用的市场上可用的 DtaSource 和定义。假设42,根据你的描述。

    • 我只会将此委托给 developer,而不是 user_admin,因为它的设置很关键(较低级别取决于它),并且它描述了 每个数据源的物理 能力和限制。
  2. 实际目录
    绿色(标识簇)。
    组织实际签约和使用的数据源和定义。比方说 12。此时我们有连接地址;端口;和 users。它直接和通过 CHECKS 调用 Functions.

    来约束 CataloguePotential

    该级别定义内容(实际存在的table),不包含任何数据值。

    • 保持 SQL 心态,因为这是最谨慎的做法,因为它是一个成熟的标准,已有 40 年的成熟期,因为它给了我们最大的灵活性: CatalogueActual 形成 SQL 目录。

    • 同样,我对集体中的对象使用了术语 RecordField,而不是 TableColumn,这将暗示关系和 SQL 含义。

    • SQL 平台
      该级别可以由查询 SQL 目录的程序自动填充。

    • "SQL" 应用程序和非 SQL 套件
      由于没有目录,人口是手动的。它可以通过 user_admin 来完成。约束将是您的程序尝试尝试查询以验证用户提供的 table 定义。

  3. 当前数据
    黄色(交易集群)
    user 已通过他的连接从数据源查询网页的当前数据。假设是,我将 user::webpage 视为中心,并进行管理(每个连接一个 user;每个网页一个 user),而不是 OO 对象。

    • 如果 OO 对象不可靠(取决于您使用的库),或者所有用户网页中只有一组对象,则需要添加更多约束。

2.2 方法

你需要:

  1. 简单层次结构
    一个单亲层次结构,用于复制 SQL 服务器目录中的固定级别定义,以及为假装“SQLs”和非 SQLs.

    • 关系层次结构以及 SQL 实施细节在 Hierarchy 文档中得到了完整定义。 [§ 2.2] 中给出了简单或单亲模型。
    • 层(不是锚点)是潜在的数据源
    • Leaf 级别是包含数据的级别,可以是 RecordStruct(对于集体中允许的数据)。
      • 在Potential Datasource中,是有代表性的,真正的RecordTypeFieldType
      • 在Actual DataSource中,它是一个实际的Record,它是RecordType的一个实例,实际的Field,它是FieldType的一个更窄的定义.
  2. Method/Struct
    为了处理 Struct,在定义方面等同于 Record,并允许 Struct 包含 Struct,我们需要一个抽象级别,这是...

    • Article

      • aField,也就是原子存储单位,xor
      • a Struct,其中包含 Articles
    • 需要独占子类型集群,在 Subtype 文档

      中与 SQL 实施细节一起完全定义
  3. Method/Array
    要支持 FieldsArray

    • 这些是对 Field 的多值依赖,因此作为子 table 实现。
      • 对于标量,NumElement 为 1。
      • 这使得 Field 上的独占子类型集群成为多余的标量。

2.3 关系数据模型

这是七次迭代后的进度。它显示 Table-关系级别(属性级别对于内联图形来说太大)。

  1. 假设
    JS(或其他)对象是 webpage/user 的本地对象。如果您的对象是全局对象,则值 tables 需要限制为 Connection。

  2. 数据模型在单个PDF:

    • Table 关系度
    • Table关系水平+样本数据
    • Table属性级别+示例数据。

2.4 表示法

  • 我所有的数据模型都在 IDEF1X 中呈现,从 1980 年代早期开始可用,关系数据建模的唯一符号,自 1993 年以来的标准。

  • IDEF1X Introduction 对于 Codd 的关系模型 或其建模方法的新手来说是必不可少的读物。请注意,IDEF1X 模型是完整的,它们具有丰富的细节和精度,显示了 所有 所需的细节,而本土模型由于不了解标准的要求,因此定义少得多。这意味着,符号需要被完全理解。