use*_*489 3 sql-server collation encoding sql-server-2014 unicode
我正在将一个 SQL 表/列中的值插入到另一个中。由于其他原因,这些列的数据类型不同,但我不明白为什么 nvarchar(10) 源和 char(10) 目标有时会在 SQL Server 2014 中导致错误:
字符串或二进制数据将被截断。
len(sourcecol) = 10 和 datalength(sourcecol) = 20。
可能是因为 nvarchar 类型的源列中存储了一些不可见的空格/字符?
Sol*_*zky 11
如果没有来自 OP 的数据示例和表 DDL,则很难确定导致OP出现此错误的确切原因是什么。以下原因:
某些代码页(由每个CHAR
/VARCHAR
字段的排序规则确定)允许使用双字节字符,以便映射超过 256 个可放入单个字节的字符。由于字符串字段的最大长度实际上是字节而不是字符的问题,最大长度为 10的CHAR
/VARCHAR
字段只能容纳 10 个字节,可以容纳 5 - 10 个字符。源字段为 时NVARCHAR(10)
,这 10 个字符可以映射到VARCHAR
字段中需要超过 1 个字节的字符。获得此错误所需要的只是 9 个“常规”单字节字符和 1 个双字节字符,这将需要 11 个实际字节。
因此,请检查CHAR(10)
目标表中字段的排序规则。一旦您知道该字段的排序规则的名称,它将指示正在使用的代码页,如果它是 932、936、949 或 950,那么这很可能是问题的根源。在这种情况下,只需将目标字段更改为最大长度为 20(为了安全,以防所有 10 个字符都是双字节)。我会推荐VARCHAR(20)
,但如果你真的非常喜欢空白填充,那就做CHAR(20)
.
一个稍微更详细的解释如下:
众所周知,VARCHAR
(即8位扩展ASCII)数据是单字节的,NVARCHAR
(即Unicode)数据是双字节的。这种对字符串数据(和编码)的理解在使用美国-英语字母表(以及相当多的其他语言,至少是大多数“活跃”语言)时总是正确的。虽然在使用大多数语言时它往往是正确的,但它肯定并不总是正确的。
对于 Unicode 数据,这种理解不正确的方式超出了本问题的范围,在此不再详述。
但是说到我们的老朋友先生VARCHAR
,有一些情况会允许字符占用2个字节而不是一个字节。是的,你没看错。但是如何?嗯,在 Unicode 出现之前,一些字母表中字符超过 255 个的文化仍然希望使用他们的母语字母表。由于这在单个字节(范围为 256 个值)中是不可能的,因此他们提出了双字节字符集 (DBCS)。它们作为不同的代码页处理,Windows 和 SQL Server 支持其中的四个:
不要让术语“双字节”混淆您的含义,即它们就像NVARCHAR
仅适用于 16 位序列一样。这些代码页实际上是可变长度编码,类似于 UTF-8,并且将使用单个字节(8 位)来表示至少前 128 个值(0 - 127)和一些(128 - 255 范围)值。
这些如何适应截断错误?好吧,NVARCHAR
和VARCHAR
数据类型的最大长度实际上是用字节而不是字符来表示的。意思VARCHAR(10)
是,最多 10 个字节,即使少于 10 个字符适合这 10 个字节。同样,NVARCHAR(10)
最多 20 个字节,即使少于 10 个字符适合那 20 个字节。
考虑到这一点,我们知道从 Unicode 转换为代码页将尝试映射到相同的字符。在大多数代码页中,这些字符的大小都是 1 个字节。但是在 4 个 DBCS 代码页中,存在相当多的字符(因此可以映射到)并且是 2 个字节(否则它们将不存在)。
这里的问题是正在使用 DBCS 代码页(用于目标),并且至少一个被映射的字符在VARCHAR
类型中占用了 2 个字节。
以下是此行为的工作示例。
测试设置
首先,运行此代码以设置测试。运行此测试的数据库的默认排序规则无关紧要。在这里,我们创建了一个临时表来保存主要 Unicode 字符集的前 65,536 个代码点中的每一个(超出初始 65,536 的字符需要两个代码点,因此每个是 4 个字节,但同样超出了此处的范围,因为它们不会更改与当前问题相关的行为;)
SET NOCOUNT ON;
IF (OBJECT_ID(N'tempdb..#Source') IS NOT NULL)
BEGIN
DROP TABLE #Source;
END;
CREATE TABLE #Source ([Character] NVARCHAR(1));
;WITH nums (num) AS
(
SELECT TOP (65536) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM [master].[sys].[columns] sc1
CROSS JOIN [master].[sys].[columns] sc2
)
INSERT INTO #Source ([Character])
SELECT NCHAR(num - 1)
FROM nums;
SELECT * FROM #Source;
Run Code Online (Sandbox Code Playgroud)
测试 1:Latin1 字符集(代码页 1252)
运行以下将不会产生任何错误。它只是有效。但它只转换前 256 个值,因为这是所有可以放入任何单字节字符集 (SBCS) 的值。
IF (OBJECT_ID(N'tempdb..#Destination_CP1252') IS NOT NULL)
BEGIN
DROP TABLE #Destination_CP1252;
END;
CREATE TABLE #Destination_CP1252 ([Character] VARCHAR(1) COLLATE Latin1_General_100_CI_AS);
INSERT INTO #Destination_CP1252 ([Character])
SELECT [Character]
FROM #Source;
SELECT [Character],
LEN([Character]) AS [NumberOfCharacters],
DATALENGTH([Character]) AS [NumberOfBytes],
CONVERT(VARBINARY(2), [Character]) AS [BinaryValue]
FROM #Destination_CP1252;
Run Code Online (Sandbox Code Playgroud)
测试 2:日语 (Shift-JIS) 字符集(代码页 932)
IF (OBJECT_ID(N'tempdb..#Destination_CP932') IS NOT NULL)
BEGIN
DROP TABLE #Destination_CP932;
END;
CREATE TABLE #Destination_CP932 ([Character] VARCHAR(1) COLLATE Japanese_Unicode_CI_AS);
INSERT INTO #Destination_CP932 ([Character])
SELECT [Character]
FROM #Source;
Run Code Online (Sandbox Code Playgroud)
运行上面显示的代码将产生以下错误:
消息 8152,级别 16,状态 2,第 1 行
字符串或二进制数据将被截断。
该语句已终止。
如果这是双字节字符集 (DBCS) 转换的问题,那么将字段大小增加 1 个字节应该可以解决它。首先,让我们确保表中没有实际插入任何内容(以便我们确定下INSERT
一条语句是将数据放入的内容):
SELECT *
FROM #Destination_CP932;
-- no rows
Run Code Online (Sandbox Code Playgroud)
伟大的。现在运行以下命令:
ALTER TABLE #Destination_CP932
ALTER COLUMN [Character] VARCHAR(2) COLLATE Japanese_Unicode_CI_AS;
INSERT INTO #Destination_CP932 ([Character])
SELECT [Character]
FROM #Source;
Run Code Online (Sandbox Code Playgroud)
没有错误。呜呼!让我们看看转换了哪些字符:
SELECT [Character],
LEN([Character]) AS [NumberOfCharacters],
DATALENGTH([Character]) AS [NumberOfBytes],
CONVERT(VARBINARY(2), [Character]) AS [BinaryValue]
FROM #Destination_CP932
WHERE 1 = 1
--AND DATALENGTH([Character]) > 1
--AND [Character] <> '?'
Run Code Online (Sandbox Code Playgroud)
如果您滚动浏览结果,您应该注意[NumberOfBytes]
和[BinaryValue]
字段。
要仅查看双字节值,请取消注释以下行并重新运行:
AND DATALENGTH([Character]) > 1
Run Code Online (Sandbox Code Playgroud)
要查看代码页 932 中的全部值,请重新注释掉DATALENGTH
where 条件并取消注释以下行并重新运行:
AND [Character] <> '?'
Run Code Online (Sandbox Code Playgroud)
我的计数显示 9483 个字符。公平地说,加上 1 来说明?
被排除的实际字符,我们在一个VARCHAR
字段中得到总共 9484 个字符的授权。
归档时间: |
|
查看次数: |
8605 次 |
最近记录: |