web-dev-qa-db-ja.com

NVARCHAR列から文字0x0000を削除できません

だから、私はReplace関数とchar(0)のバグについてすべて知っています。

不正なインポートからのNVARCHAR(128)文字を含む列(NCHAR(0x0000))があります。

SQL Server 2008 R2を使用しています。

列の照合順序はSQL_Latin1_General_CP1_CI_ASです。

私はオンラインで私が見つけることができるすべてのものを試しました、そして、何もカラムから悪臭のあるchar(0)文字を取得しません。

これが私の最新の試みであり、BAFFLING(SQLサーバーのバグ?)の結果です。

各文字をループし、0x0000を特定の文字に置き換える関数があります。

ALTER FUNCTION dbo.ReplaceCharZero
(
  @testString NVARCHAR(MAX),
  @charToReplaceWith NCHAR(1) = ' '
)
RETURNS NVARCHAR(MAX)
AS
BEGIN
    DECLARE
        @i INT = 1 ,
        @fixedString NVARCHAR(MAX) = ''
    WHILE @i <= LEN(@testString)
        BEGIN
            IF SUBSTRING(@testString, @i, 1) = CHAR(0x00)
                BEGIN
                    --PRINT 'Found' + CAST(@i AS VARCHAR)
                    SET @fixedString = @fixedString + @charToReplaceWith         
                END
            ELSE
                BEGIN
                    --PRINT 'NOT Found' + CAST(@i AS VARCHAR)
                    SET @fixedString = @fixedString
                        + SUBSTRING(@testString, @i, 1)
                END
            SET @i = @i + 1
        END
    RETURN @fixedString               
END

そして、これが私がテストするために行うことです:

BEGIN TRAN

    DECLARE @ShortDescription NVARCHAR(128), @SupplierId INT, @Language CHAR(2)

    SELECT TOP 1 @ShortDescription = ShortDescription,
                 @SupplierId = SupplierID,
                 @Language = Language
    FROM   Supplier_Multilingual
    WHERE  ShortDescription LIKE '%' +  CHAR(0x00) + '%'

    SET @ShortDescription =  REPLACE(dbo.ReplaceCharZero(@ShortDescription, ' '), '-', ' ')

    UPDATE dbo.Supplier_MultiLingual
    SET    ShortDescription = NULL 
    WHERE  SupplierID = @SupplierId
    AND    Language = @Language

    UPDATE dbo.Supplier_MultiLingual
    SET    ShortDescription = dbo.ReplaceCharZero(@ShortDescription, '') 
    WHERE  SupplierID = @SupplierId
    AND    Language = @Language

    SELECT *
    FROM   Supplier_Multilingual
    WHERE  SupplierId = @SupplierId
    AND    Language = @Language
    AND    ShortDescription LIKE '%' +  CHAR(0x00) + '%'

ROLLBACK TRAN

テストでは、列を変数として取得し、関数を実行して0x0000を取り除き、元の列をNULLで更新してから、固定列に更新します次に、クエリを実行して、0x0000文字がまだ存在するかどうかを確認します。

6
ttomsen

CHAR(0)はスペースに変換されているようです、またはCHAR(32)

以下に問題を示します。

DECLARE @Data NVARCHAR(255);
SELECT @Data = 'this is a test' + CHAR(0) + 'of null';
DECLARE @i INT;
SET @i = 1;
DECLARE @txt NVARCHAR(255);
WHILE @i < LEN(@Data)
BEGIN
    IF SUBSTRING(@Data, @i, 1) = CHAR(0) 
    BEGIN
        SET @txt = 'found a null char at position ' + CONVERT(NVARCHAR(255),@i);        
        RAISERROR (@txt, 0, 1) WITH NOWAIT;
    END
    SET @i = @i + 1;
END

enter image description here

4
Max Vernon

REPLACE関数のバグ?またはSQL Server全般では?それについてはよくわかりません。ここでの唯一の「問題」は、文字列比較がどのように処理されるかを完全に理解しているだけではありません。

照合順序は、特定の文字を他の文字と比較する方法を定義します。時には、1つ以上の他の文字と同等の文字の特定の組み合わせについてのルールがあります。また、_0x00_(null)や_0x20_(スペース)などの文字は、互いにまたは他の文字と同等であるというルールがあります。また、人生をより面白くするために、次の例に示すように、SQL Server照合順序(つまり、_SQL__で始まるもの)を使用したVARCHARデータに固有のニュアンスがいくつかあります。

_SELECT REPLACE('VARCHAR with SQL_Latin1_General_CP1_CI_AS'+CHAR(0)+'Matches', CHAR(0),
               ': ' COLLATE SQL_Latin1_General_CP1_CI_AS);
SELECT REPLACE(N'NVARCHAR with SQL_Latin1_General_CP1_CI_AS'+NCHAR(0)+N'Matches', NCHAR(0),
               N': ' COLLATE SQL_Latin1_General_CP1_CI_AS);
SELECT REPLACE('VARCHAR with Latin1_General_100_CI_AS'+CHAR(0)+'Matches', CHAR(0),
               ': ' COLLATE Latin1_General_100_CI_AS);
SELECT REPLACE(N'NVARCHAR with Latin1_General_100_CI_AS'+NCHAR(0)+N'Matches', NCHAR(0),
               N': ' COLLATE Latin1_General_100_CI_AS);
_

戻り値:

_VARCHAR with SQL_Latin1_General_CP1_CI_AS: Matches

