为什么 varchar 数据类型允许 unicode 值?

Shi*_*iva 18 sql-server datatypes character-set encoding unicode

我有一个带有 varchar 列的表。它允许使用商标 (™)、版权 (©) 和其他 Unicode 字符,如下所示。

Create table VarcharUnicodeCheck
(
col1 varchar(100)
)

insert into VarcharUnicodeCheck (col1) values ('MyCompany')
insert into VarcharUnicodeCheck (col1) values ('MyCompany™')
insert into VarcharUnicodeCheck (col1) values ('MyCompany?')
insert into VarcharUnicodeCheck (col1) values ('MyCompanyï')
insert into VarcharUnicodeCheck (col1) values ('MyCompany')

select * from VarcharUnicodeCheck
Run Code Online (Sandbox Code Playgroud)

但是varchar定义说,它允许非 unicode 字符串数据。但 Trademark(™) 和 Registered(®) 符号是Unicode字符。定义是否与 varchar 数据类型的属性相矛盾?我阅读了几个链接,例如第一个第二个。但是我仍然不明白为什么当定义说它只允许非 unicode 字符串值时它允许 unicode 字符串。

sep*_*pic 14

但 Trademark(™) 和 Registered(®) 符号是 Unicode 字符。

你错在这里。您的字符串仅包含 ascii 字符。

这是一个简单的测试,显示您的字符都是 ascii(+ 一些扩展的 ascii,ASCII 代码介于 128 和 255 之间):

declare @VarcharUnicodeCheck table
(
col1 varchar(100)
)

insert into @VarcharUnicodeCheck (col1) values ('MyCompany')
insert into @VarcharUnicodeCheck (col1) values ('MyCompany™')
insert into @VarcharUnicodeCheck (col1) values ('MyCompany?')
insert into @VarcharUnicodeCheck (col1) values ('MyCompanyï')
insert into @VarcharUnicodeCheck (col1) values ('MyCompany')

select *,
        right(col1, 1)as last_char, 
        ascii(right(col1, 1)) as_last_char_ascii
from @VarcharUnicodeCheck;
Run Code Online (Sandbox Code Playgroud)

在这里您可以清楚地看到所有字符都是 1 字节编码的:

在此处输入图片说明

是的,它们不是纯 ASCII 字符,而是扩展 ASCII 字符

在这里,我向您展示真正的 Unicode 字符Trademark(™)及其代码和二进制表示:

declare @t table (uni_ch nchar(1), ascii_ch char(1));
insert into @t values (N'™', '™');

select unicode(uni_ch) as [unicode of ™], 
       ascii(ascii_ch) [ascii of ™], 
       cast(uni_ch as varbinary(10)) as [uni_ch as varbinary], 
       cast(ascii_ch as varbinary(10)) as [ascii_ch as varbinary]
from @t;
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

最后,您可以看到 Trademark(™) Unicode 字符具有 8482 代码而不是 153:

