【问题标题】:Is There ANY Sense in SQL Data Type VARCHAR(1)?SQL 数据类型 VARCHAR(1) 有什么意义吗?
【发布时间】:2011-06-19 15:57:37
【问题描述】:

我在最近不得不使用的数据库中遇到了很多 VARCHAR(1) 字段。我翻了个白眼:显然设计师一点头绪都没有。但也许我是需要学习一些东西的人。是否有任何可以想象的理由使用 VARCHAR(1) 数据类型而不是 CHAR(1)?我认为 RDMS 会自动将一个转换为另一个。

数据库是 MS SQL 2K5,但在当时是从 Access 演变而来的。

【问题讨论】:

    标签: sql sql-server-2005 types


    【解决方案1】:

    是的,这是有道理的。

    • 更容易在语言中定义。将 varchar 定义为允许 1-8000 比说它需要 2+ 或 3+ 到 8000 更一致且更容易。

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

    存储在这方面的作用很小 - 查看 CHAR(1) 的数据库模式,您几乎会认为它必须始终具有 1 个字符值,例如信用卡必须有 16 位数字。对于某些可以为一个或可选为无的数据,情况并非如此。

    对于那些说三态 [ 1-char | 0 字符 | NULL ] 完全没用。它允许 SQL 语句,如:

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

    如果你使用 char(1)+NULL 会更困难,它可以传达相同的信息,但有细微的差别。

    【讨论】:

    • 我可以看到没有真正的理由来实现 prohibit VARCHAR(1),但我想问是否会有任何设计上的好处。关于您的第二点,为了澄清,您指出 VARCHAR(1) 可以是空字符串,但 CHAR(1) 不能?我理解了;一个模糊的理由选择 VARCHAR(1) 而不是 CHAR(1),但可以想象是一个有效的理由。
    • "•更容易在语言中定义。" - 你能详细说明一下吗?
    • 好吧,从 Oracle 的角度来看,Tom Kite 不同意!我永远不会使用 varchar(1)。 Char(1) 对我来说意味着一列将包含 1 并且恰好包含一个字符。我会质疑具有 varchar(1) OR char(1) 可为空的架构设计。
    • @Mitch - 对于每个专业人士,都有可能不同意或不同意的其他人。没什么私人的,但就像我不同意你所说的一切,只是因为你是 MS MVP。还有其他 MS MVP 在不同的主题上持有不同的观点。 Nobody 是正确的all 时间。
    • 数据为 1 个字符(课堂代码)或空白(课外活动)。假设“空白”不是指空值,这个理由意味着使用零长度字符串,这是它自己的可疑设计选择,也会违反最小惊讶规则。将 Coalesce 与 null 和长度大于零的值一起使用更加明确和灵活。
    【解决方案2】:

    AFAIK,不。

    aVARCHAR(1)需要3字节存储(存储大小为输入数据的实际长度+2字节。Ref

    CHAR(1) 需要 1 个字节。

    从存储角度来看:经验法则是,如果小于或等于 5 个字符,请考虑使用固定长度的 char 列。

    避免使用 varchar(1) 的一个原因(除了它们传达糟糕的设计推理,IMO)是在使用 Linq2SQL 时:LINQ to SQL and varchar(1) fields

    【讨论】:

    • 不是一个好的经验法则。固定长度 4 与 varchar(4) 的问题是 char(4) + '-x' 有不需要的空格。是的,您可以修剪,但这会增加复杂性,而不仅仅是使用 varchar(4)。
    • @Mitch - Linq2SQL 问题是bugbug 修复后会使该点无效,并且 extremely 已本地化为 VS2008。 social.msdn.microsoft.com/Forums/en-US/linqtosql/thread/…, social.msdn.microsoft.com/Forums/en/linqtosql/thread/…
    • IMO,这是正确的答案。 Varchar(1) 违反了最小惊讶规则,一无所获,并且在存储一个额外的位以指示实际使用的字符数时确实有成本(无论多么轻微)。我想不出任何情况下这将是一个合乎逻辑的设计选择。
    • @MartinSmith - 但是,在软件工程的某些部分中,特定类型更改的概率并不相等,并且这些概率随时间而变化。将 PK 放在 char(1) 的列中对该表来说并不昂贵。不可能是因为它可以包含的唯一值的数量是有限的。但是,使用char(1) PK 将外键约束删除到该表可能会很昂贵,如果我没记错的话,无论您使用的是char 还是varchar,都必须这样做。
    • @MartinSmith - 我承认,在少数情况下,更改 varchar(1) 而不是 char(1) 将在一段时间内完成,即使它需要数百万行。但是,在绝大多数情况下,时间差将是无关紧要的,因为执行此类操作的频率很低。使用varchar(1) 的动机是因为“总有一天”你可能不得不扩展它并且操作会稍微快一点是值得怀疑的。意图的明确性应该胜过过早的优化。
    【解决方案3】:

    varchar(1) 可以存储零长度(“空”)字符串。 char(1) 不能,因为它会被填充到一个空格中。如果这种区别对您很重要,您可能会喜欢varchar

    除此之外,如果设计人员希望考虑到将来可能需要更多字符的可能性,则这种情况的一个用例可能是。

    将固定长度数据类型从char(1) 更改为char(2) 意味着需要更新所有表行,并且首先删除访问该列的所有索引或约束。

    在生产中对大型表进行这些更改可能是一项极其耗时的操作,需要停机。

    将列从 varchar(1) 更改为 varchar(2) 要容易得多,因为它只是元数据更改(需要删除并重新创建引用该列的 FK 约束,但无需重建索引或更新数据页)。

    此外,每行节省 2 个字节可能并不总是会实现。如果行定义已经很长,这并不总是会影响数据页上可以容纳的行数。另一种情况是,如果使用 Enterprise Edition 中的压缩功能,存储数据的方式无论如何都与 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. 从桌子上放下 PK。这会将表转换为堆,这意味着所有非聚集索引都需要使用 RID 而不是聚集索引键进行更新。
    2. 更改列定义。这意味着表中的所有行都会更新,以便 Code 现在存储为 char(2)
    3. 添加回集群 PK 约束。除了重建 CI 本身,这意味着所有非聚集索引都需要使用 CI 键作为行指针而不是 RID 再次更新。

    【讨论】:

      猜你喜欢
      • 2010-12-15
      • 2015-07-21
      • 2011-12-30
      • 1970-01-01
      • 1970-01-01
      • 2016-04-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多