Kri*_*rte 4 sql-server collation t-sql character-set encoding
嗯,这个问题是众所周知的,但如果有的话,我正在寻找一个更聪明的解决方案。
出于某种原因,系统无法识别某些字符,我无法比较列
下面是一个文本示例:
对
ASPIRADOR ULTRASSONICO-LOCAÇAO (NOTA FISCAL SERVIÇO)
错误的
ASPIRADOR ULTRASSONICO-LOCA€AO (NOTA FISCAL SERVI€O)
其实我是通过这个功能来解决这个问题的
create function fixcollation(@ps_Texto VARCHAR(4000)) returns VARCHAR(4000)
as
begin
declare @vlgsv1itu INT declare @nxn68ezzi INT declare @dw17rsyva VARCHAR(50) declare @iw8a2z01i VARCHAR(50) declare @t64e98xq6 VARCHAR(50) declare @zwjs2imy3 INT declare @jsyt85sy8 VARCHAR(4000)
----------------------------------------------------
set @dw17rsyva = ' …ƃ„µ·Ç¶Ž‚Šˆ‰ÔÒÓ¡‹ÖÞØ¢•ä“”àãå♣—–éëꚇ€§'
set @iw8a2z01i = 'áàãâäÁÀÃÂÄéèêëÈÉÊËíìïÍÌÏóòõôöÓÒÕÔÖúùûüÚÙÛÜçǺØ'
set @jsyt85sy8 = @ps_Texto set @zwjs2imy3 = IsNull(datalength(@ps_Texto), 0)
set @nxn68ezzi = 1
while(@nxn68ezzi <= IsNull(datalength( @ps_Texto), 0))
begin
set @vlgsv1itu = 1
while(@vlgsv1itu <= IsNull(datalength(@dw17rsyva), 0))
begin
IF(ASCII(SUBSTRING(@ps_Texto, @nxn68ezzi, 1) COLLATE LATIN1_GENERAL_CS_AS) = ASCII(SUBSTRING(@dw17rsyva, @vlgsv1itu, 1) COLLATE LATIN1_GENERAL_CS_AS))
BEGIN
set @t64e98xq6 = SUBSTRING( @iw8a2z01i, @vlgsv1itu, 1) set @jsyt85sy8 = SUBSTRING(@jsyt85sy8, 1, @nxn68ezzi -1) + @t64e98xq6 + SUBSTRING(@jsyt85sy8, @nxn68ezzi + 1, @zwjs2imy3 - @nxn68ezzi)
break
end
set @vlgsv1itu = @vlgsv1itu + 1
end
set @nxn68ezzi = @nxn68ezzi + 1
end
return @jsyt85sy8
end
Run Code Online (Sandbox Code Playgroud)
所以,我的问题是:这是最好的方法还是我在这里错过了什么?
编辑
只是一个补充测试
select dbo.fixcollation(' …ƃ„µ·Ç¶Ž‚Šˆ‰ÔÒÓ¡‹ÖÞØ¢•ä“”àãå♣—–éëꚇ€§')
select dbo.FixCodePage850toCodePage1252(' …ƃ„µ·Ç¶Ž‚Šˆ‰ÔÒÓ¡‹ÖÞØ¢•ä“”àãå♣—–éëꚇ€§')
Run Code Online (Sandbox Code Playgroud)
这是我的生产环境中的结果
修复整理
将CodePage850 修复为CodePage1252
我个人感谢 Solomon Rutzky
Sol*_*zky 10
这是一个不正确的编码问题。字符编码为 DOS 代码页 850,但您使用的目标代码页(基于Latin1_General
排序规则)是 Windows 代码页 1252。例如,在 DOS 代码页 850 中,Ç
字符的值为 0x80(或 128十进制)。但是,Windows 代码页 1252 中 0x80 的相同值会为您提供€
. 同样,Ã
在 DOS 代码页中,850 的值为 0xC7(或十进制中的 199)。但是,Windows 代码页 1252 中 0xC7 的相同值会为您提供Ç
.
不正确的字符是不正确的,因为在为源指定了错误的编码的情况下导入到 SQL Server 中。这在 SQL Server 中不会发生,因为这将是代码页转换问题,在这种情况下,相同的“字符”将针对目标代码页中的相同字符转换其值(如果目标代码页中存在该字符,否则你会得到?
)。例如:
SELECT ASCII('Ç' COLLATE Latin1_General_CI_AS) AS [CP1252 Value],
'Ç' COLLATE SQL_Latin1_General_CP850_CI_AS AS [CharacterInCP850],
ASCII('Ç' COLLATE SQL_Latin1_General_CP850_CI_AS) AS [CP850 Value];
Run Code Online (Sandbox Code Playgroud)
返回:
SELECT ASCII('Ç' COLLATE Latin1_General_CI_AS) AS [CP1252 Value],
'Ç' COLLATE SQL_Latin1_General_CP850_CI_AS AS [CharacterInCP850],
ASCII('Ç' COLLATE SQL_Latin1_General_CP850_CI_AS) AS [CP850 Value];
Run Code Online (Sandbox Code Playgroud)
意思是,这很可能发生在文件导入期间 - BCP.exe、SQLCMD.exe、BULK INSERT
、OPENROWSET(BULK...)
、读取文件的自定义应用程序代码等 - 其中指定了错误的源代码页,或者根本没有代码页被指定为来源。如果正在执行为该文件指定代码页 1252 的导入,则会产生您在此处看到的效果,因为这些字节是针对代码页 850 而不是代码页 1252 进行编码的。
应该注意的是,如果驱动程序(ODBC 等)被告知使用错误的代码页,那么这也可能发生在来自应用程序代码的数据中。
现在,关于解决这个问题的方法:
SUBSTRING
在将字符成对加载到表变量中时执行两个循环,这将允许使用该REPLACE
函数进行单个循环。使用ASCII
函数和区分大小写、区分重音的排序规则是不必要的,并且在使用_BIN2
排序规则时容易出错(如果两个字符匹配正在搜索的内容)会更好。使用以下函数进行转换。首先它获取当前字符串的字节,然后将这些字节注入到VARCHAR
使用代码页 850的列中,然后从表变量中选择该值到局部变量中(无论如何都需要返回值),其效果为将字符串转换为数据库默认排序规则使用的代码页(此处必须是代码页 1252,否则您将无法从函数中获得“正确”的字符串):
USE [tempdb];
GO
CREATE FUNCTION dbo.FixCodePage850toCodePage1252
(
@CodePage850String VARCHAR(8000)
)
RETURNS VARCHAR(8000)
WITH SCHEMABINDING
AS
BEGIN
DECLARE @Convert850to1252 TABLE
(
[String] VARCHAR(8000) COLLATE SQL_Latin1_General_CP850_CI_AS
);
DECLARE @ReturnValue VARCHAR(8000);
INSERT INTO @Convert850to1252 ([String])
VALUES (CONVERT(VARBINARY(8000), @CodePage850String, 0));
SELECT @ReturnValue = [String] -- automatic conversion to Code Page of database
FROM @Convert850to1252;
RETURN @ReturnValue;
END;
GO
Run Code Online (Sandbox Code Playgroud)
测试这两个函数返回相同的结果:
SELECT dbo.fixcollation('lj§ ULTRASSONICO-LOCA€AO (NOTA SERVI€O)');
-- Ãëº ULTRASSONICO-LOCAÇAO (NOTA SERVIÇO)
SELECT dbo.FixCodePage850toCodePage1252('lj§ ULTRASSONICO-LOCA€AO (NOTA SERVI€O)');
-- Ãëº ULTRASSONICO-LOCAÇAO (NOTA SERVIÇO)
Run Code Online (Sandbox Code Playgroud)我想出了一个测试来检查所有字符的映射,以防万一提供翻译功能的公司错过了任何映射。我过滤掉了仅在代码页 850 中找到的图形字符和无点“i”。
USE [tempdb];
GO
;WITH nums AS
(
SELECT TOP (256) (ROW_NUMBER() OVER (ORDER BY (SELECT 0)) - 1) AS [num]
FROM master.sys.columns
), vals AS
(
SELECT nums.[num] AS [Value],
CHAR(nums.[num]) AS [Character],
dbo.fixcollation(CHAR(nums.[num])) AS [OldWay],
dbo.FixCodePage850toCodePage1252(CHAR(nums.[num])) AS [NewWay]
FROM nums
)
SELECT vals.*,
ASCII(vals.[NewWay]) AS [NewValue]
FROM vals
WHERE vals.[Character] <> vals.[NewWay] COLLATE Latin1_General_BIN2
AND vals.[OldWay] <> vals.[NewWay] COLLATE Latin1_General_BIN2
AND vals.[Value] NOT IN (176, 177, 178, 179, 180, 185, 186, 187, 188,
191, 192, 193, 194, 195, 196, 197, 200, 201,
202, 203, 204, 205, 206, 217, 218, 219, 220,
223, 254, 213); -- characters only in CP850
Run Code Online (Sandbox Code Playgroud)
这将返回一个包含 52 个字符的列表,这些字符可能在导入过程中像其他字符一样被误译,但被另一家公司提供的 UDF 跳过,该公司仅处理 98 个可能字符中的 46 个。
归档时间: |
|
查看次数: |
1431 次 |
最近记录: |