select nchar(8482), nchar(153)
Run Code Online (Sandbox Code Playgroud)

  • “扩展 ASCII”是一个非常模棱两可的术语。查看实际使用的 8 位编码会更有帮助(它是否基于语言环境/排序规则设置?)。我猜 [Windows 代码页 1252](https://en.wikipedia.org/wiki/Windows-1252),它确实将 ™ 编码为字符 153。 (18认同)
  • 应注意 127 以上的 8 位字符:127 以上的每个代码代表的内容可以并且将会根据使用的编码而变化,这将根据使用的排序规则而变化。在代码页 1252 中,unicode 8482 被映射到 153。在代码页 850 中,该位置被 214 (`Ö`) 占用,而在 ISO-8859-1(有时称为 Latin1)中,它是一个没有可打印表示的控制代码。除非你*知道*你将*总是*使用相同的代码页,否则坚持使用ANSI字符(127个或更少)或使用Unicode类型更安全。代码页 1252 在 SQL Server 中最常见,但远非普遍存在。 (7认同)
  • @sepupic 我认为您需要阅读更多有关代码点和编码之间差异的信息。[维基百科](https://en.wikipedia.org/wiki/Unicode#Mapping_and_encodings) 可能会有所帮助。“编码将 Unicode *代码点*的范围(可能是其子集)映射到某个固定大小范围内的值序列,称为 *代码值*。” 8482 是 ™ 的代码点,它可以在 Windows-1252 中编码为 \x99 (153),在 MacRoman 中编码为 \xAA,在 UTF-8 中编码为 \xE2\x84\xA2 等。 (2认同)

Dan*_*man 7

从评论中,我同意“扩展 ASCII”是一个非常糟糕的术语,它实际上意味着一个代码页,它映射 128-255 范围内的字符/代码点,超出了 ASCII 定义的标准 0-127 代码点范围。

SQL Server 通过排序规则支持许多代码页。只要底层排序规则支持该字符,非 ASCII 字符就可以存储在 varchar 中。

当 SQL Server 归类代码页为 1250 或更大时,“™”字符可以存储在 varchar/char 列中。下面的查询将列出这些:

SELECT COLLATIONPROPERTY(name, 'CodePage') AS code_page, name, description
FROM sys.fn_helpcollations()
WHERE COLLATIONPROPERTY(name, 'CodePage') >= 1250
ORDER BY name;
Run Code Online (Sandbox Code Playgroud)

但只有其中的一个子集也支持 '©' 字符,因此列排序规则需要是以下之一才能支持两者:

SELECT COLLATIONPROPERTY(name, 'CodePage') AS code_page, name, description
FROM sys.fn_helpcollations()
WHERE COLLATIONPROPERTY(name, 'CodePage') IN(
    1250
    ,1251
    ,1252
    ,1253
    ,1254
    ,1255
    ,1256
    ,1257
    ,1258
)
ORDER BY name;
Run Code Online (Sandbox Code Playgroud)


Sol*_*zky 5

但是 varchar 的定义说,它允许 non-unicode string data。但 Trademark(™) 和 Registered(®) 符号是 Unicode字符。定义是否与 varchar 数据类型的属性相矛盾?

虽然其他答案并没有错,但我认为指出基本术语的混淆会有所帮助。我在上面引用的问题中强调了两个词,作为这种混淆的一个例子。当 SQL Server 文档谈到 Unicode 和非 Unicode数据时,他们并不是在谈论字符。他们说的是代表某些字符的字节序列。Unicode的类型(之间的主要差别NCHARNVARCHARXML,和弃用/恶NTEXT)和非Unicode类型(CHARVARCHAR以及弃用/恶TEXT)是什么类型的字节序列的他们可以存储。

非 Unicode 类型存储几种 8 位编码之一,而 Unicode 类型存储单个 16 位 Unicode 编码:UTF-16 Little Endian。正如其他答案所提到的,哪些字符可以存储在 8 位/非 Unicode 编码中取决于代码页,代码页由排序规则确定。虽然其他人已经注意到“字符”的字节值可以在它所在的代码页之间变化,但在处理几个 EBCDIC 代码页之一(Windows 的变体)时,字节值甚至可以在同一代码页内变化。 1252),仅在较旧的版本中找到,不应真正使用 SQL Server 排序规则(即名称以 开头的排序规则SQL_)。

因此,定义是准确的:您可以设法存储在非 Unicode 类型中的任何字符始终为 8 位(即使它们组合使用两个 8 位值作为单个“字符”,这就是 Double-字节字符集/DBCS 代码页允许)。并且 Unicode 数据类型始终是 16 位,即使它们有时将两个 16 位值组合用作单个“字符”(即代理对,它依次代表一个补充字符)。

并且,由于 SQL Server自 SQL Server 2019 起支持 UTF-8 编码VARCHARCHAR数据类型,

VARCHAR不能再被称为“非 Unicode”。因此,从 2018 年 9 月 SQL Server 2019 的第一个公开测试版开始,我们应该将其VARCHAR称为“8 位数据类型”,即使在使用 SQL Server 2019 之前的版本时也是如此。该术语适用于所有 4 种类型可以与VARCHAR以下一起使用的编码:

  1. 扩展 ASCII
  2. 双字节字符集 (DBCS)
  3. EBCDIC
  4. UTF-8 (Unicode)

只有TEXT数据类型(从 SQL Server 2005 开始不推荐使用,所以不要使用它)是“非 Unicode”,但这只是一个技术问题,将其称为“8 位数据类型”是准确的。

NVARCHAR, NCHAR, 并且NTEXT可以称为“UTF-16”或“16 位数据类型”。我相信 Oracle 使用“仅限 Unicode”的术语NVARCHAR,但这并不能明确排除使用 UTF-8(也是 Unicode 编码)的可能性,因为它不起作用,所以最好坚持使用前两个选项。

有关新的 UTF-8 编码的详细信息,请参阅我的帖子:

SQL Server 2019 中的原生 UTF-8 支持:救世主还是假先知?

PS 我正在慢慢地更新 SQL Server 文档以反映这些更改。

PPS Microsoft 已经使用 UTF-8 信息更新了一些页面,包括问题中引用的char 和 varchar文档。它不再包含短语“非 Unicode”。但这只是一个仅供参考;它不会改变问题,因为这是关于包含被错误地认为是仅 Unicode 的字符的非 Unicode 编码。