将数组数据保存在规范化表中

时间:2013-11-30 06:49:46

标签: 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是否适用于多个表。 插入不适用于多个表,因此我认为这会使进一步的规范化无关紧要。

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

2 个答案:

答案 0 :(得分:1)

更新(请参阅下面的上一版本):

使用您的解决方案和我以前的版本实际上存在的问题是并非保存了阵列结构中的所有信息。您仍然可以通过将选项名称的最后一个元素放在单独的字段中来避免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            |
+----------+-----------------------------+------------------+

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

+----------+-----------------------------+------------------+
| 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 |
+----------+-----------------------------+------------------+---------+

答案 1 :(得分:0)

您重新设计了一个名为Entity-Attribute-Value的设计。这是标准化意味着什么。在你的问题中提出的想法,以及@ unique2的答案中提出的解决方案,都不符合正常形式的任何定义 - 它们甚至不是关系。

你是对的,唯一的约束不起作用。外键约束,甚至NOT NULL都没有。例如,您如何使数据库强制执行user.api_access的强制值?

有关为什么EAV是非关系型设计的更多讨论,请参阅我的博客EAV FAIL或许多my answers on StackOverflow个问题。

有关支持用户定义属性的替代解决方案,请参阅我的演示文稿Extensible Data Modeling。我概述了以下的优点和缺点:

  • 额外列
  • 实体 - 属性 - 值
  • 类表继承
  • 序列化LOB
  • 反向索引