更多列,或一列中的多个值 - SQL 数据库?

Posted

技术标签:

【中文标题】更多列,或一列中的多个值 - SQL 数据库?【英文标题】:More columns, or more values into one column - SQL Database? 【发布时间】:2020-11-24 11:36:31 【问题描述】:

我们有一个包含文章的数据库,从列大小的角度来看,IMO 可以改进(我们有 170 多个列) 有很多列,我们只存储一些布尔值(1 或 0)。 与其单独使用 COLUMN X、Z、Y、N 之类的东西,不如将所有内容合并到一个名为“XZYN”的列中,其中每个数字代表 XZYN 的状态。

示例: 1000 意味着 X=true,ZYN=false。 当然,这种状态会从我们的代码中解释出来。

这是个好主意吗?

【问题讨论】:

听起来像是没有任何特定目标的过早优化。但是你不尝试就无法知道。对您提议的更改进行良好的测试测试可能有助于您了解在同一列中存储多个值的问题。 【参考方案1】:

从存储的角度来看,将数据存储在单列中会“花费”更多。 bit 列(我假设您说“bool”时是指bit)的大小非常小,要存储像1000 这样的值,您可能需要intint 的大小为 4 字节,而 bit 的大小(不出所料)只有 1 位,并且多列被分组为 8 组。

SQL Server 数据库引擎优化了位列的存储。如果表中有 8 个或更少的位列,则这些列存储为 1 个字节。如果有 9 到 16 位列,则这些列存储为 2 个字节,依此类推。

这意味着,如果您有 100 个 bit 列,要将其存储为串联字符串,您将需要 10 个 int 列或 6 个 bigint 列,分别占用 40 或 48 个字节。对于 100 个 bit 列,您将只使用 13 个字节(100 / 8 = 12.5 = 13 个 1 字节组)。

将数据存储在单个列中也不是 SARGable,而且搜索它也并不简单。您不能划分列或获取余数,因为其他“列”会影响除法和余数。相反,在添加任何所需的前导零后,您必须使用 SUBSTRING 之类的东西来获得相关字符,这在我看来是相当“丑陋”的。

然而,另一种解决方案(尽管我也不推荐)是使用按位逻辑。这是您为每个位值分配不同倍数然后聚合它们的地方,然后使用按位运算符提取“列”的值。例如,假设您有 8 列,A-H。您可以为这些中的每一个分配一个 8 位二进制值的数字:

a = 1 = 2^0
b = 2 = 2^1
c = 4 = 2^2
d = 8 = 2^3
e = 16 = 2^4
f = 32 = 2^5
g = 64 = 2^6
h = 128 = 2^7

因此,如果一行想要 a、c、f 和 g 的值为真,则存储的值为 1+4+32+64 = 101。然后您可以检查该值是否为真,使用按位 (&) 运算符:

SELECT CASE V.I & 1 WHEN 0 THEN 0 ELSE 1 END AS A,
       CASE V.I & 2 WHEN 0 THEN 0 ELSE 1 END AS B,
       CASE V.I & 4 WHEN 0 THEN 0 ELSE 1 END AS C,
       CASE V.I & 8 WHEN 0 THEN 0 ELSE 1 END AS D,
       CASE V.I & 16 WHEN 0 THEN 0 ELSE 1 END AS E,
       CASE V.I & 32 WHEN 0 THEN 0 ELSE 1 END AS F,
       CASE V.I & 64 WHEN 0 THEN 0 ELSE 1 END AS G,
       CASE V.I & 128 WHEN 0 THEN 0 ELSE 1 END AS H
FROM (VALUES(101))V(I);

然而,这又不是 SARGable,但至少使用的存储空间比存储 10100110 之类的值要少得多。但是,如果您永远不会在WHERE 中的列上进行过滤,那么这可能值得探索,但如果您有机会,那就不要(尽管bit不需要过滤的按位列可能不会“坏”以减少列数)。

我的诚实意见,坚持原样。如果表格确实“太宽”,请考虑将 bit 列组分开并将它们放入单独的表格中,与您当前的表格具有 1 对 1 的关系。

【讨论】:

【参考方案2】:

如果没有代码来解释它,只看数据库的人会知道这些值的含义吗?

这也会使添加或删除任何这些标志变得非常困难 - 特别是如果您试图删除位于连接字符串“中间”的内容。

如果确实很混乱,另一种方法可能是将标志提取到另一个与当前主记录具有一对一关系的表中。但是,我不太了解您的数据模型,无法真正知道这是否可行。

您是在尝试解决与性能或可读性有关的问题吗?

【讨论】:

【参考方案3】:

这是个好主意吗?应该不会吧。

您正试图过度优化数据库。额外的费用是将任何结果列解析为您真正需要的内容。这种解析增加了开销。更重要的是,它使数据库更难使用。

值得指出的是,位打包(我将称之为您想要做的事情)确实有一些优势,主要是在节省空间方面。同样重要的是要记住,更少的空间意味着数据库更快。

如果您有 30 个这样的列,并且它们当前存储为整数,那么这就是值的 120 个字节加上相应的NULL 位的额外 30 位。您可以将它们还原为 4 个字节和一个 NULL 位 - 节省大量资金。

但是,您可以将这些存储为tinyint/char(1) 甚至bit。这会将大小减少到 30 字节甚至 1 字节——尽管您将让 NULL 位占用 30 位空间。也就是说,你可以通过切换类型来获得基本相同的效果。

或者,您可以完全删除所有这些列,只使用另一个带有“属性”的表。然后,您可以使用名称(或参考表)为每个实体存储一行,并且当属性为真时。例如,而不是:

entityid   flag1   flag2   flag3
   1         1       0       1

你会有另一个桌子:

entityid     flag
   1        'flag1'  -- or a reference to "flag1"
   1        'flag2'

这种方法有几个优点:

添加新标志很容易。 您可以添加其他信息,例如设置标志的日期。 如果标志是稀疏的,它可能会使用更多、更少的空间。

【讨论】:

以上是关于更多列,或一列中的多个值 - SQL 数据库?的主要内容,如果未能解决你的问题,请参考以下文章

sql中如何使一列中的多个重复数据只显示一次, 求大神指导,使得图中的班简名重复的只显示一次。

sql中如何使一列中的多个重复数据只显示第一条

R:如何使用其他列中的数据在一列中创建多个新值并为每个新值重复行?

sql语句将Excel中的一列批量更新到sql server中的一列中?

SQL - 为给定列中的值获取另一列的值

Sql Server 主键 外键约束