web-dev-qa-db-ja.com

SQL Server: "Latin1_General_CI_AS"と "[garbage]"の間の照合の競合を解決できません

そのため、レポートで使用するT-SQLで大きなUDFを作成しています。 UDFには、非常に多くの一般的なテーブル式が含まれています。

ある時点で、別のCTEを追加していました。

 cteCmtCauses AS (
    SELECT ProductId = p.Id,
           Name = hz.Name,
           CMT = CONCAT(IIF(hz.Cmt_c= '1', 'C', ''), IIF(hz.hz.Cmt_m = '1', 'M', ''), IIF(hz.Cmt_t = '1', 'R', ''))
    FROM [redacted]

    UNION ALL

    SELECT ProductId = p.Id,
           Name = c.Name,
           --C = c.Cmr_HasCarcinogenicRisk,
           --M = c.Cmr_HasMutagenicRisk,
           --R = c.Cmr_HasToxicForReproductionRisk,
           CMT = CONCAT(IIF(hz.Cmt_c= '1', 'C', ''), IIF(hz.hz.Cmt_m = '1', 'M', ''), IIF(hz.Cmt_t = '1', 'R', ''))
    FROM [redacted]
 ),

 cteCmtCausesConcat AS (
    SELECT ProductId = p.Id,
           ComponentIds = (
                -- Here the issue happens
                SELECT CONCAT(cte.CMT, N'|', cte.Name, dbo.QueryConcatenationString())
                FROM cteCmtCauses cte 
                WHERE cte.ProductId = p.Id
                FOR XML PATH(N''), TYPE
           )
    FROM [redacted]
 ),

UDFミューテーションを永続化しようとすると、次のエラーが発生しました。

Msg 468, Level 16, State 9, Procedure QueryProduct, Line 93
Cannot resolve the collation conflict between "Latin1_General_CI_AS" and "널㾍.鉀杫.....祉߾.䊙꛸.鈀杫..." in the concat operation.

実際、すべての試みでメッセージが少し変化しました:

Cannot resolve the collation conflict between "Latin1_General_CI_AS" and "࿠䚋.剀焩.....祉߾.䊙꛸.刀焩..." in the concat operation.
Cannot resolve the collation conflict between "Latin1_General_CI_AS" and "꿠䥆.뉀洶.....祉߾.䊙꛸.눀洶..." in the concat operation.
Cannot resolve the collation conflict between "Latin1_General_CI_AS" and "꿠洦.퉀洷.....祉߾.䊙꛸.툀洷..." in the concat operation.
Cannot resolve the collation conflict between "Latin1_General_CI_AS" and "焐柘.牀䯏.....祉߾.䊙꛸.爀䯏..." in the concat operation.

私はそれを使ってそれを回避することができました:

SELECT CONCAT(cte.CMT, N'|', cte.Name COLLATE Latin1_General_CI_AS, dbo.QueryConcatenationString())

しかし、奇妙なのは、データベース内のeverythingであり、tempdbは同じ照合順序を持っています:データベース照合順序はLatin1_General_CI_ASです(ただし、 UNION ALLであるLatin1_General_CS_ASで使用されるテーブル)。

完全に再現可能なサンプルの要旨 、データベースの照合順序がLatin1_General_CI_ASであることを確認してください。

この問題をどのように正しく解決しますか?これは既知のバグです。これを使い始めたらSQLサーバーがデータを静かに破損することを心配する必要がありますか? UDF?

使用する

Microsoft SQL Server 2012 (SP3-GDR) (KB3194721) - 11.0.6248.0 (X64) 
    Sep 23 2016 15:49:43 
    Copyright (c) Microsoft Corporation
    Business Intelligence Edition (64-bit) on Windows NT 6.1 <X64> (Build 7601: Service Pack 1) (Hypervisor)
4
Sebazzz

更新

ようやくこれをテストする時間があり、問題を再現することができました。照合の優先順位を順守しない(以下の私の元の回答に記されている)ことはa問題でしたが、-this問題ではありませんでした(両方とも同じ根本的なバグが原因である可能性があります)。これが私が今確認できることです:

  1. O.P.のテストスクリプト(Gistを使用)では、重要な部分が欠落しているため、この問題は発生しません。NVARCHARおよびComponentテーブルのStatement "Name"列のいずれかの照合順序を、他と異なるものにする必要があります。
  2. 実際のエラーは、UNION ALLの2つの「名前」列間の照合の不一致です。
  3. UNION ALLshould haveの照合の不一致により、CONCAT関数に到達する前にクエリが終了しました。両方の列がVARCHARで、少なくとも1つの列がNVARCHARである場合、クエリは終了しました。その場合、CONCAT関数はクエリが正しく終了しないようにします。
  4. CONCATが関係している場合(少なくともこのUNION ALLシナリオでは)、クエリが正しく動作しない2つの方法があります。
    1. サブクエリの列(照合が一致しないUNION ALLを含む)がnotである場合、CONCAT関数の最初のパラメーターであると、誤解を招くエラーメッセージで終了し、エラー「UNION ALL操作」ではなく「concat操作」にあります(エラーメッセージのガベージコレーション名を忘れないでください!)
    2. サブクエリの列(照合が一致しないUNION ALLを含む)isCONCAT関数の最初のパラメーターである場合、実際にはsucceedを使用します。 CONCAT関数によって返される値に対するデータベースのデフォルトの照合。 (以下の最終テストケースを参照)
  5. CONCAT組み込み関数はSQL Server 2014で修正されました。これは、この動作も、「元の回答」(以下を参照)に示されている不正な動作も、そのバージョンから再現可能であるためです(そして私はテストしました) 2014年、2016年、2017年、2019年)。
