用户设置的数据库设计

69

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

(选项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

这里描述了关于动态设置的优缺点。https://martinfowler.com/apsupp/properties.pdf - Kirill A
5个回答

131

其他答案已经详细阐述了您各种选项的利弊。

我认为对于大多数应用程序来说,您的选项1(属性包)是最佳设计,特别是如果您增加了一些保护措施来防范属性包的弱点。

请参阅以下ERD:

Property Bag ERD

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

SETTING表还具有指示用户设置应由FK或无限制的varchar输入定义的标志。您还可以添加data_typeSETTING以告诉系统如何编码和解释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_VALUE" 中选择还是需要输入 "USER_SETTING.unconstrained_value"。允许用户使用其设置的GUI需要了解提供哪个选项以及如何强制执行 "SETTING.data_type" 以及如果它们存在,则为 "min_value" 和 "max_value" 设置限制。

使用此设计,您可以驱动数据表中的允许设置,包括足够的元数据来强制执行一些基本的约束/合理性检查所选值(或输入)的用户。

编辑:示例查询

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

-- 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 上查看此内容。


1
漂亮的图表,ALLOWED_SETTINGS_VALUE,在这里属性是如何定义的?为什么不选第三个选项? :) - 001
1
@PeterPenzov - 更新什么?你的问题对我来说过于模糊,无法帮助你。总的来说,我可以说数据库设计中没有什么特别的地方会使更新记录变得棘手。你觉得常规的SQL “UPDATE”语句行不通的原因是什么? - Joel Brown
2
@YamiOdymel - 我不会这样做。如果你有单值事实(即一次只有一个正确答案)并且这些事物在你的系统中具有特殊含义(例如头像,令牌,电子邮件等),那么将这些事物作为用户属性存储(即USER表上的列)。如果你的应用程序更加结构化,使用属性包结构不是一个好主意。 - Joel Brown
2
@SarvarNishonboev 我使用自己创建的ERD形状表格与Visio一起工作,但您也可以使用通用的方框和线条,并在每个端点上使用乌鸦脚符号“箭头”。我还使用自定义线条纹理,使其具有手绘效果,在草图/高级草图中非常有用,以传达它是一张草图而不是正式设计。 - Joel Brown
1
@Orion 这取决于你的业务规则。如果有一些设置你期望每个用户都需要,那么你可以编写一个脚本(或其他代码)逐一设置默认值,或者你可以有一个模板用户,像 SQL Server 的模板数据库或 Windows 默认用户文件夹结构。 - Joel Brown
显示剩余22条评论

18

方案一(如注明的“属性袋”)容易实现,几乎没有前期分析。但它有很多缺点。

  1. 如果您想限制 UserSettings.Code 的有效值,则需要一个辅助表来列出有效标记的列表。 因此,您要么(a)没有对 UserSettings.Code 进行验证 - 您的应用程序代码可以将任何值转储到其中,错过捕获错误的机会,或者要在新的有效标记列表上添加维护。

  2. UserSettings.Value 可能具有字符串数据类型,以适应可能输入其中的所有不同值。 因此,您失去了真正的数据类型 - 整数、布尔、浮点等,以及 RDMBS 在插入不正确的值时进行的数据类型检查。 再次,您自己引入了潜在的 QA 问题。 即使对于字符串值,您也失去了对列长度的约束能力。

  3. 无法根据 Code 在列上定义默认值。 因此,如果您想让 EmailLimitMax 默认为 5,则无法这样做。

  4. 类似地,无法在 Values 列上放置 CHECK 约束以防止无效值。

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

  6. 如注明的,查找适用于多个标记的条件的 UserID 的查询变得更加困难 - 它需要为每个被测试条件加入一次表联接。

  • 不幸的是,属性包邀请应用程序开发人员将新代码只是粘贴到属性包中,而不分析它在应用程序的其余部分中如何使用。对于大型应用程序而言,这成为“隐藏”属性的来源,因为它们没有经过正式建模。这就像使用纯标记-值而不是命名属性来进行对象模型一样:它提供了一种逃逸阀门,但您会错过编译器在强类型、命名属性方面所给予的所有帮助。或者像在生产XML时没有模式验证。

  • 列名称方法是自我说明的。表中的列列表告诉任何开发人员可能的用户设置。

  • 我曾经使用过属性包; 但只是作为一个逃生口,我经常后悔。我从未说过“天啊,我希望我把显式列作为属性包”。


    1
    你会推荐什么替代方案? - HummingBird24

    16
    考虑这个简单的例子。
    如果你有2张表,UserTable(包含用户详细信息)和SettingsTable(包含设置详细信息)。那么创建一个新的表UserSettings来关联UserTableSettingsTable,如下图所示。

    用户设置数据库设计

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

    2
    这是一个很好的答案。也许应该稍微复杂一些,以允许多种数据类型的使用。 - user3308043

    5
    每个选项都有其适用的场合,选择取决于您的具体情况。以下是每个选项的优缺点比较:
    选项1:优点
    - 可以处理许多选项 - 新选项可以轻松添加 - 可以开发通用界面来管理选项
    选项1:缺点
    - 当添加新选项时,更新所有用户帐户的新选项更加复杂 - 选项名称可能失控 - 需要额外的元数据才能验证允许的选项值,这使得验证更加复杂
    选项2:优点
    - 每个选项是单独的列,因此每个选项的验证比选项1更容易
    选项2:缺点
    - 每个新选项都需要进行数据库更新 - 如果有很多选项,则数据库表可能变得更难使用

    1
    每个选项都有其优点,但对于大量设置来说,选项1是最佳选择。 - Stephen Senkomago Musoke
    3
    配置也可以存储在数据库中作为 JSON、XML 格式,您对此有何看法? - 001

    4

    很难评估“最佳”是因为它取决于你想运行的查询类型。

    选项1(通常称为“属性包”,“名称值对”或“实体-属性-值”或EAV)使存储预先未知架构的数据变得容易。但是,它使得运行常见的关系查询变得困难或有时不可能。例如,想象一下运行相当于

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

    这将很快变得非常复杂,特别是因为您的数据库引擎可能无法以数字上有意义的方式比较varchar字段(因此"> 5"可能不会按照您的预期工作)。
    我建议先确定您想要运行的查询,然后查看哪种设计最适合支持这些查询。如果您只需要检查单个用户的限制,则属性包就可以了。如果您需要跨所有用户进行报告,那么它可能不太适用。
    对于JSON或XML也是如此-它们适用于存储单个记录,但使查询或报告所有用户更加困难。例如,想象一下搜索电子邮件地址“bob@domain.com”的配置设置-这将需要搜索所有XML文档以找到节点“email address”。

    或者,配置可以作为JSON、XML存储在数据库中,你认为呢? - 001
    你可以随时查询限制,然后再添加新记录,因此这不是问题。 - 001
    我认为你也可以编写一些函数/过程来检查输入的类型,然后根据正确的值执行适当的查询,在这种情况下,“>5”将不会针对 varchar 进行评估。 - SdSaati

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接