SQL数据类型VARCHAR(1)有任何意义吗?

20

我最近在处理的数据库中遇到了很多VARCHAR(1)字段。 我翻了个白眼:显然设计者不知道该怎么做。 但也许是我需要学习一些东西。 有没有任何可以想象的理由使用VARCHAR(1)数据类型而不是CHAR(1)? 我认为RDBMS会自动将一个转换为另一个。

这个数据库是MS SQL 2K5,但从过去的Access发展而来。

3个回答

17

确实有意义。

  • 在语言中定义更容易。使用varchar定义1-8000比使用需要2+或3+到8000的方式更为一致和简单。

  • VARCHAR(1)中的VARying CHARacter方面正是如此。它可能不是最佳的存储方式,但传达了一个特定的含义,即数据要么是1个字符(教室代码),要么是空白(室外活动),而不是NULL(未知/尚未分类)。

存储在这里几乎起不到任何作用-查看CHAR(1)的数据库模式,您几乎会认为它必须始终具有1个字符值,例如信用卡必须有16位数字。然而,在某些数据中并非如此,它可以只有一个或可选地没有。

对于那些认为三态[1-char | 0-char | NULL]完全无用的人,使用VARCHAR(1)与CHAR(1)+ NULL组合也存在差异。它允许使用SQL语句进行:

select activity + '-' + classroom
from  ...

如果你使用char(1)+NULL,虽然可以传达相同的信息,但细微差别会使事情变得更加困难。


1
我可以看出来,实现中没有真正的理由禁止VARCHAR(1),但我想知道是否有任何设计上的好处。关于你的第二点,只是为了澄清,你是在指出VARCHAR(1)可以是空字符串,但CHAR(1)不行吗?我能看出来;选择VARCHAR(1)而不是CHAR(1)的一个晦涩的原因,但可能是有效的。 - user565869
•在该语言中定义更容易。 - 你需要详细说明吗? - Mitch Wheat
1
从Oracle的角度来看,Tom Kite不同意!我永远不会使用varchar(1)。对我来说,Char(1)意味着一个列将只包含一个字符。我会质疑一个具有varchar(1)或char(1)可为空的架构设计。 - Mitch Wheat
1
@Mitch - 对于每个专业人士,都会有其他人可能同意或不同意。没有个人恩怨,但这就像我并不完全同意你说的一切,仅仅因为你是微软MVP。在不同的主题下,还有其他微软MVP持有不同的观点。没有人总是正确的。 - RichardTheKiwi
1
假设“空白”不是指null,这个理由意味着使用零长度字符串,这是一个可疑的设计选择,也会违反最小惊讶原则。使用Coalesce与null和长度大于零的值更明确、更灵活,可以更好地解决这个问题,因为数据要么是1个字符(教室代码),要么为空(户外活动)。 - Thomas
显示剩余5条评论

11
据我所知,不行。
一个VARCHAR(1)需要3个字节的存储空间(存储大小为实际输入数据的长度+2字节。参考)。
一个CHAR(1)只需要1个字节。
从存储的角度来看:一个经验法则是,如果它小于或等于5个字符,请考虑使用固定长度的char列。
避免使用varchar(1)的原因(除了它们在设计上表现出糟糕的设计思路外,在我看来)是在使用Linq2SQL时:LINQ to SQL and varchar(1) fields

3
不是一个好的经验法则。使用固定长度的 char(4) 和变长长度的 varchar(4) 的问题在于 char(4) + '-x' 会产生不必要的空格。虽然可以修剪,但这会增加不必要的复杂性,而使用 varchar(4) 就能简单地解决这个问题。 - RichardTheKiwi
2
在我看来,这是正确的答案。Varchar(1) 违反了最小惊讶原则,没有任何收益,并且在存储额外位以指示实际使用的字符数时会产生一些成本(尽管很小)。我无法想象任何情况下它会是一个逻辑上正确的设计选择。 - Thomas
1
@MartinSmith - 然而,在软件工程的某些部分,特定类型的更改的概率并不相等,并且这些概率随时间变化。在一个 char(1) 列中删除 PK 对于该表来说不会很昂贵。这是因为它可以包含的唯一值的数量是有限的。然而,在具有 char(1) PK 的表中删除外键约束可能是昂贵的,如果我没有错的话,无论您使用 char 还是 varchar,都必须这样做。 - Thomas
2
@MartinSmith - 我承认在一组狭窄的情况下,将varchar(1)改为char(1)会在时间上产生有意义的差异,尽管这需要处理多个百万行。然而,绝大多数情况下,由于执行此类操作的频率很低,所以时间差别是无关紧要的。因为“有朝一日”您可能需要扩展它并且该操作会稍微快一些,所以使用varchar(1)的动机是值得怀疑的。表达意图的清晰度应该胜过过早优化。 - Thomas
1
@Thomas - 如果它可能变化,但目前仅限于1个字符,则在我看来,varchar(1)char(1)更清楚地表明了意图。 - Martin Smith
显示剩余11条评论

5
一个 varchar(1) 可以存储零长度字符串(“空”字符串),而一个 char(1) 不行,因为它会被填充成一个单独的空格。如果这个区别对你很重要,你可能会更喜欢使用 varchar。 除此之外,这种用法的一个案例可能是,如果设计者希望未来可能需要更多字符。 将固定长度数据类型从 char(1) 改为 char(2) 需要先更新所有表行并删除访问该列的任何索引或约束条件。 在生产环境中对大型表进行这些更改可能是一项非常耗时的操作,需要停机时间。 将列从 varchar(1) 更改为 varchar(2) 要简单得多,因为它只是一个元数据更改(参考该列的 FK 约束需要被删除和重新创建,但无需重新构建索引或更新数据页)。 此外,每行节省 2 字节的效果可能并不总是实现。如果行定义已经很长,这并不总是影响能适应数据页的行数。另一种情况是,如果在企业版中使用压缩功能,数据存储的方式与 Mitch 的答案中提到的完全不同。无论如何,varchar(1)char(1) 最终都会以相同的方式存储在短数据区域中。 @Thomas - 例如,尝试这个表定义。
CREATE TABLE T2
(
Code VARCHAR(1),
Foo datetime2,
Bar int,
Filler CHAR(4000),
PRIMARY KEY CLUSTERED (Code, Foo, Bar)
)

INSERT INTO T2
SELECT TOP 100000 'A', 
                  GETDATE(), 
                  ROW_NUMBER() OVER (ORDER BY (SELECT 0)),
                  NULL
FROM master..spt_values v1,  master..spt_values v2

CREATE NONCLUSTERED INDEX IX_T2_Foo ON T2(Foo) INCLUDE (Filler);
CREATE NONCLUSTERED INDEX IX_T2_Bar ON T2(Bar) INCLUDE (Filler);

对于 varchar 类型的列,将列定义从 varchar(1) 变为 varchar(2) 是很容易的。这只是一种元数据的更改。

ALTER TABLE T2 ALTER COLUMN Code VARCHAR(2) NOT NULL

如果将 char(1) 更改为 char(2),必须执行以下步骤:
  1. 删除表中的主键。这将把表转换为堆,这意味着所有非聚集索引都需要使用 RID 而不是聚集索引键进行更新。
  2. 更改列定义。这意味着表中的所有行都将更新,以便将 Code 现在存储为 char(2)
  3. 重新添加聚集主键约束。除了重建聚集索引本身之外,这还意味着所有非聚集索引需要再次使用 CI 键作为行指针进行更新,而不是使用 RID。

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