鉴于此表:
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;
因为更新匹配但没有效果:
id description
----------- -----------
1 CO2
(1 affected rows)
(1 affected rows)
id description
----------- -----------
1 CO2
(1 affected rows)
就好像 SQL Server 确定,因为2显然只是一个很小的2,所以最终值不会改变,所以不值得改变它。
有人可以对此有所了解,并可能提出解决方法(而不是更新到中间值)?
下标 2 不是 varchar 字符集的一部分(在任何排序规则中,不仅仅是 Modern_Spanish)。所以让它成为一个 nvarchar 常量:
@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
由于VARCHAR
缺少N
字符串文字上的前缀而导致的转换。而且,数据库的默认排序规则的代码页不包含完全相同的字符,但找到了“最合适”的映射,这就是为什么你得到 a2
而不是?
.您可以通过执行以下简单测试来查看此效果:
回报:
需要明确的是,如果数据库的默认排序规则的代码页确实包含完全相同的字符,那么它将在该代码页中转换为相同的字符。然后,在您的情况下,由于您要存储到
NVARCHAR
列中,它会再次翻译回原始的 Unicode 字符。下面的最后一个示例显示了这种行为。重要提示:请注意,转换是在解释字符串文字时发生的,这是在将其存储到列中之前。这意味着即使该列可以包含该字符,它也已根据数据库的默认排序规则转换为其他内容,这一切都是由于省略了
N
该字符串文字上的前缀。这正是你正在(或曾经)经历的。例如,如果您的数据库的默认排序规则是韩语排序规则之一(四个双字节字符集之一),那么您不会看到这个问题,因为“下标 2”字符在该字符中可用设置(代码页 949)。试试下面的测试看看(它使用列的排序规则而不是数据库的默认排序规则,因为这样更容易显示):
回报:
如您所见,Latin1_General 排序规则使用代码页 1252(
Modern_Spanish
排序规则使用的相同代码页)作为VARCHAR
数据,没有完全匹配,但它们确实具有“最佳匹配”映射(这就是您所看到的) )。但是,使用代码页 949 作为VARCHAR
数据的韩语排序规则确实与“下标 2”字符完全匹配。为了进一步说明,我们可以使用韩国排序规则之一的默认排序规则创建一个新数据库,然后运行问题中的确切 SQL:
回报:
更新
对于有兴趣了解更多关于这里到底发生了什么(即所有血腥细节)的人,请参阅我刚刚发布的两部分调查:
在 WHERE 条件下使用哪种排序规则将 NVARCHAR 转换为 VARCHAR?(第 2 部分的 A 部分:“鸭子”)
在 WHERE 条件下使用哪种排序规则将 NVARCHAR 转换为 VARCHAR?(第 2 部分的 B 部分:“兔子”)