Mysql 用户设置的数据库设计

Mysql 用户设置的数据库设计,mysql,sql-server,database,database-design,relational-database,Mysql,Sql Server,Database,Database Design,Relational Database,在设计用于存储用户设置的表时,下列哪个选项(如果有)被视为最佳做法 (备选方案1) (备选方案2) 为每个设置创建一个新表,例如,通知设置要求您创建: "USER_ALERT_SETTINGS" -Id -UserId -EmailAdded (i.e true) -EmailRemoved -PasswordChanged ... ... "USER_EMAIL_SETTINGS" -Id -UserId -EmailLimitMax .... (备选方案3) 每个选项都有其位置,选择取

在设计用于存储用户设置的表时,下列哪个选项(如果有)被视为最佳做法

(备选方案1)

(备选方案2)

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

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

"USER_EMAIL_SETTINGS"
-Id
-UserId
-EmailLimitMax
....
(备选方案3)


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

选项1:优点:

  • 可以处理很多选择
  • 可以轻松添加新选项
  • 可以开发一个通用接口来管理选项
备选案文1:缺点

  • 添加新选项后,使用新选项更新所有用户帐户会更加复杂
  • 选项名称可能会失控
  • 允许选项值的验证更加复杂,需要额外的元数据
备选方案2:专业人员

  • 每个选项的验证都比选项1容易,因为每个选项都是一个单独的列
备选案文2:缺点

  • 每个新选项都需要数据库更新
  • 有了许多选项,数据库表可能会变得更难使用
    • 很难评估“最佳”,因为它取决于您希望运行的查询类型

      选项1(通常称为“属性包”、“名称-值对”或“实体-属性-值”或EAV)使存储模式未知的数据变得容易。然而,这使得运行公共关系查询变得很困难,有时甚至不可能。例如,想象一下运行

      select count(*) 
      from USER_ALERT_SETTINGS 
      where EmailAdded = 1 
      and Email_LimitMax > 5
      
      这将很快变得非常复杂,特别是因为您的数据库引擎可能无法以有数字意义的方式比较varchar字段(因此“>5”可能无法按您预期的方式工作)

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

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

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

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

    • Value可能有一个字符串数据类型来容纳可能进入其中的所有不同值。因此,您已经丢失了真正的数据类型–整数、布尔值、浮点值等,以及RDMBS在插入错误值时将执行的数据类型检查。同样,你给自己带来了一个潜在的QA问题。即使对于字符串值,也无法约束列的长度

    • 不能基于代码在列上定义默认值。因此,如果您希望EmailLimitMax默认为5,则无法执行此操作

    • 类似地,不能在“值”列上设置检查约束以防止无效值

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

    • 如前所述,在多个标记上应用条件的情况下查找用户ID的查询变得更难编写–它需要为每个被测试的条件向表中加入一个连接

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

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


    • 我用过财产袋;但这仅仅是一种逃避,我常常为此感到后悔。我从来没有说过“哎呀,我真希望我把那个明确的专栏变成一个财产袋。”

      其他答案巧妙地概括了你各种选择的利弊

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

      见以下ERD:

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

      "USER" -Name ... -ConfigXML
      select count(*) 
      from USER_ALERT_SETTINGS 
      where EmailAdded = 1 
      and Email_LimitMax > 5
      
      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                   |
      +------+---------+------------+--------------------------+---------------------+
      
      -- 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')
      ;
      
      -- 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