Mysql - 灵活,类似excel的结构

时间:2014-03-02 09:56:31

标签: mysql sql excel

我最近继承了一个已经开始的项目,现在我有一个挑战。其中一个要求是允许用户在应用程序内部创建一个“数据库”,该数据库可以包含可变数量的用户定义列(它是一个类似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()实现按任何列排序的能力。

我正在考虑这种设计,因为我对性能和满足要求有一些疑问:

  • 它必须是可排序的(通过任何列),这意味着用户按列2排序asc,并且行正确排序。
  • 必须是可搜索/可过滤的。用户可以按任何列中的值进行过滤,并且只应返回包含搜索短语的行。

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

有人可以建议,当前的数据库结构是否可以用于此目的并帮助我满足后者的要求?或者也许有更好的解决方法?

最后一个问题,是否可以在一个查询中返回每列的值?在DB中,记录如下所示:

-------------------------------------------
| 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拆分成列?

我需要一个灵活有效的结构,我希望有人可以就此提出建议,我真的很感激任何帮助或建议。

5 个答案:

答案 0 :(得分:4)

的Excel -2- MySQL的

将Excel格式灵活,动态地调整为MySQL关系模式

此解决方案的方法可能适用于其他关系数据库系统,因为除了符合SQL的DDL和DML命令之外,它不依赖于MySQL的任何特定功能。可以通过内部数据库约束和存储过程apis的组合来处理此数据库的维护,或者通过备用脚本语言和用户界面在外部处理。本演练的重点是架构设计的目的,数据和支持值的组织以及其他增强功能的潜在扩展点。

调整电子表格的模式概述和设计概念

架构利用了一个假设,即电子表格网格上的每个数据点都可以由唯一的键组合表示。最简单的组合是行列坐标对,例如" A1" (A栏,第1行)或" G72" (G栏,第72行)

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

Sample Spreadsheet Data for Excel Simulation

一对坐标还可以包括唯一分配的电子表格/迷你数据库ID值。对于多用户环境,通过添加支持用户ID值以与每个电子表格ID关联,仍可以使用相同的架构。

定义最小的模式单元:向量

将所有关于每个数据点的识别元信息捆绑在一起后,该集合现在被标记为单个全局唯一ID,现在某些ID可能看起来像" 向量"

  

mathematical definition VECTOR 是多个组件的集合,其值用于简化通过多个(n)维度描述的空间中存在的问题的解决方案。

解决方案是可扩展的:迷你数据库可以小到2行x 2列或数百到数千行和列宽。

轻松搜索,排序和转动

根据具有共同属性的向量的数据值构建搜索查询,例如:

  1. 数据库/电子表格ID和所有者(例如,10045,所有者='海伦')
  2. 相同列:(示例,列" A")
  3. 您的数据集将是所有向量ID及其相关数据值,这些值具有这些常见值。可能通过一些简单的矩阵代数转换来完成枢轴输出...电子表格网格只有两个维度,所以它不会那么难!

    处理不同的数据类型:一些设计注意事项

    简单方法:将所有数据存储为VARCHAR类型,但跟踪原始数据类型,以便在查询向量的数据值时可以应用正确的转换函数。只需保持一致并使用您的API或输入流程警惕地警告数据存储中的数据填充......您最不希望调试的最后一件事就是遇到了STRING类型字符的数字转换函数。

    下一节包含用于设置单表解决方案的DDL代码,该解决方案使用多列来管理可能在给定电子表格网格中托管的不同可能数据类型。

    通过MySQL提供电子表格网格的单表解决方案

    下面是 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);
    

    有一种诱惑,就是跑掉并开始过度规范化这个表,但冗余可能并不那么糟糕。分离与电子表格相关的信息(例如所有者/用户名和其他人口统计信息),但除非您了解基于矢量的设计的目的和一些性能权衡,否则将其保持在一起。

      

    使用过度规范化模式的一个这样的权衡是现在所需的数据值分散在多个表中。现在,过滤条件可能必须应用于这些连接中涉及的不同表。看起来很讽刺,我观察到,尽管存在一些明显的冗余,但在查询和报告时,扁平的,单一的表结构仍然很好。

         

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

         
        

    例如:如果将FK链接到支持表,并且有20个人的有限用户列表,那么搜索带有数百万条记录的USER_OWNER列会从潜在的提升中受益...也被称为 CARDINALITY 的问题,这有助于数据库构建执行计划,可以通过其他未知的数据集进行快捷。

      

    恢复数据:一些示例查询

    第一个是基本查询,以有组织的网格状格式将数据拉出来......就像原始的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
    

    即使这里的基本查询仍然有点特定,无法管理宽度或长度中一个或多个值的电子表格的可扩展通用解决方案。但是您可以看到此示例中的内部查询如何保持不变,并且可以以不同方式快速过滤或排序完整的数据集。

    一些离别的想法:( a.k.a.一些可选的家庭作业)

    1. 可以通过灵活的多表解决方案来解决这个问题。我能够在 THREE 中完成此任务。

      DB_VECTOR(正如您已经看到的)进行了一些修改:数据值被移出,严格的位置信息(行和列ID)以及全球唯一的电子表格ID被遗忘。

      DB_DATA用作原始数据字段的最终主页:STRING_DATA,NUMERIC_DATA和DATE_DATA ...每个记录由VID(向量ID)唯一标识。

    2.   

      在多表解决方案中,我使用唯一的VID作为具有多个关联维度(所有者,工作表ID,行,列等)的指针,以指向其对应的数据值。

      此设计实用程序的一个示例:"查找"功能或查询,用于根据数据本身的属性或向量组件(行,列,工作表ID等)或组合来标识向量ID集合及其指向的数据。

      可能的是,不是在处理此模式的代码的不同部分之间传播大量数据(电子表格本身),查询只处理特定属性而只是推送列表(数组) ?)或一组通用唯一ID,指向需要的数据。

      1. 初始化新的电子表格:如果您继续使用多表设计,您的DB_VECTOR表将成为一个空心的二进制集合,其中包含指向实际数据的指针。在填充原始数据值之前,首先需要存在VECTOR_ID(vid),以便链接这两个值。

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

      3. 又一个维度:Excel电子表格包含多个工作表。如何支持此约定会改变您的矢量设计?工程师和科学家甚至将这个极限推到人类可以看到的三个维度以上。怎么会改变事情?如果你尝试了,你是否发现它是否施加了限制,或者它是否重要?

      4. 偶然发现... :我当前的DB_VECTOR表包含一个名为" DETAILS"的额外VARCHAR值。我发现它是一个有用的catch-bin,用于杂项自定义属性,它可以是唯一的,一直到最低(VECTOR ID / POINTER)级别......或者你可以使用它为不寻常的集合创建一个自定义标签可能没有易于定义关系的向量(例如Excel'"范围名称"属性)...你会用它做什么?

      5. 如果你还和我在一起......谢谢。这是数据库设计中具有挑战性的思考练习。为了清楚起见,我故意遗漏了关于优化和性能考虑因素的全面扩展讨论......或许以后需要考虑的事情。

        您项目的最佳愿望。

答案 1 :(得分:1)

经过一番思考后,我想我可能有一个解决方案,但我不确定它是否是最好的解决方案。在应用程序中运行查询之前,我已经知道虚拟“数据库”有多少列,并且由于我知道需要搜索哪一列(本例中为第3列),我可以构建一个类似的查询:

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子句进行搜索。

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

答案 2 :(得分:1)

为什么不将表格存储模型化为表格?只需专门构建ALTER|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;

故事的道德是你不应该对抗数据库服务器 - 使用它对你有利。

答案 3 :(得分:1)

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;

答案 4 :(得分:1)

我的第一印象是你可能会过度思考这一点。我猜你希望在所有db字典(玩家)中获得3个或更多玩家组合的排列。并且sqlfiddle建议在db_record表中记录所有这些,以便稍后检索。

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

Using where; Using temporary; Using filesort

“使用临时;使用filesort”表示使用临时表并且在filesort期间必须多次命中磁盘的效率低下。第一个执行时间是25ms(在缓存之前,在第二次执行之后将其虚拟地降低到2ms)

对于原始问题,在“应用程序”中创建“数据库”?如果您指的是数据库中的灵活数据库,您可能过度使用关系数据库。尝试将一些职责转移到应用程序层代码(php?),在数据库之外是,并让关系数据库做最好的事情,关联相关的数据表。保持简单。

相关问题