NVARCHAR 存储 SQL Server 上 UCS-2 编码不支持的字符

Vit*_*tox 2 sql-server unicode encoding ucs2 collation

根据 SQL Server 的文档(和遗留文档),nvarchar没有_SC排序规则的字段应该使用UCS-2 ENCODING.

从 SQL Server 2012 (11.x) 开始,当使用支持补充字符 (SC) 的排序规则时,这些数据类型存储完整范围的 Unicode 字符数据并使用 UTF-16 字符编码。如果指定了非 SC 归类,则这些数据类型仅存储 UCS-2 字符编码支持的字符数据子集。

它还指出,UCS-2 ENCODING仅存储 支持的子集字符UCS-2。从维基百科UCS-2 规范

UCS-2 对每个字符使用 0 到 65,535 之间的单个代码值 [...],并且只允许两个字节(一个 16 位字)来表示该值。因此,UCS-2 允许 BMP 中表示字符的每个代码点的二进制表示。UCS-2 不能表示 BMP 之外的代码点。

因此,根据上面的规范,我似乎无法存储像这样的表情符号:其值为0x1F60D(或十进制的 128525,远高于 UCS-2 的 65535 限制)。但是在 SQL Server 2008 R2 或 SQL Server 2019(都带有 default SQL_Latin1_General_CP1_CI_AS COLLATION)上,在一个nvarchar字段上,它被完美地存储和返回(尽管不支持与LIKE或进行比较=):

在此处输入图片说明

SMSS 无法正确呈现表情符号,但这里是从查询结果中复制和粘贴的值:

所以我的问题是:

  1. nvarchar字段是否真的USC-2在 SQL Server 2008 R2 上使用(我也在 SQL Server 2019 上进行了测试,具有相同的非_SC排序规则并得到相同的结果)?

  2. 是微软的文档nchar/nvarchar误导性“那么这些数据类型存储只能由UCS-2字符编码支持的字符数据的子集”?

  3. 是否UCS-2 ENCODING支持超过 65535 的代码点?

  4. 当它不受支持时,SQL Server 如何能够正确存储和检索该字段的数据UCS-2 ENCODING

注意:服务器的排序规则是SQL_Latin1_General_CP1_CI_AS,字段的排序规则是Latin1_General_CS_AS
注 2:原始问题说明了关于 SQL Server 2008 的测试。我在 SQL Server 2019 上测试并得到了相同的结果,分别具有相同的COLLATIONs.
注意 3:我测试的所有其他字符,在UCS-2支持的范围之外,都以相同的方式运行。一些是: , , , ,

Sol*_*zky 9

关于问题中发布的 MS 文档片段、示例代码、问题本身以及对问题的评论中所做的陈述,这里有几处澄清。我相信,通过我的以下帖子中提供的信息,可以消除大部分困惑:

SQL Server 中每个字符有多少字节:完全完整指南

