Mysql – 灵活,类似excel的结构

我最近inheritance了一个已经开始的项目,现在我有一个挑战。 其中一个要求是允许用户在应用程序内部创build一个“数据库”,这个数据库可以具有可变数量的用户定义列(这是一个类似于Excel的结构)。

这里是我当前结构的sqlfiddle 。

这里是我用来提取行的查询:

select `row`, group_concat(dd.value order by field(`col`, 1, 2, 3) asc) as `values` from db_record dr, db_dictionary dd where dr.database_id in (1, 2, 3) and dr.database_dictionary_id = dd.id group by `row` order by group_concat(dd.value order by field(`col`, 1, 2, 3) asc); 

通过使用group_concat()来实现按任意列进行sorting的能力。

我正在考虑这个devise,因为我对性能和满足要求有一些怀疑:

  • 它必须是可sorting的(按任意列),这意味着用户按第2列对asc进行sorting,并且行被正确sorting。
  • 它必须是可search/可过滤的。 用户可以按任意列中的值进行过滤,只有包含search短语的行才能返回。

我认为第一个要求是由我上面粘贴的查询处理。 第二个 – 我也试着用LIKE在查询中添加HAVING子句,但它比较了整个GROUP_CONCAT()结果。

有人可以build议,目前的数据库结构是否可以达到目的,并帮助我满足后者的要求? 或者,也许有更好的方法来解决这个问题?

最后一个问题,是否有可能返回一个查询中的每列的值? 在数据库中,logging如下所示:

 ------------------------------------------- | database_id | dictionary_id | row | col | ------------------------------------------- | 1 | 1 | 1 | 1 | ------------------------------------------- | 2 | 2 | 1 | 2 | ------------------------------------------- | 3 | 3 | 1 | 3 | ------------------------------------------- 

我想获得按行分组的查询结果,类似于:(列1 .. 3值是dictionary_id值)

 ---------------------------------------- | row | column 1 | column 2 | column 3 | ---------------------------------------- | 1 | 1 | 2 | 3 | ---------------------------------------- 

在MySQL中可以实现吗? 或者唯一的解决办法是使用GROUP_CONCAT(),然后我可以使用PHP来拆分成列?

我需要一个灵活而有效的结构,我希望有人能就此提出build议,我会很感激任何帮助或build议。

Excel的2-的MySQL

Excel格式灵活,dynamic的自适应到MySQL关系模式

此解决scheme的方法可能适用于其他关系数据库系统,因为它不依赖于MySQL的任何特定function,除了符合SQL的DDL和DML命令。 此数据库的维护可以通过内部数据库约束和存储过程API的组合来处理,也可以通过备用脚本语言和用户界面从外部进行处理。 本文的重点是模式devise的目的,数据和支持价值的组织,以及扩展的潜在的附加增强点。

架构概述和devise概念来调整电子表格

该模式利用了电子表格网格上的每个数据点都可以由唯一键组合来表示的假设。 最简单的组合可以是行列坐标对,例如“A1”(列A,行号1)或“G72”(列G,行号72)

本演示将演示如何以电子表格forms将以下数据样本调整为可重复使用的多用户关系数据库格式。

Excel仿真的电子表格数据示例

一对坐标还可以包含唯一指定的电子表格/迷你数据库ID值。 对于多用户环境,通过添加支持的用户ID值来关联每个电子表格ID,仍然可以使用相同的模式。

定义最小的模式单位:vector

在将所有关于每个数据点的识别元信息捆绑在一起之后,该集合现在被标记有单个全局唯一的ID,这对于现在可能看起来像“ vector ”的目录。

通过math定义的VECTOR是多个组件及其值的集合,用于简化存在于通过多个(n)维度描述的空间中的问题的解决scheme。

该解决scheme是可扩展的:小型数据库可以小至2行x 2列或数百至数千行和列宽。

轻松search,sorting和透视

要从具有共同属性的向量的数据值构buildsearch查询,例如:

  1. 数据库/电子表格ID和所有者(示例,10045,所有者='HELEN')
  2. 同列(例如,列“A”)

你的数据集将是所有vectorID和它们相关的数据值,它们具有这些共同的值。 枢轴输出可以用一般简单的matrix代数变换来完成……一个电子表格网格只有两个维度,所以它不会那么难!

