【问题标题】:Database design for user settings用户设置的数据库设计
【发布时间】:2012-04-29 14:23:28
【问题描述】:

在设计用于存储用户设置的表时,以下哪些选项(如果有)被认为是最佳做法?

(选项 1)

USER_SETTINGS
-Id
-Code (example "Email_LimitMax")
-Value (example "5")
-UserId

(选项 2)

为每个设置创建一个新表,例如,通知设置需要您创建:

"USER_ALERT_SETTINGS"
-Id
-UserId
-EmailAdded (i.e true)
-EmailRemoved 
-PasswordChanged
...
...

"USER_EMAIL_SETTINGS"
-Id
-UserId
-EmailLimitMax
....

(选项 3)

"USER"
-Name
...
-ConfigXML

【问题讨论】:

标签: mysql sql-server database database-design relational-database


【解决方案1】:

每个选项都有其位置,选择取决于您的具体情况。我正在比较以下每个选项的优缺点:

选项 1:优点:

  • 可以处理许多选项
  • 可以轻松添加新选项
  • 可以开发通用接口来管理选项

选项 1:缺点

  • 添加新选项后,使用新选项更新所有用户帐户会更加复杂
  • 选项名称可能会失控
  • 允许的选项值的验证更加复杂,为此需要额外的元数据

选项 2:优点

  • 每个选项的验证比选项 1 更容易,因为每个选项都是一个单独的列

选项 2:缺点

  • 每个新选项都需要更新数据库
  • 如果有很多选项,数据库表可能会变得更难使用

【讨论】:

  • 各有优势,但选项 1 最适合大量设置
  • 或者配置可以存储在数据库中的JSON,XML,你怎么看?
【解决方案2】:

很难评估“最佳”,因为它取决于您要运行的查询类型。

选项 1(通常称为“属性包”、“名称值对”或“实体属性值”或 EAV)可以轻松存储您事先不知道其架构的数据。但是,它使运行常见的关系查询变得困难——有时甚至是不可能的。例如,想象一下运行相当于

select count(*) 
from USER_ALERT_SETTINGS 
where EmailAdded = 1 
and Email_LimitMax > 5

这很快就会变得非常复杂,尤其是因为您的数据库引擎可能无法以有意义的数字方式比较 varchar 字段(因此 "> 5" 可能无法按您预期的方式工作)。

我会计算出您要运行的查询,然后看看哪种设计最能支持这些查询。如果您所要做的只是检查单个用户的限制,那么属性包就可以了。如果您必须针对所有用户进行报告,则可能不需要。

对于 JSON 或 XML 也是如此 - 可以存储单个记录,但会使查询或报告所有用户变得更加困难。例如,假设搜索电子邮件地址“bob@domain.com”的配置设置 - 这将需要搜索所有 XML 文档以找到节点“电子邮件地址”。

【讨论】:

  • 或者配置可以存储在数据库中的JSON,XML,你怎么看?
  • 在添加新记录之前,您可以随时查询检查限制,因此它不是问题。
  • 我认为您还可以创建一些函数/程序来检查输入的类型,然后根据正确的值执行正确的查询,在这种情况下,">5" 不会针对 varchar 进行评估。
【解决方案3】:

选项 1(如前所述,“财产包”)易于实施 - 很少进行前期分析。但它有很多缺点。

  1. 如果要限制 UserSettings.Code 的有效值,则需要一个用于有效标签列表的辅助表。因此,您要么 (a) 没有对 UserSettings.Code 进行验证——您的应用程序代码可以转储任何值,从而错过捕捉错误的机会,或者您必须在新的有效标签列表中添加维护。

    李>
  2. UserSettings.Value 可能有一个字符串数据类型来容纳可能进入其中的所有不同值。因此,您丢失了真正的数据类型——整数、布尔值、浮点数等,以及 RDMBS 在插入不正确的值时会进行的数据类型检查。再一次,你给自己买了一个潜在的 QA 问题。即使对于字符串值,您也失去了限制列长度的能力。

  3. 您不能根据代码在列上定义 DEFAULT 值。所以如果你想让 EmailLimitMax 默认为 5,你就做不到。

  4. 同样,您不能在 Values 列上设置 CHECK 约束来防止无效值。

  5. 属性包方法失去对 SQL 代码的验证。在命名列方法中,如果 Blah 不存在,“从 UserSettings where UserID = x 中选择 Blah”的查询将得到 SQL 错误。如果 SELECT 在存储过程或视图中,则在应用 proc/view 时会出现错误——在代码投入生产之前。在属性包方法中,您只会得到 NULL。因此,您丢失了数据库提供的另一个自动 QA 功能,并引入了一个可能未被检测到的错误。

  6. 如前所述,查找适用于多个标签的条件的 UserID 的查询变得更难编写 - 它需要为每个正在测试的条件加入表中。

  7. 不幸的是,Property Bag 邀请应用程序开发人员将新代码粘贴到属性包中,而无需分析它将如何在应用程序的其余部分中使用。对于大型应用程序,这成为“隐藏”属性的来源,因为它们没有正式建模。这就像使用纯标记值而不是命名属性来构建对象模型:它提供了一个逃生阀,但是您缺少编译器为您提供的强类型命名属性的所有帮助。或者喜欢在没有模式验证的情况下进行生产 XML。

  8. 列名方法是自记录的。表格中的列列表告诉任何开发人员可能的用户设置是什么。