首先要做的事(这是唯一的方法,对吗?):我并不是在侮辱编写 MS 文档的人,因为 SQL Server 本身就是一个巨大的产品,需要涵盖的内容很多,等等,但是对于时刻(直到我有机会更新它),请谨慎阅读“官方”文档。有几个关于排序规则/Unicode 的错误陈述。

  1. UCS-2 是一种处理 Unicode 字符集子集的编码。它以 2 字节为单位工作。使用 2 个字节,您可以对 0 - 65535 的值进行编码。此代码点范围称为 BMP(基本多语言平面)。BMP 是所有不是补充字符的字符(因为它们是 BMP 的补充),它确实包含一组专门用于编码 UTF-16 中的补充字符的代码点(即 2048 代理代码点)。这是 UTF-16 的完整子集。

  2. UTF-16 是一种处理所有 Unicode 字符集的编码。它也以 2 字节为单位工作。事实上,UCS-2 和 UTF-16 在 BMP 代码点和字符方面没有区别。不同之处在于 UTF-16 使用 BMP 中的 2048 个代理代码点来创建代理对,这是所有补充字符的编码。虽然补充字符是 4 字节(在 UTF-8、UTF-16 和 UTF-32 中),但在以 UTF-16 编码时它们实际上是两个 2 字节代码单元(同样,它们是UTF-8中的四个 1 字节单元-8,以及一个 4 字节的 UTF-32)。

  3. 由于 UTF-16 仅扩展了 UCS-2 可以完成的工作(通过实际定义代理代码点的用法),因此在两种情况下可以存储的字节序列绝对没有区别。用于在 UTF-16 中创建补充字符的所有 2048 个代理代码点都是 UCS-2 中的有效代码点,它们在 UCS-2 中没有任何定义的用法(即解释)。

  4. NVARCHAR, NCHAR, 和已弃用的所以不使用它-NTEXT数据类型都存储以 UCS-2 / UTF-16 编码的 Unicode 字符。从存储的角度来看,绝对没有区别。因此,如果某些东西(甚至在 SQL Server 之外)说它可以存储 UCS-2,这并不重要。如果它可以做到这一点,那么它就可以固有地存储 UTF-16。事实上,虽然我没有机会更新上面链接的帖子,但正如预期的那样,我已经能够在 Windows XP 上运行的 SQL Server 2000 中存储和检索表情符号(其中大部分是补充字符)。我认为,直到 2003 年才定义了补充字符,当然在 1999 年开发 SQL Server 2000 时也没有定义。事实上(再次),UCS-2 只用于 Windows/SQL Server,因为微软在 UTF-16 最终确定和发布之前就推进了开发(并且很快,UCS-2 就过时了)。

  5. UCS-2 和 UTF-16 之间的唯一区别是 UTF-16 知道如何解释代理对(由一对代理代码点组成,因此至少它们被适当地命名)。这就是_SC排序规则(从 SQL Server 2017 开始,还有版本_140_排序规则,其中包括对补充字符的支持,因此_SC它们的名称中都没有):它们允许内置的 SQL Server 函数正确解释补充字符. 就是这样!这些归类在没有做存储和检索增补字符,也没有他们甚至有什么与排序或比较它们有关(尽管“排序规则和 Unicode 支持”文档明确指出这是这些排序规则所做的 - 我的“待办事项”列表中的另一项要修复)。对于名称中既没有_SC也没有的排序规则_140_(尽管新的 SQL Server 2019Latin1_General_100_BIN2_UTF8 可能是灰色区域,至少,我记得那里或Japanese_*_140_BIN2排序规则存在一些不一致),内置函数仅处理 BMP 代码点(即 UCS-2)。

  6. 不“处理”补充字符意味着不将两个代理代码点的有效序列解释为实际上是单个补充代码点。因此,对于非“SC”排序规则,BMP 代理代码点 1 (B1) 和 BMP 代理代码点 2 (B2) 只是这两个代码点,没有一个被定义,因此它们显示为两个“无” (即 B1 后跟 B2)。这就是为什么它可能使用一分为二增补字符SUBSTRING/ LEFT/RIGHT因为他们不知道把这两个BMP代码点在一起。但是,一个“SC”核对将读取那些代码点B1和B2从磁盘或存储器和看到单个补充代码点现在S.它可以被正确地处理经由SUBSTRING/ CHARINDEX/等

  7. NCHAR()函数(不是数据类型;是的,命名不当的函数;)也对当前数据库的默认排序规则是否支持补充字符很敏感。如果是,则传入 65536 和 1114111 之间的值(补充字符范围)将返回非NULL值。如果不是,则传入 65535 以上的任何值将返回NULL. (当然,如果NCHAR()总是有效的话会好得多,因为存储/检索总是有效,所以请投票支持这个建议:NCHAR() 函数应该总是为值 0x10000 - 0x10FFFF 返回补充字符,而不管活动数据库的默认排序规则如何) .

  8. 幸运的是,您不需要“SC”排序规则来输出补充字符。您可以粘贴文字字符,或转换 UTF-16 Little Endian 编码的代理对,或使用该NCHAR()函数输出代理对。以下适用于在 Windows XP 上运行的 SQL Server 2000(使用 SSMS 2005):

    SELECT N'', -- 
    CONVERT(VARBINARY(4), N''), -- 0x3DD8A9DC
    CONVERT(NVARCHAR(10), 0x3DD8A9DC), --  (regardless of DB Collation)
    NCHAR(0xD83D) + NCHAR(0xDCA9) --  (regardless of DB Collation)
    
    Run Code Online (Sandbox Code Playgroud)

    有关在使用非“SC”排序规则时创建补充字符的更多详细信息,请参阅我对以下 DBA.SE 问题的回答: 如何将 SQL Server Unicode / NVARCHAR 字符串设置为表情符号或补充字符?

  9. 这些都不会影响您看到的内容。如果您存储一个代码点,那么它就在那里。它的行为方式——排序、比较等——由排序规则控制。但是,它的显示方式由字体和操作系统控制。没有字体可以包含所有字符,因此不同的字体包含不同的字符集,在更广泛使用的字符上有很多重叠。但是,如果字体具有映射的特定字节序列,则它可以显示该字符。这就是为什么在 Windows XP 上运行的 SQL Server 2000(使用 SSMS 2005)中正确显示增补字符所需的唯一工作是添加包含字符的字体并进行一两次小的注册表编辑(对 SQL Server 没有更改)。

  10. SQL_*排序规则中的补充字符和名称中没有版本号的排序规则没有排序权重。因此,它们都等同于彼此以及任何其他没有排序权重的 BMP 代码点(包括“空格”(U+0020)和“空”(U+0000))。他们开始在版本_90_整理中解决这个问题。

  11. 除了可能需要用于查询编辑器和/或网格结果和/或错误 + 消息的字体更改为具有所需字符的字体之外,SSMS 与此无关。(SSMS 不会渲染空间数据之外的任何内容;字符由显示驱动程序 + 字体定义 + 可能其他内容渲染)。

因此,文档中的以下声明(来自问题):

如果指定了非 SC 归类,则这些数据类型仅存储 UCS-2 字符编码支持的字符数据子集。

既荒谬又不正确。他们可能打算说数据类型只存储UTF-16编码的子集(因为 UCS-2子集)。此外,即使它说“UTF-16 字符编码”,它仍然是错误的,因为您传入的字节将被存储(假设列或变量中有足够的可用空间)。