Á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)
Sol*_*zky 22
@gbn 已经解释了基本原因和修复,但您所看到的行为的具体原因是:
VARCHAR
文字(无N
前缀)而不是NVARCHAR
文字(带N
前缀的字符串),因此 Unicode 字符将转换为VARCHAR
.VARCHAR
是一种 8 位编码,在大多数情况下,每个字符一个字节,但也可以每个字符两个字节。另一方面,NVARCHAR
是 16 位编码 (UTF-16 Little Endian),每个字符有两个字节或四个字节。VARCHAR
单字节字符集(其中大部分)的数据最多为 256 个字符,双字节字符集的数据最多为 65,536 个字符(仅少数)。另一方面,NVARCHAR
数据可以映射超过 110 万个 Unicode 字符(尽管目前映射不到 250k)。VARCHAR
数据完成的映射数量有限,不同的字符分组(基于语言/文化)分布在多个“代码页”(即字符集)中VARCHAR
数据(NVARCHAR
是所有字符)NVARCHAR
(即 Unicode / UTF-16 / 所有字符)转换为VARCHAR
(基于大多数排序规则中指定的代码页的字符集)时,将使用数据库的默认排序规则?
)。因此,由于缺少字符串文字的前缀,您看到的是NVARCHAR
toVARCHAR
转换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)
更新
对于有兴趣了解更多关于这里到底发生了什么(即所有血腥细节)的人,请参阅我刚刚发布的两部分调查: