当前位置: 首页>>技术问答>>正文


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

webfans 技术问答 , , , , , 去评论

问题描述

鉴于此表:

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不是varchar字符集的一部分(在任何排序规则中,不仅仅是Modern_Spanish)。所以使它成为一个nvarchar常量:

UPDATE test SET description = N'CO₂' WHERE id = 1;

次佳解决思路

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

  1. 您正在使用VARCHAR文字(无N前缀)而不是NVARCHAR文字(带有N前缀的字符串),因此Unicode字符将转换为VARCHAR

  2. VARCHAR是一种8位编码,在大多数情况下,每个字符一个字节,但每个字符也可以是两个字节。另一方面,NVARCHAR是16位编码(UTF-16 Little Endian),每个字符是两个字节或四个字节。

  3. 由于用于映射字符的可用字节数的差异,8位编码本质上在可映射的字符数方面受到更多限制。对于Single-Byte字符集(大多数),VARCHAR数据最多为256个字符,对于Double-Byte字符集(最多只有少数几个),最多为65,536个字符。另一方面,NVARCHAR数据可以映射超过110万个Unicode字符(尽管当前映射的不到250k)。

  4. 由于8位/VARCHAR数据的映射数量有限,因此不同的字符分组(基于语言/文化)分布在多个”Code Pages”(即字符集)中

  5. 每个排序规则指定用于VARCHAR数据的代码页(如果有)(NVARCHAR是所有字符)

  6. 当转换的字符串从NVARCHAR字面的或可变的(即统一字符编码/UTF-16 /所有字符)到VARCHAR(基于代码页,其在大多数排序规则指定上的字符集),数据库的默认排序规则用于

  7. 如果用于转换的排序规则的代码页不包含相同的字符,但包含”best fit”映射,则将使用”best fit”映射。

  8. 如果用于转换的排序规则的代码页不包含相同的字符或包含”best fit”映射,则将使用默认的”replacement”字符(最常见的是?)。

因此,您看到的是NVARCHARVARCHAR转换,因为缺少字符串文字上的N前缀。并且,数据库的默认排序规则的代码页不包含完全相同的字符,但找到了”best fit”映射,这就是为什么要获得2而不是?的原因。

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

SELECT '₂', N'₂';

返回:

2    ₂

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

重要提示:请注意,转换是在解释字符串文字时发生的,该字符串文字存储在列中之前。这意味着即使列可以容纳该字符,它也将基于数据库的默认排序规则转换为其他字符,所有这些都归因于该字符串文字中的N前缀。而这正是你所经历的(或正在经历的)。

例如,如果您的数据库的默认排序规则是韩国排序规则之一(四个Double-Byte字符集之一),那么您将不会看到此问题,因为该字符集中有“下标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;

返回:

8bit_Latin1_General-1252    8bit_Korean-949    UTF16LE_Latin1_General-1252
2                           ₂                  ₂

正如您所看到的,使用代码页1252(与Modern_Spanish Collat​​ions使用的代码页相同)的VARCHAR数据的Latin1_General Collat​​ions没有完全匹配,但它们确实有”best fit”映射(这是您所看到的) 。但是,使用VARCHAR数据的代码页949的朝鲜语校对确实与“下标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;

返回:

id  description
1   CO2


id  description
1   CO₂

UPDATE

对于有兴趣了解更多关于这里究竟发生了什么的人(即所有血腥细节),请参阅我刚发布的two-part调查:

参考资料

本文由朵颐IT整理自网络, 文章地址: https://duoyit.com/article/3053.html,转载请务必附带本地址声明。