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


17

我有一个带有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

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


12
所有字符均为Unicode字符。
马丁·史密斯

Microsoft表示UTF-16 / UCS-2时经常使用UNICODE。因此,由于UNICODE是某种上下文,因此他们甚至可能不算UTF-8。
CodesInChaos

1
@CodesInChaos:我很难解析您的评论,但是我担心您将Unicode与各种UTF-n编码混淆了。
莫妮卡(Monica)与Lightness比赛'18

1
@马丁史密斯:如果所有字符都是Unicode字符,那么为什么Microsoft varchar定义说它允许非Unicode字符串数据?
湿婆

2
在VARCHAR字符编码不是Unicode,但所有字符的Unicode中存在
马丁·史密斯

Answers:


15

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

你错了 您的字符串仅包含ascii字符。

这是一个简单的测试,向您显示您的字符全部为ascii(+一些字符extended ascii在128至255之间的ascii代码):

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;

在这里,您可以清楚地看到所有字符都是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;

在此处输入图片说明

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

select nchar(8482), nchar(153)

1
但是您提到的文章中没有“ ASCII”字样,他们只在谈论unicode和非unicode字符,而您使用的Trademark(™)不是unicode。
Sepupic

16
“扩展ASCII”是一个非常模棱两可的术语。查看实际使用的8位编码会更有用(它基于语言环境/排序规则设置吗?)。我猜想Windows代码页1252确实确实将™编码为字符
153。– IMSoP

2
@sepupic我认为您需要阅读更多有关代码点和编码之间的区别的信息。维基百科可能会有所帮助。“编码将Unicode 代码点范围(可能是其子集)映射到某个固定大小范围内的值序列,称为代码值。” 8482是™的代码点,可以在Windows-1252中编码为\ x99(153),在MacRoman中编码为\ xAA,在UTF-8中编码为\ xE2 \ x84 \ xA2,等等
。– curiousdannii

7
应当注意127以上的8位字符:127以上的每个代码可以并且将根据使用的编码而改变,这取决于所使用的排序规则。在代码页1252中,Unicode 8482被映射到153。在代码页850中,该点由214(Ö)占据,而在ISO-8859-1(有时称为Latin1)中,它是没有可打印表示形式的控制代码。除非你知道总是使用相同的代码页是比较安全的坚持ANSI字符(127或更少),或使用Unicode类型。代码页1252在SQL Server中最常见,但并非无处不在。
David Spillett

4
@Shiva 每个软件开发人员绝对绝对肯定要了解Unicode和字符集。ASCII是许多编码的子集,并且几乎所有这些编码都包含非ASCII符号,并且同时不是Unicode。Unicode也有许多不同的编码(例如UTF-8,UTF-32等)。
jpmc26 '18

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;

但是其中只有一个子集也支持'©'字符,因此列排序规则将需要是以下一项以支持这两种:

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;

4

但是varchar的定义说,它允许非Unicode字符串数据。但是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 Server排序规则SQL_)。

因此,定义是正确的:您可以设法以非Unicode类型存储的任何字符始终都是8位(即使它们将两个8位值组合使用为单个“字符”,也就是Double-字节字符集/ DBCS代码页允许)。而且Unicode数据类型始终为16位,即使它们有时有时结合使用两个16位值作为单个“字符”(即,代表替代字符的替代对)。

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

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

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

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

NVARCHARNCHARNTEXT可以称为“ 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文档。它不再包含短语“ non-Unicode”。但这只是一个仅供参考。它不会改变问题,因为这是关于非Unicode编码的,其中包含误认为仅Unicode的字符。


3

这个问题包含一个关于Unicode是什么的主要误解。Unicode字符集及其编码(例如UTF-8和UTF-16)是计算机中表示文本的多种方式之一,其目的是取代所有其他字符集和编码。如果“非Unicode数据”的意思是“ Unicode中不存在的字符”,那么我在此答案中使用的所有文本都不能以该类型存储,因为日常英语中使用的所有拉丁字母和常用标点符号都是包含在Unicode中。

文本表示可以大致分为两部分:一个字符集,将不同的字符(字母,数字,符号等)映射到参考图表上的数字;以及将这些数字表示为位模式(在磁盘上,通过网络连接等)的编码。在这里,我们最关心的是第一部分:图表上针对特定字符集列出了哪些字符。

由于Unicode旨在为世界上的每个字符提供数字(称为“代码点”),因此像Wikipedia这样的引用通常会将字符的Unicode位置引用为标准参考信息。但是,这并不意味着其他字符集也没有针对该相同字符的映射。

仍在使用的最古老,最简单的字符集(和编码)是ASCII,它具有128个不同字符(0到127)的映射,因为它使用7位来编码每个字符。由于这不包括许多重音符号和通用符号,因此以后的编码使用8位,并映射相同的前128个字符,并通过填充位置128到255来添加到字符集中。其中值得注意的是标准ISO 8859-1ISO 8859- 15和Microsoft特定的Windows代码页1252

所以,回来MS SQL服务器:一个“Unicode字符串”,存储在一个ncharnvarcharntext列,可以代表所有的Unicode字符集映射的角色,因为它采用的是Unicode编码来存储数据。“非Unicode字符串”,存储在一个charvarchartext列中,仅可以表示在映射到的人物一些其他编码。您可以存储在非Unicode列中的所有内容也可以存储在Unicode列中,反之亦然。

要确切地知道可以存储哪些字符,您需要了解所使用的“排序规则”,该排序规则规定了Microsoft所谓的“代码页”,如本Microsoft参考页上所述。在您的情况下,很可能您使用的是我之前提到的非常常见的代码页1252。

您提到的字符同时存在于Unicode和代码页1252中:

  • 商标(™)出现在Unicode中的位置8482和CP1252中的位置153
  • 碰巧,Registered(®)出现在Unicode和CP1252中的位置174

3
“ Unicode是编码文本以供计算机使用的多种方式之一” –这是不正确的。Unicode只是字符和符号的集合,其中每个字符都有自己的唯一代码点,该代码点只是一个数字。编码的工作是将那些代码点与字节序列进行匹配。UTF-8和UTF-16是编码,而Unicode不是。

@poke在回答中进一步讲时,我在这里使用“编码”来表示“将字符映射到图表上的位置”和“将这些位置表示为位序列”。也许有一个更好的用语,但我不确定会是什么。
IMSoP '18

3
好吧,您不能仅将“编码”与您自己的定义一起使用。很抱歉在这里挑剔,但您不能在开头为“该问题包含有关Unicode是什么的主要误解”的答案中做到这一点

2
IMSoP(和@poke):我完全同意poke关于使用“ encoding”来表示除编码以外的其他含义的说法,尽管我也对IMSoP的困境感到同情。我的首选是将Unicode称为具有多种编码的字符集,而通常字符集和编码可互换使用,因为在大多数情况下(或可能是全部?)都是一对一的关系。
所罗门·鲁兹基

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.