无法将“CO2”更新为“CO”?在表格行中

Álv*_*lez 20 sql-server collation t-sql sql-server-2008-r2 unicode

鉴于此表:

CREATE TABLE test (
    id INT NOT NULL,
    description NVARCHAR(100) COLLATE Modern_Spanish_CI_AS NOT NULL
);
INSERT INTO test (id, description) VALUES (1, 'CO2');
Run Code Online (Sandbox Code Playgroud)

我意识到我无法解决排版问题:

SELECT * FROM test WHERE id = 1;
UPDATE test SET description = 'CO?' WHERE id = 1;
SELECT * FROM test WHERE id = 1;
Run Code Online (Sandbox Code Playgroud)

因为更新匹配但没有效果:

id          description
----------- -----------
1           CO2

(1 affected rows)

(1 affected rows)

id          description
----------- -----------
1           CO2

(1 affected rows)
Run Code Online (Sandbox Code Playgroud)

就好像 SQL Server 确定了这一点,因为? 显然只是一个很小的2,最终值不会改变,所以不值得改变它。

有人可以对此有所了解并提出解决方法(除了更新为中间值)?

gbn*_*gbn 30

下标 2 不是 varchar 字符集的一部分(在任何排序规则中,而不仅仅是 Modern_Spanish)。所以让它成为一个 nvarchar 常量:

UPDATE test SET description = N'CO?' WHERE id = 1;
Run Code Online (Sandbox Code Playgroud)

  • @ÁlvaroGonzález 和 gbn:需要明确的是,“下标 2”在相关数据库的默认排序规则指定的代码页中不可用,这是用于字符串文字和变量的排序规则,而不是列的排序规则(尽管两者都是可能使用相同的代码页)。但是,“下标 2”可通过韩语校对在代码页 949 中找到。这在这里没有帮助,但仅供参考。我的 [answer](https://dba.stackexchange.com/a/191619/30859) 中有详细信息和示例。 (2认同)

Sol*_*zky 22

@gbn 已经解释了基本原因和修复,但您所看到的行为的具体原因是:

  1. 您使用的是VARCHAR文字(无N前缀)而不是NVARCHAR文字(带N前缀的字符串),因此 Unicode 字符将转换为VARCHAR.
  2. VARCHAR是一种 8 位编码,在大多数情况下,每个字符一个字节,但也可以每个字符两个字节。另一方面,NVARCHAR是 16 位编码 (UTF-16 Little Endian),每个字符有两个字节或四个字节。
  3. 由于用于映射字符的可用字节数不同,8 位编码本质上在可映射的字符数方面受到更多限制。VARCHAR单字节字符集(其中大部分)的数据最多为 256 个字符,双字节字符集的数据最多为 65,536 个字符(仅少数)。另一方面,NVARCHAR数据可以映射超过 110 万个 Unicode 字符(尽管目前映射不到 250k)。
  4. 由于可以使用 8 位/VARCHAR数据完成的映射数量有限,不同的字符分组(基于语言/文化)分布在多个“代码页”(即字符集)中
  5. 每个排序规则指定哪个代码页(如果有)用于VARCHAR数据(NVARCHAR是所有字符)
  6. 将字符串文字或变量从NVARCHAR(即 Unicode / UTF-16 / 所有字符)转换为VARCHAR(基于大多数排序规则中指定的代码页的字符集)时,将使用数据库的默认排序规则
  7. 如果用于转换的排序规则的代码页不包含相同的字符,但包含“最佳匹配”映射,则将使用“最佳匹配”映射。
  8. 如果用于转换的排序规则的代码页不包含相同的字符或包含“最佳匹配”映射,则将使用默认的“替换”字符(最常见?)。

因此,由于缺少字符串文字的前缀,您看到的是NVARCHARtoVARCHAR转换N。而且,数据库的默认排序规则的代码页不包含完全相同的字符,但找到了“最适合”的映射,这就是为什么您得到2的是?.

您可以通过执行以下简单测试来查看此效果:

SELECT '?', N'?';
Run Code Online (Sandbox Code Playgroud)

返回:

2    ?
Run Code Online (Sandbox Code Playgroud)

需要明确的是,如果数据库的默认排序规则的代码页确实包含完全相同的字符,那么它将在该代码页中转换为相同的字符。然后,在您的情况下,由于您要存储到一NVARCHAR列中,它会再次转换回原始的 Unicode 字符。下面的最后一个示例显示了这种行为。

重要提示:请注意,转换发生在解释字符串文字时,也就是在将其存储到列中之前。这意味着即使该列可以包含该字符,它也已经根据数据库的默认排序规则转换为其他内容,所有这一切都是由于N去掉了该字符串文字的前缀。而这正是您正在(或曾经)经历的。

例如,如果您的数据库的默认排序规则是韩文排序规则之一(四个双字节字符集之一),那么您就不会看到此问题,因为该字符中存在“下标 2”字符集(代码页 949)。尝试以下测试来查看(它使用列的排序规则而不是数据库的默认排序规则,因为它更容易显示):

CREATE TABLE #TestChar
(
    [8bit_Latin1_General-1252] VARCHAR(2) COLLATE Latin1_General_100_CI_AS_SC,
    [8bit_Korean-949] VARCHAR(2) COLLATE Korean_100_CI_AS_SC,
    [UTF16LE_Latin1_General-1252] NVARCHAR(2) COLLATE Latin1_General_100_CI_AS_SC
);

INSERT INTO #TestChar VALUES (N'?', N'?', N'?');

SELECT * FROM #TestChar;
Run Code Online (Sandbox Code Playgroud)

返回:

8bit_Latin1_General-1252    8bit_Korean-949    UTF16LE_Latin1_General-1252
2                           ?                  ?
Run Code Online (Sandbox Code Playgroud)

如您所见,Latin1_General 排序规则使用代码页 1252(与排序规则使用的代码页相同Modern_Spanish)作为VARCHAR数据,没有完全匹配,但它们确实有一个“最适合”的映射(这就是您所看到的)。但是,使用代码页 949 作为VARCHAR数据的韩文排序规则确实与“下标 2”字符完全匹配。


为了进一步说明,我们可以创建一个新数据库,其默认排序规则是朝鲜语排序规则之一,然后运行问题中的确切 SQL:

CREATE DATABASE [TestKorean-949] COLLATE Korean_100_CI_AS_KS_WS_SC;
ALTER DATABASE [TestKorean-949] SET RECOVERY SIMPLE;
GO

USE [TestKorean-949];

CREATE TABLE test (
    id INT NOT NULL,
    description NVARCHAR(100) COLLATE Modern_Spanish_CI_AS NOT NULL
);
INSERT INTO test (id, description) VALUES (1, 'CO2');


SELECT * FROM test WHERE id = 1;
UPDATE test SET description = 'CO?' WHERE id = 1;
SELECT * FROM test WHERE id = 1;
Run Code Online (Sandbox Code Playgroud)

返回:

id  description
1   CO2


id  description
1   CO?
Run Code Online (Sandbox Code Playgroud)

更新

对于有兴趣了解更多关于这里到底发生了什么(即所有血腥细节)的人,请参阅我刚刚发布的两部分调查: