在SQL Server数据库中使用单行配置表,是一个不好的想法吗?

166

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

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

那么,应该采用什么方法来存储这些信息呢?

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

12个回答

212

我过去有两种方法来处理这个问题 - 单行表格和键值对表格 - 每种方法都有其优缺点。

单行表格

  • 优点:值以正确的类型存储
  • 优点:在代码中更易于处理(由于上述原因)
  • 优点:可以为每个设置单独提供默认值
  • 缺点:需要模式更改才能添加新设置
  • 缺点:如果有很多设置,表格可能变得非常宽

键/值对

  • 优点:添加新设置不需要进行模式更改
  • 优点:表格模式较窄,额外的行用于新设置
  • 缺点:每个设置都具有相同的默认值(null/empty?)
  • 缺点:所有内容都必须存储为字符串(即nvarchar)
  • 缺点:在处理代码中的设置时,您必须知道设置的类型并将其转换

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

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

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

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


6
在我们的LOB应用程序中,我们使用单行方式。这些值都是正确类型的,这使得在应用程序中使用它们变得更加简单。我们的架构会随着应用程序一起进行版本管理,因此配置设置的更改就像任何应用程序修订一样进行管理。 - DaveE
21
单行正向索引:可以在某些列上定义外键! - wqw
10
您可以始终使用类型标识符创建键/值对来确定哪个列具有其值类型中的值。这样做可以兼顾两者,并且在需要时可以使用存储过程获取该值,但不会更改原意。 - Middletone
27
实施单行解决方案后,可能会真正毁掉你的一天的一件事情是当你被要求“让我们也跟踪每个值上次更改的时间和谁更改了它…” - Dave Mateer
11
单行解决方案的另一个优点是:我曾为一个客户构建了一个应用程序,其中包括一个称为“设置”的单行表格。后来我又得到了另外两个客户想要使用同一应用程序,但需要不同的设置。只需在表格中添加一个“client_id”主键即可为每个客户维护单独的设置。(当您意识到这些“设置”实际上只是您尚未建模的更高级实体的属性时,问题就解决了。) - Jeffrey Kemp
显示剩余14条评论

11

你应该创建一个表格,其中包含信息类型和信息值(至少)。这样一来,每当添加新信息时,就可以避免创建新列。


1
简单而整洁。只需使用键值对列表即可开始工作。您可能需要考虑一下默认值,这取决于使用的上下文... - Paul Kohler
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类型),但是只需要添加一行新设置,就可以通过数据库写入访问来实现。


5

就我个人而言,如果单行存储可行的话,我会选择这样做。把它存储在 SQL 表中可能有点浪费,但实际上这样做也没有什么大碍。


4
正如您所猜测的那样,除了最简单的情况外,将所有配置参数放在单个行中有许多缺点。这是一个不好的想法...
一种方便存储配置和/或用户首选项类型信息的方法是使用XML。许多DBMS支持XML数据类型。XML语法允许您扩展描述配置的“语言”和结构,随着此配置的演变而发展。 XML的一个优点是其对分层结构的隐式支持,例如允许存储小型配置参数列表,而无需使用带有编号后缀的名称来命名这些参数。 XML格式的可能缺点是搜索和通常修改此数据不像其他方法那样直截了当(没有什么复杂的,但不像其他方法那样简单/自然)。
如果您想更接近关系模型,则实体-属性-值模型可能是您需要的,其中各个值存储在通常看起来像表格的表中:
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模型还将“元数据”即与属性本身相关的数据放置在数据表中,从而避免了当配置参数存储在单行中时常见的所有硬编码列名。

3
对于大多数配置表的用途来说,这似乎有些过度设计。 - JerryOL
我认为这种方法背后的总体思路很好。但是为什么要选择XML呢?只需选择一个简单的数据交换格式,如JSON或YAML,您就可以从其他两种变体中获得优势。 - schlamar
1
EAV是关系型的,但它并没有被规范化。当然有它的使用场景(例如ORM系统似乎很喜欢它们),但是认为元数据在数据库中对于EAV来说是一个令人信服的理由并不足以使用它。所有RDBMS都包含系统表中的元数据,因此单行表也在数据库中具有元数据。硬编码列名也不是问题。如果您为实体和属性使用键,则必须在其他地方定义它们的硬编码查找表(或更糟糕的是在您的表示层)。 - Davos

3

在规范化方法中添加新的配置参数时,您无需更改模式,但可能仍需更改代码以处理新值。

将“alter table”添加到部署中似乎并不像使用单行方法那样简单且类型安全,但这并不是一个很大的权衡。


2

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

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

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

2

您可以通过为每个主要类型添加一列并添加一列告诉您数据所在的列来实现不进行转换的键/值对。

因此,您的表格可能如下所示:

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, 

它会占用更多的空间,但最多只使用几十个属性。您可以使用 case 语句根据 column_num 值提取 / 连接正确的字段。

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
我的第一反应是将这些数据添加到一个平面文件中。你说得对,使用表格来代替确实可以规避DBMS的约束机制。然而,我认为如果你过于努力地遵循正确的数据库技术,那么你就会错过重点。看看第一个答案;在SO上投票最高的:https://dev59.com/qnRC5IYBdhLWcg3wG9Rb - Stephano
2
我会选择键值对,将其全部转储到字典中,在启动时进行排序。 - Paul Creasey
@Stephano,但是这个问题被投票赞同很多次是因为人们不同意还是同意呢?j/k :-) - David Murdoch
显示剩余2条评论

1
将关键列设置为varchar,值列设置为JSON。 1 是数字,而"1"是字符串。 truefalse都是布尔值。您还可以拥有对象。

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