NVARCHAR with SQL_Latin1_General_CP1_CI_AS

VARCHAR with Latin1_General_100_CI_AS

NVARCHAR with Latin1_General_100_CI_AS
_

そこで、@ Maxの answer にあるクエリに基づいたクエリを使用して、この動作を見てみましょう。文字列リテラルとCHAR(0)Nプレフィックスを追加しました。また、次の部分を見やすくするために、追加のNCHAR(0)を追加しました。そして、使用されている実際のコードポイントを表示するクエリを追加しました(_0x0000_の値が実際にそこにあることを証明するため、およびREPLACE()を呼び出して、本当にバグがあるかどうかを確認します)。 。

_DECLARE @Data NVARCHAR(255);
SELECT @Data = N'this is' + NCHAR(0) + N'a test' + NCHAR(0) + N'of null';

SELECT @Data;
SELECT CONVERT(VARBINARY(50), @Data);
SELECT REPLACE(@Data, NCHAR(0), N'~');
_

これは

0x7400680069007300200069007300000061002000740065007300740000006F00660020006E0075006C006C00

これは

最初の結果は、_(null)_の終了により、文字列が「is」の直後に終了することを示しています。 2番目の結果は基になるコードを示しており、_0x0000_文字の2つのインスタンスを強調しています。 3番目の結果は、REPLACE関数が、渡されたNCHAR(0)と_0x0000_文字が一致しないように見えることを示しています。

しかし、NCHAR(0)がここで一致することを期待する必要がありますか?バイナリ照合を強制することにより、文字列比較に通常適用されるすべての等価ルールを効果的に無効にすることができます。 __BIN2_照合は非推奨であり、特に必要がない限り使用すべきではないため、__BIN_照合を使用します。

上記のセットに次のクエリを追加して、バッチを再実行します。

_SELECT REPLACE(@Data, NCHAR(0) COLLATE Latin1_General_100_BIN2, N'~');
_

次の追加の結果が得られるはずです。

これはヌルのテストです

したがって、REPLACE関数は実際に機能し、これはSQL Server 2008 R2、SP3とSQL Server 2012 SP2の両方でテストされました。


さて、REPLACEの問題のみに対処してNCHAR(0)で機能しないが、スペースに相当するNCHAR(0)には対処しなかった(つまり、NCHAR(32))またはNCHAR(0x20))。

ここで、@ Maxの回答からのメインクエリの適応を使用します。テスト文字列に再度NCHAR(0)を追加し(実際には、位置8のスペースを置き換えただけです)、一致する文字のコードポイントをRAISERRORメッセージに追加しました。

_SET NOCOUNT ON;
GO
DECLARE @Data NVARCHAR(255);
SELECT @Data = N'this is' + NCHAR(0) + N'a test' + NCHAR(0) + N'of null';

DECLARE @i INT,
        @CodePoint INT;
SET @i = 1;

WHILE @i < LEN(@Data)
BEGIN
    IF SUBSTRING(@Data, @i, 1) = NCHAR(0) --COLLATE Latin1_General_100_BIN2
    BEGIN
        SET @CodePoint = UNICODE(SUBSTRING(@Data, @i, 1));
        RAISERROR (N'Found a NULL char (Code Point = %d) at position: %d',
                   10, 1, @CodePoint, @i) WITH NOWAIT;
    END;
    SET @i = @i + 1;
END;
_

このクエリ(COLLATE句がまだコメント化されている)は次を返します。

_Found a NULL char (Code Point = 32) at position: 5
Found a NULL char (Code Point = 0) at position: 8
Found a NULL char (Code Point = 32) at position: 10
Found a NULL char (Code Point = 0) at position: 15
Found a NULL char (Code Point = 32) at position: 18
_

これらは@Maxのテストで報告されたものと同じ位置ですが、それぞれのケースでどのコードポイントが一致しているかを示しています。そして、はい、それは_32_と_0_の両方と同等です。

ここで、COLLATE句のコメントを外して、再実行します。戻ります:

_Found a NULL char (Code Point = 0) at position: 8
Found a NULL char (Code Point = 0) at position: 15
_

これを行うもう1つの方法は、COLLATE句を使用せずに、IFステートメントを次のように変更することです。

_IF ( UNICODE(SUBSTRING(@Data, @i, 1)) = 0 )
_

もちろん、これらの2つの修正(WHILE句またはUNICODE()関数のいずれかを使用するCOLLATEループ)のどちらも、元の問題を解決するために必要です単純なREPLACECOLLATE句を使用)がそれを処理するため、入力データの_0x0000_文字。


まとめ:

  • 文字列の文字を置換または削除する場合は、ループを使用する必要はありません。 REPLACE関数は、COLLATEキーワードを介して3つの入力パラメーターの少なくとも1つに__BIN2_照合を指定する限り、正常に機能します(技術的にはどちらでもかまいません。バイナリ照合は数値コードポイント値のみを比較するため、バイナリ照合)。
  • 特定のコードポイントをテストする必要がある場合(上記のようなループ内など)、UNICODE()関数を使用するのがおそらく最も高速です。これは、実際にそこにある値を報告するだけだからです。これはCOLLATEキーワードを使用するよりも高速になるはずです。
  • 一連のコードポイント/文字をテストする必要がある場合は、COLLATEキーワードを使用して、__BIN2_バイナリ照合順序を指定します。
6
Solomon Rutzky