mysql - 在规范化表中保存数组数据

标签 mysql arrays database-design data-structures entity-attribute-value

我正在使用自定义用户设置功能更新我的用户管理系统。换句话说,我想在数据库中保存自定义用户设置/选项。最初我考虑序列化数据并将其保存到 TEXT 字段。但这样做的缺点是无法通过配置值进行搜索。

因此我决定将数据规范化并存储在不同的表中。由于配置数据是多维的,我不得不将数据扁平化。例如,

Array (
    [user] => Array (
            [is_developer] => 1
            [api_access] => 1
        )

    [api] => Array (
            [scopes] => Array (
                    [0] => astro
                    [1] => user_info
                )

            [default_scope] => user_info
        )

    [astro] => Array (
            [location_id] => 12345
            [timezone] => America/New_York
            [coordinates] => Array (
                    [latitude] => 12.22
                    [longitude] => 24.44
                )

        )

)

转换为

+---------+-----------------------------+------------------+
| user_id | config                      | value            |
+---------+-----------------------------+------------------+
|       1 | user.is_developer           | 1                |
|       1 | user.api_access             | 1                |
|       1 | api.scopes                  | astro            |
|       1 | api.scopes                  | user_info        |
|       1 | api.default_scope           | user_info        |
|       1 | astro.location_id           | 12345            |
|       1 | astro.timezone              | America/New_York |
|       1 | astro.coordinates.latitude  | 12.22            |
|       1 | astro.coordinates.longitude | 24.44            |
+---------+-----------------------------+------------------+

有没有更好的方法来存储这些数据?

优点:

  • 能够搜索自定义选项,例如列出所有开发人员和具有 API 访问权限的用户。
  • 能够只加载一个选项组。
    SELECT * FROM user_config WHERE config LIKE 'astro.%' AND user_id = 1;

缺点:

  • 无法指定唯一键,因为数字数组有保存配置键。如果我将数字索引附加到键(api.scopes.0api.scopes.1 等),那么优势 #1 将部分丢失。
  • 由于唯一键不存在,我无法使用性能更好的 INSERT ... ON DUPLICATE UPDATE 查询。相反,当需要更新数据时,我必须删除现有值并在事务中重新插入数据。

更新:

看了unique2的回答,我又有了新的想法。进一步规范化数据可能会解决唯一键问题。即,将键保存在一个表中,将数据保存在另一个表中。 需要检查 INSERT ... ON DUPLICATE UPDATE 是否适用于多个表。 Insert 不适用于多个表,所以我想这使得进一步规范化变得无关紧要.

mysql> SELECT * FROM user_config;
+-----------+---------+-----------------------------+
| config_id | user_id | config                      |
+-----------+---------+-----------------------------+
|         1 |       1 | api.default_scope           |
|         2 |       1 | api.scopes                  |
|         3 |       1 | astro.coordinates.latitude  |
|         4 |       1 | astro.coordinates.longitude |
|         5 |       1 | astro.location_id           |
|         6 |       1 | astro.timezone              |
|         7 |       1 | user.api_access             |
|         8 |       1 | user.is_developer           |
+-----------+---------+-----------------------------+
8 rows in set (0.00 sec)

mysql> SELECT * FROM user_config_value;
+-----------+------------------+
| config_id | value            |
+-----------+------------------+
|         1 | user_info        |
|         2 | astro            |
|         2 | user_info        |
|         3 | 12.22            |
|         4 | 24.44            |
|         5 | 12345            |
|         6 | America/New_York |
|         7 | 1                |
|         8 | 1                |
+-----------+------------------+
9 rows in set (0.00 sec)

注意:

  • 将数据存储在 NOSQL 数据库中不是一种选择。
  • 实际上,这更像是一个学术问题,因为实际存储的自定义数据非常小,而且对于极少数用户来说也是如此。因此,在我的特定情况下,将其存储为序列化数组或加载整个数组不会产生巨大的开销。

最佳答案

更新(见下方之前的版本):

您的解决方案和我以前的版本实际上都存在一个问题,即并非您的数组结构中的所有信息都被保存。您仍然可以通过将选项名称的最后一个元素放在单独的字段中来避免 api.scopes.1。如果将其与软删除结合使用,则可以使用 INSERT ... ON DUBLICATE UPDATE

+----------+-------------------+---------------+------------------+---------+
| user_id* | config_group*     | config*       | value            | deleted |
+----------+-------------------+---------------+------------------+---------+
|        1 | user              | is_developer  | 1                |       0 |
|        1 | user              | api_access    | 1                |       0 |
|        1 | api.scopes        | 1             | astro            |       0 |
|        1 | api.scopes        | 2             | user_info        |       0 |
|        1 | api               | default_scope | user_info        |       0 |
|        1 | astro             | location_id   | 12345            |       0 |
|        1 | astro             | timezone      | America/New_York |       0 |
|        1 | astro.coordinates | latitude      | 12.22            |       0 |
|        1 | astro.coordinates | longitude     | 24.44            |       0 |
+----------+-----------------------------------+------------------+---------+
* marks key columns

上一版本:

如果您将数据拆分到两个表中,您可以为每个表使用一个唯一的键。

第一个表包含所有采用单个值的配置选项(* 标记关键列):

+----------+-----------------------------+------------------+
| user_id* | config*                     | value            |
+----------+-----------------------------+------------------+
|        1 | user.is_developer           | 1                |
|        1 | user.api_access             | 1                |
|        1 | api.default_scope           | user_info        |
|        1 | astro.location_id           | 12345            |
|        1 | astro.timezone              | America/New_York |
|        1 | astro.coordinates.latitude  | 12.22            |
|        1 | astro.coordinates.longitude | 24.44            |
+----------+-----------------------------+------------------+

第二个表包含由一组值组成的所有选项(没有重复;* 标记关键列):

+----------+-----------------------------+------------------+
| user_id* | config*                     | value*           |
+----------+-----------------------------+------------------+
|        1 | api.scopes                  | astro            |
|        1 | api.scopes                  | user_info        |
+----------+-----------------------------+------------------+

因此,您可以使用数据库来确保数据完整性。 INSERT ... ON DUBLICATE UPDATE 可以自然地处理第一个表。如果您愿意使用软删除,您也可以将它与您的第二个表一起使用:

+----------+-----------------------------+------------------+---------+
| user_id* | config*                     | value*           | deleted |
+----------+-----------------------------+------------------+---------+
|        1 | api.scopes                  | astro            |       0 |
|        1 | api.scopes                  | user_info        |       0 |
|        1 | api.scopes                  | old              |       1 |
+----------+-----------------------------+------------------+---------+

关于mysql - 在规范化表中保存数组数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20297487/

相关文章:

c++ - 在 for 循环中使用数组 - C++

java - java中如何将Int转换为Int[]?

c - 结构给我 "expression must be modifiable value"

c# - 什么时候将标志存储为位掩码比使用关联表更好?

php - 如何查看从单行表中爆炸检索的多个图像

php - 将多维数组插入mysql PHP的递归函数

mysql - MySQL 中用于检索联接表的分层查询

数据库死锁

sql - 表中数据的归档策略和限制

java - 优化大表上的 MySQL 查询