处理不同的数据types:一些devise注意事项

简单的方法 :将所有的数据存储为VARCHARtypes,但跟踪原始数据types,以便在查询向量的数据值时,可以应用正确的转换函数。 只要保持一致并使用您的API或input过程警惕地警惕数据存储中您的数据的人口…最后要debugging的是一个数字转换函数,它遇到了STRINGtypes的字符。

下一部分包含用于设置单表解决scheme的DDL代码,该解决scheme使用多个列来pipe理可能位于给定电子表格网格中的不同可能数据types。

通过MySQL服务电子表格网格的单表解决scheme

下面是在MySQL 5.5.32上制定的DDL。

 -- First Design Idea... Using a Single Table Solution. CREATE TABLE DB_VECTOR ( vid int auto_increment primary key, user_id varchar(40), row_id int, col_id int, data_type varchar(10), string_data varchar(500), numeric_data int, date_data datetime ); -- Populate Column A with CITY values INSERT INTO DB_VECTOR (user_id, row_id, col_id, data_type, string_data, numeric_data, date_data) VALUES ('RICHARD', 2, 1, 'STRING', 'ATLANTA', NULL, NULL); INSERT INTO DB_VECTOR (user_id, row_id, col_id, data_type, string_data, numeric_data, date_data) VALUES ('RICHARD', 3, 1, 'STRING', 'MACON', NULL, NULL); INSERT INTO DB_VECTOR (user_id, row_id, col_id, data_type, string_data, numeric_data, date_data) VALUES ('RICHARD', 4, 1, 'STRING', 'SAVANNAH', NULL, NULL); INSERT INTO DB_VECTOR (user_id, row_id, col_id, data_type, string_data, numeric_data, date_data) VALUES ('RICHARD', 5, 1, 'STRING', 'FORT BENNING', NULL, NULL); INSERT INTO DB_VECTOR (user_id, row_id, col_id, data_type, string_data, numeric_data, date_data) VALUES ('RICHARD', 6, 1, 'STRING', 'ATHENS', NULL, NULL); -- Populate Column B with POPULATION values INSERT INTO DB_VECTOR (user_id, row_id, col_id, data_type, string_data, numeric_data, date_data) VALUES ('RICHARD', 2, 2, 'NUMERIC', NULL, 1500000, NULL); INSERT INTO DB_VECTOR (user_id, row_id, col_id, data_type, string_data, numeric_data, date_data) VALUES ('RICHARD', 3, 2, 'NUMERIC', NULL, 522000, NULL); INSERT INTO DB_VECTOR (user_id, row_id, col_id, data_type, string_data, numeric_data, date_data) VALUES ('RICHARD', 4, 2, 'NUMERIC', NULL, 275200, NULL); INSERT INTO DB_VECTOR (user_id, row_id, col_id, data_type, string_data, numeric_data, date_data) VALUES ('RICHARD', 5, 2, 'NUMERIC', NULL, 45000, NULL); INSERT INTO DB_VECTOR (user_id, row_id, col_id, data_type, string_data, numeric_data, date_data) VALUES ('RICHARD', 6, 2, 'NUMERIC', NULL, 1325700, NULL); 

有一个逃跑的诱惑,并开始过度正常化这个表,但冗余可能不会那么糟糕。 分离与电子表格相关的信息(例如OWNER / USER名称和其他人口统计信息),但要保持一致,直到您了解基于vector的devise的目的和一些性能权衡。

一种这样的超标准化模式折衷是现在所需的数据值分散在多个表中。 过滤标准现在可能必须适用于这些连接涉及的不同表格。 看起来很讽刺,我注意到,即使有一些明显的冗余,在查询和报告方面,扁平化,单一的表格结构也很好。

附加说明:为通过外键关联链接到主数据源的支持数据创build表格是一个不同的故事……表格之间存在一种隐含关系,但是许多RDBMS系统实际上是基于外键连接进行自我优化的。

例如:如果search带有数百万条logging的USER_OWNER列,如果它被FK链接到一个支持表(可识别20个用户的有限用户列表),那么它将从潜在的提升中受益…这也被称为CARDINALITY的问题,帮助数据库build立可以通过未知数据集进行捷径的执行计划。

恢复数据:一些示例查询

首先是一个基本的查询,将数据以有组织的,类似网格的格式返回…就像原来的Excel页面一样。

  SELECT base_query.CITY, base_query.POPULATION FROM ( SELECT CASE WHEN col_a.data_type = 'STRING' THEN col_a.string_data WHEN col_a.data_type = 'NUMERIC' THEN col_a.numeric_data WHEN col_a.data_type = 'DATETIME' THEN col_a.date_data ELSE NULL END as CITY, CASE WHEN col_b.data_type = 'STRING' THEN col_b.string_data WHEN col_b.data_type = 'NUMERIC' THEN col_b.numeric_data WHEN col_b.data_type = 'DATETIME' THEN col_b.date_data ELSE NULL END as POPULATION FROM db_vector col_a, db_vector col_b WHERE ( col_a.col_id = 1 AND col_b.col_id = 2 ) AND ( col_a.row_id = col_b.row_id) ) base_query WHERE base_query.POPULATION >= 500000 ORDER BY base_query.POPULATION DESC 

即使这里的基本查询仍然是一个小的特定的pipe理可扩展,通用的解决scheme的电子表格的宽度或长度的一个或多个值。 但是你可以看到这个例子中的内部查询是如何保持不变的,一个完整的数据集可以很快被过滤或者以不同的方式sorting。

一些分手的想法:(又名一些可选的家庭作业)

  1. 这可以通过灵活的多表解决scheme来解决。 我能在三年内完成这个任务。

    DB_VECTOR(正如你已经看到的)经历了一些修改:数据值被移出,严格的位置信息(行和列ID)加上全局唯一的电子表格ID被留下。

    DB_DATA被用作原始数据字段的最终归属:STRING_DATA,NUMERIC_DATA和DATE_DATA …每个由VID(向量id)唯一标识的logging。

在多表解决scheme中,我将唯一VID用作具有多个关联维度(所有者,工作表ID,行,列等)的指针来指向其相应的数据值。

这个devise实用程序的一个例子: “查找”函数或查询的可能性,它根据数据本身的属性或向量组件(行,列,列,表单ID等)…或组合。

这种可能性不是在处理这个模式的代码的不同部分之间传递大量的数据(电子表格本身),查询只处理特定的属性,只是推送列表(数组?)或一组普遍唯一的标识符指向所需的数据。

  1. 初始化新电子表格 :如果您追求多表格devise,那么您的DB_VECTOR表将成为一个中空的带有指向实际数据指针的集合。 在填充原始数据值之前,VECTOR_ID(vid)将需要先存在,以便可以链接这两个值。

  2. 哪一种方法是? :使用行和列ID的数字值似乎是最简单的方法,但我注意到:(a)我很容易混合列和行…更糟的是,没有注意到,直到为时已晚; (b)Excel实际上有一个约定:行(数字),列(字母:A到ZZ +?)用户在使用我们的模式时是否会错过约定或迷路? 对我们的数据向量采用非数字识别scheme有什么问题吗?

  3. 另一个维度 :Excel电子表格有多张表。 如何支持这个惯例改变你的vector的devise? 工程师和科学家甚至把这个极限推到了人类可以看到的三个维度之外。 那会如何改变呢? 如果你试了一下,你是否发现它是否强加了一个限制,或者它有什么关系?

  4. 陷入这一个… :我目前的DB_VECTOR表包含一个额外的名为“DETAILS”的VARCHAR值。 我发现它是一个有用的catch-bin,可以一直到最低级别(VECTOR ID / POINTER)级别,也可以是独一无二的自定义属性,也可以用它来创build一个不寻常的向量可能没有一个容易定义的关系(如Excel的“范围名称”属性)…你会用它来做什么?

如果你还在我身边…谢谢。 这是数据库devise中一个具有挑战性的思想练习。 为了清楚起见,我故意遗漏了关于优化和性能考虑的全面讨论……也许以后要考虑一下。

对你的项目最好的祝愿。

为什么不将表格存储模型化为表格? 只需构buildALTER|CREATE|DROP TABLE语句,就可以获得实际拥有数据库服务器的所有好处。 索引和SQL浮现在脑海。