-- DROP TABLE #Mix;
CREATE TABLE #Mix
(
  [VC1]  VARCHAR(50)  COLLATE SQL_Latin1_General_CP437_CS_AS,
  [VC2]  VARCHAR(50)  COLLATE Azeri_Cyrillic_100_CS_AS_WS,
  [NVC1] NVARCHAR(50) COLLATE Frisian_100_CS_AI_KS,
  [NVC2] NVARCHAR(50) COLLATE Sami_Sweden_Finland_100_CI_AI
);
INSERT INTO #Mix ([VC1], [VC2], [NVC1], [NVC2]) VALUES (0xB0, 0xDE, 0xDE, 0xDE);
SELECT * FROM #Mix;
/*
VC1    VC2    NVC1    NVC2

░      Ю      Þ       Þ
*/



SELECT CONCAT(N'Both VARCHAR', sub.[WhatEva])
FROM   (
    SELECT [VC1]
    FROM #Mix
    UNION ALL
    SELECT [VC2]
    FROM #Mix
) sub([WhatEva]);
/*
 Msg 457, Level 16, State 1, Line XXXXX
Implicit conversion of varchar value to varchar cannot be performed because the
   collation of the value is unresolved due to a collation conflict between
   "Azeri_Cyrillic_100_CS_AS_WS" and "SQL_Latin1_General_CP437_CS_AS" in
   UNION ALL operator.
*/


SELECT CONCAT(N'At least one NVARCHAR', sub.[WhatEva])
FROM   (
    SELECT [VC1]
    FROM #Mix
    UNION ALL
    SELECT [NVC1]
    FROM #Mix
) sub([WhatEva]);
/*
-- 2012
Msg 468, Level 16, State 9, Line XXXXX
Cannot resolve the collation conflict between "{db_default_collation}" and
   "堓.ꚤ鍛翹.堓.툀堗.툀堗.쓀姧.꺱䱷..꺱䱷......꺱䱷..툘堗.帎鍲翹.堓..錿翹..."
   in the concat operation.

-- 2014, 2016, 2017, 2019
Msg 451, Level 16, State 1, Line XXXXX
Cannot resolve collation conflict between "Frisian_100_CS_AI_KS" and
   "SQL_Latin1_General_CP437_CS_AS" in UNION ALL operator occurring in SELECT
   statement column 1.
*/


-- SUCCESS!?!?!?! Should be an error!!!
SELECT CONCAT(sub.[WhatEva], N':At least one NVARCHAR') AS [ConcatSuccessWTF?],
       SQL_VARIANT_PROPERTY(CONCAT(sub.[WhatEva], N':At least one NVARCHAR'),
                            'collation') AS [ResultingCollation]
FROM   (
    SELECT [VC1]
    FROM #Mix
    UNION ALL
    SELECT [NVC1]
    FROM #Mix
) sub([WhatEva]);
/*
-- 2012
ConcatSuccessWTF?          ResultingCollation

░:At least one NVARCHAR    {db_default_collation}
Þ:At least one NVARCHAR    {db_default_collation}


-- 2014, 2016, 2017, 2019
Msg 456, Level 16, State 1, Line XXXXX
Implicit conversion of nvarchar value to sql_variant cannot be performed because the
   resulting collation is unresolved due to collation conflict between
   "Frisian_100_CS_AI_KS" and "SQL_Latin1_General_CP437_CS_AS" in UNION ALL operator.
*/

これは既知のバグですか

パブリックフォーラムで指摘されているかどうかは不明ですが、次のバージョン(SQL Server 2014)で修正されたため、内部で(Microsoftに)通知されているはずですが、SQL ServerでテストしたService Packにはありません。 2012、SP4 GDR(11.0.7462.6)。

この問題を適切に解決する方法

