【问题标题】:Using a Single Row configuration table in SQL Server database. Bad idea?在 SQL Server 数据库中使用单行配置表。馊主意?
【发布时间】:2011-01-19 00:17:03
【问题描述】:

在开发购物车应用程序时,我发现我需要根据管理员的偏好和要求保存设置和配置。此信息可以是公司信息、运输帐户 ID、PayPal API 密钥、通知首选项等。

在关系数据库系统中创建一个表来存储单行似乎非常不合适。

存储此信息的适当方式是什么?

注意:我的 DBMS 是 SQL Server 2008,而编程层是用 ASP.NET(用 C# 语言)实现的。

【问题讨论】:

    标签: sql database-design configuration relational-database


    【解决方案1】:

    我过去做过这两种方法——单行表和键/值对表——每种方法都有积极和消极的一面。

    单行

    • 肯定:值以正确的类型存储
    • 肯定的:在代码中更容易处理(由于上述原因)
    • 正:可以为每个设置单独指定默认值
    • 否定:需要更改架构才能添加新设置
    • 否定:如果设置很多,表格会变得很宽

    键/值对

    • 肯定:添加新设置不需要更改架构
    • 肯定:表架构很窄,有额外的行用于新设置
    • 否定:每个设置都有相同的默认值(null/empty?)
    • 否定:所有内容都必须存储为字符串(即 nvarchar)
    • 否定:在处理代码中的设置时,您必须知道设置是什么类型并进行转换

    单行选项是迄今为止最容易使用的选项。这是因为您可以将每个设置以正确的类型存储在数据库中,而不必在代码中存储设置的类型以及它们的查找键。

    使用这种方法时我担心的一件事是“特殊”单行设置表中有多行。我通过(在 SQL Server 中)克服了这个问题:

    • 添加默认值为 0 的新位列
    • 创建检查约束以确保该列的值为 0
    • 在位列上创建唯一约束

    这意味着表中只能存在一行,因为位列必须具有值 0,但由于唯一约束,只能存在具有该值的一行。

    【讨论】:

    • 我们在 LOB 应用程序中执行单行操作。这些值都是正确的类型,这使得在应用程序中使用它们更加简单。我们的模式与应用程序一起进行版本控制,因此对配置设置的更改就像任何应用程序修订一样得到管理。
    • 单行正数:可以在某些列上定义FK!
    • 您始终可以使用类型标识符进行键/值对来确定哪个列具有其值类型中的值。这为您提供了两全其美的优势,您可以在需要时使用存储过程来获取价值。
    • 在实施单行解决方案后,真正毁掉你一天的事情是,当你后来的任务是“让我们同时跟踪每个值的最后一次更改以及谁更改了它...... 。”
    • 单行解决方案的另一个优点,我在一个案例中发现:我有一个为一个客户端构建的应用程序,带有一个用于“设置”的单行表。后来我得到了另外两个客户,他们想要使用相同的应用程序,但想要不同的设置:我所要做的就是在表中添加一个“client_id”PK,以便为每个客户维护一组单独的设置。 (此时您会意识到这些“设置”实际上只是您尚未建模的更高级别实体的属性。)
    【解决方案2】:

    您应该创建一个包含信息类型和信息值列的表(至少)。这样您就不必在每次添加新信息时都创建新列。

    【讨论】:

    • 简单整洁。只需从那里使用键值对列表即可。您可能需要考虑一下默认值,具体取决于使用的上下文...
    • 为什么创建新列有问题?我知道在某些情况下,由于更新 SQL 模式的政治问题,开发人员必须避免这种情况,但问题中没有提到这一点。
    【解决方案3】:

    单行就可以了;它甚至会有强类型:

    show_borders    bit
    admin_name      varchar(50)
    max_users       int
    

    一个缺点是它需要更改架构 (alter table) 才能添加新设置。一种替代方法是规范化,最终得到如下表格:

    pref_name       varchar(50) primary key
    pref_value      varchar(50) 
    

    这具有弱类型(所有内容都是 varchar),但添加新设置只是添加一行,您只需使用数据库写入访问权限即可。

    【讨论】:

      【解决方案4】:

      就个人而言,如果可行,我会将其存储在一行中。将其存储在 SQL 表中是否矫枉过正?可能,但这样做并没有真正的危害。

      【讨论】:

        【解决方案5】:

        如您所料,除了最简单的情况外,将所有配置参数放在一行中会有很多缺点。这是个坏主意……

        在 XML 中存储配置和/或用户偏好类型信息的便捷方式。许多 DBMS 支持 XML 数据类型。 XML 语法允许您随着配置的发展而扩展描述配置的“语言”和结构。 XML 的一个优点是它对层次结构的隐式支持,例如,允许存储配置参数的小列表,而不必使用编号后缀命名它们。 XML 格式的一个可能缺点是搜索和一般修改这些数据不像其他方法那样直接(没有复杂,但没有那么简单/自然)

        如果您想更接近关系模型Entity-Attribute-Value model 可能是您需要的,其中各个值存储在通常如下所示的表中:

        EntityId     (foreign key to the "owner" of this attribute)
        AttributeId  (foreign key to the "metadata" table where the attribute is defined)
        StringValue  (it is often convenient to have different columns of different types
        IntValue      allowing to store the various attributes in a format that befits 
                      them)
        

        其中 AttributeId 是表的外键,其中定义了每个可能的属性(在您的情况下为“配置参数”),例如

        AttributeId  (Primary Key)
        Name
        AttributeType     (some code  S = string, I = Int etc.)
        Required          (some boolean indicating that this is required)
        Some_other_fields   (for example to define in which order these attributes get displayed etc...)
        

        最后,EntityId 允许您识别“拥有”这些不同属性的某个实体。在您的情况下,它可能是一个 UserId,如果您只有一个配置要管理,它甚至可能只是隐含的。

        除了允许可能的配置参数列表随着应用程序的发展而增长之外,EAV 模型将“元数据”,即与属性本身相关的数据,放在数据表中,因此避免了列的所有硬编码配置参数存储在一行中时常见的名称。

        【讨论】:

        • 对于配置表的大多数用途来说,这听起来有点矫枉过正。
        • 我认为这种方法背后的总体思路很棒。但为什么是 XML?只需选择一种简单的数据交换格式,例如 JSON 或 YAML,您就可以从其他两种变体中获得优势。
        • EAV 是关系性的,但未标准化。它肯定有一些用例(例如 ORM 系统似乎很喜欢它们),但元数据在 EAV 数据库中的论点并不是使用它的令人信服的理由。无论如何,所有 RDBMS 都在系统表中包含您可以发现的元数据,因此单行表在数据库中也有元数据。硬编码的列名也不是问题。如果您使用实体和属性的键,那么您在其他地方有一个硬编码的查找表来定义它们(或者更糟糕的是它在您的表示层中)。
        【解决方案6】:

        在规范化方法中添加新配置参数时,您当然不必更改架构,但您仍可能更改代码以处理新值。

        在您的部署中添加“更改表”似乎并不是为了单行方法的简单性和类型安全性而做出的巨大权衡。

        【讨论】:

          【解决方案7】:

          键值对类似于可以存储配置设置的 .Net App.Config。

          所以当你想检索你可以做的值时:

          SELECT value FROM configurationTable
          WHERE ApplicationGroup = 'myappgroup'
          AND keyDescription = 'myKey';
          

          【讨论】:

            【解决方案8】:

            执行此操作的常用方法是使用与属性文件类似的“属性”表。在这里,您可以存储您所有的应用程序常量,或者您只需要拥有的不太固定的东西。

            然后,您可以根据需要从该表中获取信息。同样,当您发现要保存一些其他设置时,您可以将其添加进去。这是一个示例:

            property_entry_table

            [id, scope, refId, propertyName, propertyValue, propertyType] 
            1, 0, 1, "COMPANY_INFO", "Acme Tools", "ADMIN"  
            2, 0, 1, "SHIPPING_ID", "12333484", "ADMIN"  
            3, 0, 1, "PAYPAL_KEY", "2143123412341", "ADMIN"   
            4, 0, 1, "PAYPAL_KEY", "123412341234123", "ADMIN"  
            5, 0, 1, "NOTIF_PREF", "ON", "ADMIN"  
            6, 0, 2, "NOTIF_PREF", "OFF", "ADMIN"   
            

            这样您可以存储您拥有的数据,以及您明年将拥有但还不知道的数据:)。

            在此示例中,您的范围和 refId 可用于后端的任何内容。因此,如果 propertyType "ADMIN" 的范围为 0 refId 2,您就知道它是什么偏好。

            当有一天您还需要在此处存储非管理员信息时,属性类型就派上用场了。

            请注意,您不应以这种方式存储购物车数据,也不要为此进行查找。但是,如果数据是系统特定的,那么你当然可以使用这种方法。

            例如:如果你想存储你的DATABASE_VERSION,你会使用这样的表。这样,当您需要升级应用程序时,您可以查看属性表以查看客户端的软件版本。

            关键是您不想将它用于与购物车有关的东西。将业务逻辑保存在定义明确的关系表中。属性表仅用于系统信息。

            【讨论】:

            • @finnw 我完全同意这种方法不应该用于查找,尤其是当有很多不同类型的查找时。也许我误解了这个问题。听起来他需要一个用于常量和系统属性的表。既然如此,为什么有 10 个不同的表?
            • 注意:他说的是“保存设置和配置”,而不是“我需要保存相关的购物车数据”
            • 我对此的反对意见是,您正在绕过 SQL 的键入和其他约束机制,以避免在添加新属性时更新 SQL 模式。正如您所说的“您明年将拥有但还不知道的数据”。是的,你明年会有新数据,但是在添加新数据时,有什么会阻止你创建新的(类型化的)SQL 列、CHECK 和可能的 FOREIGN KEY 约束?
            • 我的第一反应是简单地将这些数据添加到平面文件中。你是对的,这个使用表的过程确实会绕过 DBMS 的约束机制。但是,我会说,如果您太努力地遵循正确的数据库技术,那么您就错过了重点。查看第一个答案;投票最高的 SO:stackoverflow.com/questions/406760/…
            • 我会使用键值对,在启动时将其全部转储到字典中并进行排序。
            【解决方案9】:

            有一个作为 varchar 的键列和一个作为 JSON 的值列。 1 是数字,而 "1" 是字符串。 truefalse 都是布尔值。你也可以有对象。

            【讨论】:

              【解决方案10】:

              我不确定单行是否是配置的最佳实现。您最好为每个配置项设置一行,其中包含两列(configName、configValue),尽管这需要将所有值转换为字符串并返回。

              无论如何,使用单行进行全局配置并没有什么坏处。将其存储在数据库(全局变量)中的其他选项更糟糕。您可以通过插入第一个配置行来控制它,然后在表上禁用插入以防止出现多行。

              【讨论】:

                【解决方案11】:

                您可以通过为每种主要类型添加一列和一列告诉您数据在哪一列中来进行键/值对而无需转换。

                所以你的表格看起来像:

                id, column_num, property_name, intValue, floatValue, charValue, dateValue
                1, 1, weeks, 51, , ,
                2, 2, pi, , 3.14159, , 
                3, 4, FiscYearEnd, , , , 1/31/2015
                4, 3, CompanyName, , , ACME, 
                

                它使用了更多的空间,但您最多使用了几十个属性。您可以使用 column_num 值之外的 case 语句来拉取/加入正确的字段。

                【讨论】:

                  【解决方案12】:

                  对不起,我来了,多年以后。但无论如何,我所做的事情是简单而有效的。我只是创建了一个包含三 () 列的表:

                  ID - 整数 (11)

                  名称 - varchar (64)

                  值 - 文本

                  在创建新配置列、更新或读取之前我所做的是序列化“值”!这样我就可以确定类型(嗯,php是:))

                  例如:

                  b:0;适用于 BOOLEAN (false)

                  b:1;适用于 BOOLEAN (true)

                  i:1988;适用于NT

                  s:5:"卡德尔";适用于 5 个字符长度的 STRING

                  我希望这会有所帮助:)

                  【讨论】:

                  • 为什么不为类型创建一个新列? i:1988 似乎您正试图将两条信息折叠成一列。
                  • @maksymiuk 很简单,因为一旦反序列化,您将获得确切的类型,而不是在(if 或 switch)...等之后使用循环
                  • 不需要任何循环或开关或任何东西,它实际上会删除必须从每一行解析信息的步骤,而如果你有一个额外的类型列,则类型信息是已经可供组件提取信息,而无需执行任何其他步骤,而不仅仅是初始查询
                  • 您的意思是对整数执行echo (int) $var 之类的操作,对其他类型执行其他操作?
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-11-21
                  • 2019-05-25
                  • 2021-08-15
                  相关资源
                  最近更新 更多