示例模式:

 CREATE TABLE Worksheets ( WorksheetID int auto_increment primary key, WorkbookID int not null, Name varchar(256) not null, TableName nvarchar(256) not null ); CREATE TABLE Columns ( ColumnID int auto_increment primary key, WorksheetID int not null, ColumnSequenceNo int not null, Name varchar(256) not null, PerceivedDatatype enum ('string', 'number') not null ) -- Example of a dynamically generated data table: -- Note: The number in the column name would correspond to -- ColumnSequenceNo in the Columns table CREATE TABLE data_e293c71b-b894-4652-a833-ba817339809e ( RowID int auto_increment primary key, RowSequenceNo int not null, Column1String varchar(256) null, Column1Numeric double null, Column2String varchar(256) null, Column2Numeric double null, Column3String varchar(256) null, Column3Numeric double null, -- ... ColumnNString varchar(256) null, ColumnNNumeric double null ); INSERT INTO Worksheets (WorkbookID, Name, TableName) VALUES (1, `Countries`, `data_e293c71b-b894-4652-a833-ba817339809e`); SET @worksheetID = LAST_INSERT_ID(); INSERT INTO Columns (WorksheetID, ColumnSequenceNo, Name, PerceivedDatatype) VALUES (@worksheetID, 1, `Country Name`, `string`), (@worksheetID, 2, `Population`, `numeric`), (@worksheetID, 3, `GDP/person`, `numeric`); -- example of an insert for a new row: -- if the new data violates any perceived types, update them first INSERT INTO data_e293c71b-b894-4652-a833-ba817339809e ( RowSequenceNo, Column1String, Column2String, Column2Numeric, Column3String, Column3Numeric) VALUES ( 1, `United States of America`, `3000000`, 3000000, `34500`, 34500); -- example of a query on the first column: select * from data_e293c71b-b894-4652-a833-ba817339809e where Column1String like `United%`; -- example of a query on a column with a numeric perceived datatype: select * from data_e293c71b-b894-4652-a833-ba817339809e where Column3Numeric between 4000 and 40000; 

道德的故事是,你不应该争取数据库服务器 – 使用它的优势。

 select `row`, group_concat(if(field(`row`, 1), dd.value, null)) as row1, group_concat(if(field(`row`, 2), dd.value, null)) as row2, group_concat(if(field(`row`, 3), dd.value, null)) as row3 from db_record dr left join db_dictionary dd on (dr.dictionary_id = dd.id) where dr.database_id = 0 group by `column` having row1 like '%biu%' order by `row` uni; 

我的第一印象是,你可能会过度这样做。 我猜你希望在所有数据库字典(玩家)中获得3个或更多玩家组合的排列组合。 sqlfiddlebuild议将所有这些logging在db_record表中以便稍后检索。

使用group_concat非常昂贵,使用'having'也是如此。 当您查看原始的sqlfiddle的执行计划,它在“Extra”列中说

 Using where; Using temporary; Using filesort 

“使用临时文件;使用filesort”是使用临时表的低效率的performance,并且在filesort期间必须多次击中磁盘。 第一个执行时间是25ms(在被caching之前,在第二次执行之后虚拟地降低到2ms)

对于原来的问题,在“应用程序”里面创build一个“数据库”? 如果你的意思是一个灵活的数据库内的数据库,你可能过度使用关系数据库。 尝试将某些职责转移到应用层代码(php?),在数据库之外,并让关系数据库尽其所能,将相关数据表关联起来。 把事情简单化。

经过一番思考,我想我可能有一个解决scheme,但我不知道这是否是最好的。 在应用程序中运行查询之前,我已经知道虚拟“数据库”有多less个列,并且由于我知道需要search哪个列(本例中为第3列),因此可以构build如下查询:

 select `row`, group_concat(if(field(`column`, 1), dd.value, null)) as column1, group_concat(if(field(`column`, 2), dd.value, null)) as column2, group_concat(if(field(`column`, 3), dd.value, null)) as column3 from db_record dr left join db_dictionary dd on (dr.dictionary_id = dd.id) where dr.database_id = 1 group by `row` having column3 like '%biu%' order by `columns` asc; 

所以,在PHP中,我可以为每列添加group_concat(if(...)) ,并添加HAVING子句进行search。

但是,如果可能的话,我想获得关于该解决scheme的一些反馈。