如何在 UTF8 排序的 varchar(N) 列中插入长字符串?

How can I insert long string into UTF8-collated varchar(N) column?

提问人:habdl 提问时间:10/31/2023 最后编辑:GSerghabdl 更新时间:10/31/2023 访问量:72

问:

我在源中有数据,我需要将其插入到另一个表中。
我需要使用的排序规则是 UTF8(特别是 )。
问题是,即使我将数据削减到最大允许长度(使用函数,或 或其他),某些数据的插入也会失败,并且
VARCHAR(MAX)VARCHAR(N)Czech_100_CI_AS_SC_UTF8LEFTSUBSTRING

消息 2628,级别 16,状态 1,第 25
行 字符串或二进制数据将在表“tablename”列“columnname”中被截断。截断值:'sometext'。

我的问题是,如何适当地缩短文本,使其适合,但仍然尽可能多地保留它?
字符串的实际长度将取决于所使用的字符,对于纯拉丁文本,可以使用全长。如果有几个重音字符,则可用长度会缩短一点。如果文本是完全 Unicode(非拉丁文字,例如日语),则可用长度可能会减半或三分之一。

重现步骤:

DROP TABLE IF EXISTS [#tmpdl]
GO
CREATE TABLE [#tmpdl] ([Txt] VARCHAR(10) COLLATE Czech_100_CI_AS_SC_UTF8 NULL)
GO
INSERT INTO [#tmpdl] ([Txt]) VALUES (LEFT('123456789abcd', 10)) --this works
GO
INSERT INTO [#tmpdl] ([Txt]) VALUES (LEFT('123456789ábcd', 10)) --this fails
GO
INSERT INTO [#tmpdl] ([Txt]) VALUES (LEFT(N'一二三四', 10)) --this also fails
GO
sql-server utf-8 varchar sql-server-collation

评论

0赞 DavidG 10/31/2023
你为什么使用而不是?VARCHARNVARCHAR
1赞 Martin Smith 10/31/2023
如果你,那么它似乎可以随心所欲地工作。虽然不确定我会推荐吗SET ANSI_WARNINGS OFF;
0赞 Martin Smith 10/31/2023
@DavidG - UTF8 排序规则通常与 - 这给出了每个字符编码的可变字节。 无论领度如何,仍将使用编码VARCHARNVARCHARUCS-2
0赞 nbk 10/31/2023
问题是他 SQL 使用 16 位字符和 utf8 9 位,因此您必须寻找干净的转换
0赞 habdl 10/31/2023
@DavidG 我正在使用 VARCHAR 和 UTF8 排序规则,因为项目需要它。这些要求是一成不变的,我对此无能为力。

答:

3赞 GSerg 10/31/2023 #1

即使我将数据削减到最大允许长度(使用 function 或 或其他什么),某些数据的插入也会失败LEFTSUBSTRING

with or you 正在将其削减到指定的字符数,而 in 指定字节数,因此在截断 or 之后,您会留下一个字符串,该字符串可能仍包含超过 10 个字节 - 这是相同的,出于插入的目的,就好像您从未截断过它一样。LEFTSUBSTRING10varchar(10)LEFTSUBSTRING

将字符串截断为多个字符而不是字节的一种简单方法是使用 或 ,因为它们会在不适合的代理项对之前剪切字符串保留原始排序规则CASTCONVERT

INSERT INTO [#tmpdl] ([Txt]) VALUES (cast(N'123456789ábcd' COLLATE Czech_100_CI_AS_SC_UTF8 as varchar(10)));
INSERT INTO [#tmpdl] ([Txt]) VALUES (cast(N'一二三四' COLLATE Czech_100_CI_AS_SC_UTF8 as varchar(10)));

请注意,这需要 SQL Server 2012 或更高版本。

评论

0赞 habdl 10/31/2023
谢谢,这有效!我假设如果数据较长,CAST 或 CONVERT 会产生相同的错误。
2赞 GSerg 10/31/2023
@habdl 该错误仅发生在隐式截断上。Cast 使其明确。
0赞 Thom A 10/31/2023 #2

对于 UTF8 值表示可以存储在数据类型中的字节数,而不是字符数。对于该值,这些值都是 ANSI 字符,大小为 1 个字节。但是,对于 ,字符的大小为 2 个字节;这会使您尝试的字符串的 datalength 为 11 个字节,而不是 10 个字节。你的最后一个字符串也是一个类似的故事, :10'123456789abcd''123456789ábcd'áINSERT'一二三四'

SELECT V.YourString,
       DATALENGTH(V.YourString),
       DATALENGTH(LEFT(V.YourString,10))
FROM (VALUES('123456789abcd' COLLATE Czech_100_CI_AS_SC_UTF8),
            (CONVERT(varchar(20),N'123456789ábcd' COLLATE Czech_100_CI_AS_SC_UTF8)),
            (CONVERT(varchar(20),N'一二三四' COLLATE Czech_100_CI_AS_SC_UTF8)))V(YourString);
YourString (英语) 数据长度 10CharsDataLength
123456789ABCD(英语:ABCCD) 13 10
123456789ábc 14 11
一二三四 12 12

您需要确保定义的列的长度有足够的空间容纳所有字节,而不是字符数:

DROP TABLE IF EXISTS #tmpdl
GO
CREATE TABLE [#tmpdl] ([Txt] VARCHAR(12) COLLATE Czech_100_CI_AS_SC_UTF8 NULL)
GO
INSERT INTO [#tmpdl] ([Txt]) VALUES (LEFT('123456789abcd', 10)) --this works
GO
INSERT INTO [#tmpdl] ([Txt]) VALUES (LEFT('123456789ábcd', 10)) --this fails
GO
INSERT INTO [#tmpdl] ([Txt]) VALUES (LEFT(N'一二三四', 10)) --this also fails
GO
SELECT *
FROM  #tmpdl;
GO
DROP TABLE #tmpdl