我用过财产袋;但只是作为一个逃生阀,我经常后悔。我从来没有说过“哎呀,我希望我把那个明确的列变成一个属性包。”

【讨论】:

  • 你会推荐什么?
【解决方案4】:

其他答案巧妙地概述了您的各种选择的利弊。

我相信您的选项 1(财产袋)是大多数应用程序的最佳整体设计,特别是如果您针对财产袋的弱点构建了一些保护。

请参阅以下 ERD:

在上面的 ERD 中,USER_SETTING 表与 OP 非常相似。不同之处在于,此设计没有 varchar CodeValue 列,而是对 SETTING 表进行了 FK,该表定义了允许的设置(代码)和值的两个互斥列。一个选项是可以接受任何类型用户输入的 varchar 字段,另一个选项是合法值表的 FK。

SETTING 表还有一个标志,指示用户设置是应该由 FK 定义还是由不受约束的 varchar 输入定义。您还可以将data_type 添加到SETTING 以告诉系统如何编码和解释USER_SETTING.unconstrained_value。如果您愿意,您还可以添加SETTING_GROUP 表来帮助组织用户维护的各种设置。

这种设计允许您围绕您的设置制定规则。这方便、灵活且易于维护,同时避免了混战。


编辑:更多细节,包括一些例子......

请注意,上面的 ERD 已经增加了更多的列详细信息(SETTING 上的范围值和 ALLOWED_SETTING_VALUE 上的列)。

这里有一些示例记录用于说明。

SETTING:
+----+------------------+-------------+--------------+-----------+-----------+
| id | description      | constrained | data_type    | min_value | max_value |
+----+------------------+-------------+--------------+-----------+-----------+
| 10 | Favourite Colour | true        | alphanumeric | {null}    | {null}    |
| 11 | Item Max Limit   | false       | integer      | 0         | 9001      |
| 12 | Item Min Limit   | false       | integer      | 0         | 9000      |
+----+------------------+-------------+--------------+-----------+-----------+

ALLOWED_SETTING_VALUE:
+-----+------------+--------------+-----------+
| id  | setting_id | item_value   | caption   |
+-----+------------+--------------+-----------+
| 123 | 10         | #0000FF      | Blue      |
| 124 | 10         | #FFFF00      | Yellow    |
| 125 | 10         | #FF00FF      | Pink      |
+-----+------------+--------------+-----------+

USER_SETTING:
+------+---------+------------+--------------------------+---------------------+
| id   | user_id | setting_id | allowed_setting_value_id | unconstrained_value |
+------+---------+------------+--------------------------+---------------------+
| 5678 | 234     | 10         | 124                      | {null}              |
| 7890 | 234     | 11         | {null}                   | 100                 |
| 8901 | 234     | 12         | {null}                   | 1                   |
+------+---------+------------+--------------------------+---------------------+

从这些表中,我们可以看到一些可以确定的用户设置是最喜欢的颜色、项目最大限制和项目最小限制。最喜欢的颜色是字母数字的选择列表。项目最小和最大限制是设置了允许范围值的数字。 SETTING.constrained 列确定用户是从相关的ALLOWED_SETTING_VALUEs 中选择还是需要输入USER_SETTING.unconstrained_value。允许用户使用其设置的 GUI 需要了解提供哪个选项以及如何强制执行 SETTING.data_typemin_valuemax_value 限制(如果存在)。

使用这种设计,您可以表格驱动允许的设置,包括足够的元数据,以对用户选择(或输入)的值执行一些基本的约束/健全性检查。

