整理错误字符的问题

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.exeSQLCMD.exeBULK INSERTOPENROWSET(BULK...)、读取文件的自定义应用程序代码等 - 其中指定了错误的源代码页,或者根本没有代码页被指定为来源。如果正在执行为该文件指定代码页 1252 的导入,则会产生您在此处看到的效果,因为这些字节是针对代码页 850 而不是代码页 1252 进行编码的。

应该注意的是,如果驱动程序(ODBC 等)被告知使用错误的代码页,那么这也可能发生在来自应用程序代码的数据中。

现在,关于解决这个问题的方法:

  1. 理想情况下,将更新/修复导入数据的方法,以正确说明已对数据进行编码的实际代码页。
  2. 如果无法修复导入过程,那么其他公司提供的功能就不是最佳选择。事实上,这可能是最慢、最复杂的方法,也容易出错(如果他们没有映射所有字符)。没有理由SUBSTRING在将字符成对加载到表变量中时执行两个循环,这将允许使用该REPLACE函数进行单个循环。使用ASCII函数和区分大小写、区分重音的排序规则是不必要的,并且在使用_BIN2排序规则时容易出错(如果两个字符匹配正在搜索的内容)会更好。
  3. 使用以下函数进行转换。首先它获取当前字符串的字节,然后将这些字节注入到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 个。