あなたまたは他の誰かがまだSQL Server 2012を使用していて、これが実行されている場合は、UNION ALL操作内にあるソースでの照合の競合を解決するのが最善です。 notが必要な照合順序を持つ列を含むテーブルを選択し、COLLATE関数がなくてもUNION ALL操作がそれ自体で成功するようにCONCAT句をそこで適用します。使用されています。これは、COLLATE関数でCONCATを指定するよりも優れています。これは、UNION ALLの後で、この場合shouldは失敗しますが、次のバグにより成功するためです。 CONCAT


元の答え

照合順序の問題(照合順序の問題から生じるエラーメッセージとは別)は、CONCAT組み込み関数が照合順序の優先順位を尊重しないため、すべての入力パラメーターが同じ照合順序である必要があるためです。明らかに、他と同じ照合順序ではない1つの入力パラメーターがあります。そのパラメーターはcte.Nameであり、現在COLLATEキーワードを使用して修正しています。

このシナリオは次のようにシミュレートできます。任意のデータベースから実行できます。次のコードを実行しているデータベースのデフォルトの照合順序は、SQL_Latin1_General_CP1_CI_ASです。

CREATE TABLE #TT (Col1 NVARCHAR(50) COLLATE SQL_EBCDIC278_CP1_CS_AS);
INSERT INTO #TT values ('something');


SELECT CONCAT('now this is ', tmp.Col1)
FROM #TT tmp;
/*
Msg 468, Level 16, State 9, Line 17
Cannot resolve the collation conflict between "SQL_Latin1_General_CP1_CI_AS" and
    "SQL_EBCDIC278_CP1_CS_AS" in the concat operation.
*/

また、次の2つのクエリは、照合が各入力パラメーターごとに評価され、最初の入力パラメーターが使用される照合を設定していることを示しています。

SELECT CONCAT('now this is ', tmp.Col1, N' else' COLLATE Latin1_General_100_CI_AS)
FROM #TT tmp;
/*
Msg 468, Level 16, State 9, Line 23
Cannot resolve the collation conflict between "SQL_Latin1_General_CP1_CI_AS" and
    "SQL_EBCDIC278_CP1_CS_AS" in the concat operation.
Msg 468, Level 16, State 9, Line 23
Cannot resolve the collation conflict between "SQL_Latin1_General_CP1_CI_AS" and
    "Latin1_General_100_CI_AS" in the concat operation.
*/

SELECT CONCAT('now this is ' COLLATE Latin1_General_100_CI_AS, tmp.Col1, N' else')
FROM #TT tmp;
/*
Msg 468, Level 16, State 9, Line 30
Cannot resolve the collation conflict between "Latin1_General_100_CI_AS" and
    "SQL_EBCDIC278_CP1_CS_AS" in the concat operation.
Msg 468, Level 16, State 9, Line 30
Cannot resolve the collation conflict between "Latin1_General_100_CI_AS" and
    "SQL_Latin1_General_CP1_CI_AS" in the concat operation.
*/

この競合は、次の2つの方法で修正できます。

  1. CONCAT組み込み関数は使用しないでください。 CONCATの主な利点は、各パラメーターを文字列型にする必要がないことです。内部で文字列への変換を処理します。これは、連結する文字列以外の項目がいくつかある場合に便利です。しかし、文字列しか持っていない場合は、何のメリットもありません。おそらく、それらすべてを関数に渡すとパフォーマンスが低下します。さらに、CONTACTを使用しないことで、照合順序の優先順位が引き継がれ、ほとんどの場合、競合が自動的に解決されます。

    SELECT 'now this is ' + tmp.Col1
    FROM #TT tmp;
    -- now this is something
    

    この場合、照合順序の優先順位によって、tmp.Col1列の照合順序が文字列リテラルの照合順序(「現在の」データベースのデフォルトの照合順序を使用する)をオーバーライドすることが決定されます。

  2. COLLATE句を使用します(既に行っているように)。これはCOLLATEキーワードの使用の1つであるため、このアプローチには何の問題もありません。

    -- Force the Collation of the column in the temp table to match the "current" database:
    SELECT CONCAT('now this is ', tmp.Col1 COLLATE SQL_Latin1_General_CP1_CI_AS)
    FROM #TT tmp;
    -- now this is something
    
    
    -- Force the Collation of the string literal to match the column in the temp table:
    SELECT CONCAT('now this is ' COLLATE SQL_EBCDIC278_CP1_CS_AS, tmp.Col1)
    FROM #TT tmp;
    -- now this is something
    

    これらの2つのケースでは、使用する照合順序は最初の入力パラメーターによって決定され、この場合、列定義から取得される2番目のパラメーター(下部の例)の照合順序に明示的に設定する必要があります。または、2番目のパラメーターは、最初のパラメーター(上の例)の照合に一致するように明示的に設定する必要があります。この場合、文字列リテラルであるため、データベースのデフォルトから取得されます。

4
Solomon Rutzky

簡単な回避策を探している場合は、 COLLATE DATABASE_DEFAULT を使用すると、物事を再び進めることができます。

1
pacreely