编辑:示例查询

下面是一些示例 SQL,使用上述数据列出给定用户 ID 的设置值:

-- DDL and sample data population...
CREATE TABLE SETTING
    (`id` int, `description` varchar(16)
     , `constrained` varchar(5), `data_type` varchar(12)
     , `min_value` varchar(6) NULL , `max_value` varchar(6) NULL)
;

INSERT INTO SETTING
    (`id`, `description`, `constrained`, `data_type`, `min_value`, `max_value`)
VALUES
    (10, 'Favourite Colour', 'true', 'alphanumeric', NULL, NULL),
    (11, 'Item Max Limit', 'false', 'integer', '0', '9001'),
    (12, 'Item Min Limit', 'false', 'integer', '0', '9000')
;

CREATE TABLE ALLOWED_SETTING_VALUE
    (`id` int, `setting_id` int, `item_value` varchar(7)
     , `caption` varchar(6))
;

INSERT INTO ALLOWED_SETTING_VALUE
    (`id`, `setting_id`, `item_value`, `caption`)
VALUES
    (123, 10, '#0000FF', 'Blue'),
    (124, 10, '#FFFF00', 'Yellow'),
    (125, 10, '#FF00FF', 'Pink')
;

CREATE TABLE USER_SETTING
    (`id` int, `user_id` int, `setting_id` int
     , `allowed_setting_value_id` varchar(6) NULL
     , `unconstrained_value` varchar(6) NULL)
;

INSERT INTO USER_SETTING
    (`id`, `user_id`, `setting_id`, `allowed_setting_value_id`, `unconstrained_value`)
VALUES
    (5678, 234, 10, '124', NULL),
    (7890, 234, 11, NULL, '100'),
    (8901, 234, 12, NULL, '1')
;

现在是提取用户设置的 DML:

-- Show settings for a given user
select
  US.user_id 
, S1.description 
, S1.data_type 
, case when S1.constrained = 'true'
  then AV.item_value
  else US.unconstrained_value
  end value
, AV.caption
from USER_SETTING US
  inner join SETTING S1
    on US.setting_id = S1.id 
  left outer join ALLOWED_SETTING_VALUE AV
    on US.allowed_setting_value_id = AV.id
where US.user_id = 234

SQL Fiddle 中查看此内容。

【讨论】:

  • 好图,ALLOWED_SETTINGS_VALUE,这里的属性是怎么定义的?为什么不是选项 3? :)
  • @001 - 如果您想要的话,选项 3 没有任何问题。它可以让你在设置中放置任何东西,这取决于你的应用程序来理解它。它不允许您向用户提供有关哪些设置可用/允许的指导 - 至少它会将该指导强制到您的代码中,而不是表驱动它。 ALLOWED_SETTING_VALUE 可能是值的 varchar 和描述/标题的 varchar(可选)。值的实际类型将根据 SETTING.data_type 进行,但为了理智,它会被编码为 varchar。
  • @PeterPenzov - 更新什么?您的问题太模糊,我无法为您提供帮助。总的来说,我可以说数据库设计没有什么奇特之处,这使得更新记录变得棘手。是否有某些原因您觉得常规 SQL UPDATE 语句不起作用?
  • @YamiOdymel - 我不会。如果您有单值的事实(即一次只有一个正确答案)并且如果这些事物在您的系统中具有特殊意义(例如头像、令牌、电子邮件...),那么将这些事物存储为用户的属性(即USER 表上的列)。如果您的应用程序更加结构化,则使用属性包结构不是一个好主意。
  • @SarvarNishonboev 我将 Visio 与我前段时间自己创建的 ERD 形状表一起使用,但您可以使用两端带有鱼尾纹符号“箭头”的通用框和线条。我还使用自定义线条纹理为其提供手绘外观,我发现这在草稿/高级草图中很有用,以传达它是草图而不是正式设计。
【解决方案5】:

考虑这个简单的例子。

如果您有 2 个表,UserTable(包含用户详细信息)和 SettingsTable(包含设置详细信息)。然后创建一个新表 UserSettings 用于关联 UserTable 和 SettingsTable,如下所示

希望您能从这个示例中找到正确的解决方案。

【讨论】:

  • 这是一个很好的答案。允许多种数据类型可能会稍微复杂一些。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-17
  • 1970-01-01
  • 2012-09-17
  • 2017-01-04
相关资源
最近更新 更多