在SQL Server数据库中使用单行配置表。馊主意?


145

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

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

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

注意:我的DBMS是SQL Server 2008,并且编程层是使用ASP.NET(在C#中)实现的。

Answers:


189

过去,我已经完成了这两种方式-单行表和键/值对表-每种方法都有其积极性和消极性。

单排

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

键/值对

  • 肯定:添加新设置不需要更改架构
  • 正:表架构狭窄,多余的行用于新设置
  • 负数:每个设置都有相同的默认值(是否为空/空?)
  • 负数:所有内容都必须存储为字符串(即nvarchar)
  • 否定的:处理代码中的设置时,您必须知道设置是什么类型并将其强制转换

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

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

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

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


5
我们在LOB应用程序中执行单行操作。这些值都是正确的类型,这使得在应用程序中使用它们变得更加简单。我们的架构与应用程序一起进行了版本控制,因此可以像管理任何应用程序修订版一样管理对配置设置的更改。
DaveE'2

17
单行正:可以在某些列上定义FK!
2010年

8
您始终可以使用具有类型标识符的键/值对来确定哪一列的值类型具有该值。这为您提供了两全其美的优势,您可以在需要时使用存储的proc来获取值。
Middletone 2010年

19
实施单行解决方案后,可能会真正毁掉您的一天的事情是,您后来承担的任务是“让我们跟踪每个值的上次更改时间以及谁更改了该值……。”
Dave Mateer

6
我在一种情况下发现的单行解决方案的另一个优点是:我有一个为一个客户端构建的应用程序,其中有一个用于“设置”的单行表。后来,我有两个其他的客户端想要使用相同的应用程序,但是想要不同的设置:我要做的就是向表中添加一个“ client_id” PK,以便为每个客户端维护一套单独的设置。(这是当您意识到这些“设置”实际上只是您尚未建模的高级实体的属性时。)
Jeffrey Kemp

10

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


1
简单而整洁。只需从那里处理键值对列表即可。您可能需要考虑默认值,具体取决于使用的上下文...
Paul Kohler 2010年

4
为什么创建新列是一个问题?我知道在某些情况下,由于更新SQL模式的政治问题,开发人员必须避免使用它,但是问题中没有提及。
finnw

6

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

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表中是否矫kill过正?可能,但是这样做并没有真正的危害。


4

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

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

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

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是表的外键,其中定义了每个可能的Attribute(在您的情况下为“配置参数”),

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模型还将“元数据”(即与属性本身有关的数据)放置在数据表中,从而避免了通常常见的列名的所有硬编码。当配置参数存储在一行中时。


3
对于配置表的大多数使用来说,这听起来有些过分。
JerryOL 2010年

我认为这种方法背后的总体思路很棒。但是为什么要使用XML?只需选择一种简单的数据交换格式(如JSON或YAML),您就可以从其他两个版本中受益。
schlamar

1
EAV是关系型的,但未标准化。当然有一些用例(例如,ORM系统似乎喜欢它们),但是关于元数据位于EAV数据库中的说法并不是令人信服的理由。无论如何,所有RDBMS都会在系统表中包含元数据,因此单行表在数据库中也具有元数据。硬编码的列名也不是问题。如果对实体和属性使用键,那么您将在其他定义它们的地方有一个硬编码的查找表(或者更糟的是在您的表示层中)。
达沃斯

3

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

为单行方法的简单性和类型安全性,在部署中添加“变更表”似乎不是一个很大的折衷。


2

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

因此,当您想要检索值时,可以执行以下操作:

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

1

常用的方法是在属性文件旁添加一个“属性”表。在这里,您可以存储所有应用程序常量,也可以存储不需要的常量。

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

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个不同的表?
Stephano

注意:他说的是“保存设置和配置”,而不是“我需要保存相关的购物车数据”
Stephano 2010年

我对此的反对意见是,您在添加新属性时会绕过SQL的类型和其他约束机制,以避免更新SQL模式。正如您所说的那样,“您明年将拥有的数据还不知道。” 是的,您明年将有新数据,但是在添加新数据(类型化的)SQL列,CHECK以及可能的FOREIGN KEY约束时,该如何阻止它呢?
finnw

我的第一个直觉是将这些数据简单地添加到平面文件中。没错,使用表的过程实际上将绕过DBMS的约束机制。但是,我想说的是,如果您太努力地遵循适当的数据库技术,那么您将失去重点。查看第一个答案;最高表决SO: stackoverflow.com/questions/406760/...
Stephano

2
我会去键值对,在启动时将其全部转储到字典中,然后进行排序。
Paul Creasey 2010年

0

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

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


0

通过为每种主要类型添加一列并告诉您数据在哪一列中,您可以在不进行转换的情况下进行键/值对。

因此,您的表将类似于:

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语句来拉/联接右侧字段。


0

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

ID-整数(11)

名称-varchar(64)

值-文字

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

例如:

b:0; 用于B OOLEAN(false

b:1; 适用于B OOLEAN(true

i:1988; 是 NT

s:5:“ Kader”; 适用于长度为5个字符的S TRING

我希望这有帮助 :)


1
为什么不只为类型创建一个新列?i:1988似乎您正在尝试将两段信息折叠到一个列中。
maksymiuk '16

@maksymiuk立即,因为一旦取消序列化,您将获得准确的类型,而不是在(if或switch)之后使用循环...等等
Kader Bouyakoub

不需要任何循环,切换或任何操作,它实际上消除了必须从每一行中解析信息的步骤,但是,如果您有一个用于类型的额外列,则类型信息已经可供提取信息的组件使用。除了最初的查询外,无需执行其他任何步骤
maksymiuk

您是说echo (int) $var对整数进行运算,对其他类型进行运算?
卡德·布亚库布

0

将键列作为varchar,将值列作为JSON。1是数字而"1"字符串。true并且false都是布尔值。您也可以有对象。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.