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

NVARCHAR storing characters not supported by UCS-2 encoding on SQL Server

通过SQL服务器的documentation (and legacy documentation),一个nvarchar字段没有_SC排序规则,应该使用UCS-2 ENCODING.

Starting with SQL Server 2012 (11.x), when a Supplementary Character (SC) enabled collation is used, these data types store the full range of Unicode character data and use the UTF-16 character encoding. If a non-SC collation is specified, then these data types store only the subset of character data supported by the UCS-2 character encoding.

它还声明 UCS-2 ENCODING 仅存储 UCS-2 支持的子集字符。来自维基百科 UCS-2 specification:

UCS-2, uses a single code value [...] between 0 and 65,535 for each character, and allows exactly two bytes (one 16-bit word) to represent that value. UCS-2 thereby permits a binary representation of every code point in the BMP that represents a character. UCS-2 cannot represent code points outside the BMP.

因此,根据上述规范,我似乎无法存储以下表情符号:其值为 0x1F60D(或十进制为 128525,远高于 UCS-2 的 65535 限制).但是在 SQL Server 2008 R2 或 SQL Server 2019(均具有默认值 SQL_Latin1_General_CP1_CI_AS COLLATION)上,在 nvarchar 字段上,它被完美地存储并返回(尽管不支持与 LIKE= 进行比较):

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

所以我的问题是:

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

  2. Microsoft 的 nchar/nvarchar 文档是否误导了“那么这些数据类型仅存储 UCS-2 字符编码支持的字符数据子集”?

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

  4. UCS-2 ENCODING 不支持时,SQL 服务器如何能够正确存储和检索该字段的数据?

注意:服务器的排序规则是 SQL_Latin1_General_CP1_CI_AS,字段的排序规则是 Latin1_General_CS_AS
注意 2:最初的问题陈述了关于 SQL Server 2008 的测试。我在 SQL Server 2019 上测试并得到了相同的结果,各自的 COLLATIONs.
注意 3:我测试的每个其他字符,在 UCS-2 支持范围之外,都以相同的方式运行。一些是: , , , ,

这里有一些关于问题中 posted 的 MS 文档片段、示例代码、问题本身以及问题评论中的陈述的澄清。我相信,通过我的以下 post 中提供的信息,可以消除大部分的困惑:

How Many Bytes Per Character in SQL Server: a Completely Complete Guide

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

  1. UCS-2 是一种处理 Unicode 字符集子集的编码。它以 2 字节为单位工作。使用 2 个字节,您可以对值 0 - 65535 进行编码。此代码点范围称为 BMP(基本多语言平面)。 BMP 是所有 not Supplementary Characters 的字符(因为那些是 BMP 的补充字符),but 它确实包含一组代码专门用于在 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 中),但它们实际上是两个 2 字节代码单元 在 UTF-16 中编码时(同样,它们是 UTF-8 中的四个 1 字节单位,以及 UTF-32 中的一个 4 字节单位)。

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

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

  5. UCS-2 和 UTF-16 之间的唯一区别是 UTF-16 知道如何解释 代理对(由一对代理代码组成点,所以至少它们被恰当地命名了)。这是 _SC 归类(并且,从 SQL Server 2017 开始,版本 _140_ 归类包括对补充字符的支持,因此 none 其中有 _SC 在他们的名字中)进来:他们允许 built-in SQL 服务器功能正确解释补充字符。而已!这些归类与存储和检索补充字符没有任何关系,甚至与排序或比较它们也没有任何关系(即使“排序规则和 Unicode 支持”文档特别指出,这就是这些排序规则的作用——我的“待办事项”列表中的另一个要修复的项目)。对于名称中既没有 _SC 也没有 _140_ 的排序规则(尽管 new-as-of-SQL Server 2019 Latin1_General_100_BIN2_UTF8 可能 是 grey-area,至少,我记得那里或 Japanese_*_140_BIN2 排序规则存在一些不一致),built-in 函数仅处理 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(补充字符范围)之间的值将 return 非 NULL 值。如果不是,则传入任何大于 65535 的值将 return NULL。 (当然,如果 NCHAR() 总是有效,那就更好了,因为存储/检索总是有效,所以请投票支持这个建议:NCHAR() function should always return Supplementary Character for values 0x10000 - 0x10FFFF regardless of active database's default collation)。

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

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

    有关在使用非“SC”排序规则时创建增补字符的更多详细信息,请参阅我对以下 DBA.SE 问题的回答: How do I set a SQL Server Unicode / NVARCHAR string to an emoji or Supplementary Character?

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

  10. SQL_* 排序规则中的补充字符和名称中没有版本号的排序规则没有排序权重。因此,它们都彼此相等,也与任何其他没有排序权重的 BMP 代码点(包括“space”(U+0020)和“null”(U+0000))相等。他们开始在版本 _90_ 排序规则中修复此问题。

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

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

If a non-SC collation is specified, then these data types store only the subset of character data supported by the UCS-2 character encoding.

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