web-dev-qa-db-fra.com

SQL Server: impossible de résoudre le conflit de classement entre "Latin1_General_CI_AS" et "[garbage]"

J'écris donc une grande FDU en T-SQL à utiliser dans les rapports. L'UDF contient plusieurs expressions de table courantes.

À un moment donné, j'ajoutais un autre 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]
 ),

En essayant de persister la mutation UDF, j'ai eu cette erreur:

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.

Et en fait, à chaque tentative, le message a changé un peu:

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.

J'ai pu contourner ce problème en utilisant:

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

Mais la chose étrange est, tout dans la base de données et tempdb a le même classement: le classement de la base de données, étant Latin1_General_CI_AS (sauf dans l'un des tableaux utilisés dans le UNION ALL lequel est Latin1_General_CS_AS).

Gist of full reproductible sample , assurez-vous que le classement de la base de données est Latin1_General_CI_AS.

Comment puis-je résoudre correctement ce problème , s'agit-il d'un bogue connu et dois-je m'inquiéter de la corruption silencieuse de mes données par le serveur SQL une fois que j'ai commencé à l'utiliser UDF?

En utilisant

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

MISE À JOUR

J'ai finalement eu le temps de tester cela et j'ai pu reproduire le problème. Bien que ne pas respecter la priorité de classement (noté dans ma réponse d'origine ci-dessous) était un problème , ce n'était pas ce problème (bien que les deux puissent être causés par le même bug sous-jacent). Voici ce que je peux maintenant confirmer:

  1. Le script de test de l'OP (via Gist) ne produit pas ce problème car il manque un élément critique: forcer le classement d'une des colonnes NVARCHAR "Name" dans les Component et Statement les tables doivent être différentes les unes des autres.
  2. L'erreur réelle est une incompatibilité de classement entre les deux colonnes "Nom" dans le UNION ALL.
  3. Le décalage de collation dans le UNION ALL aurait dû terminer la requête avant même qu'elle n'atteigne la fonction CONCAT, et ce serait le cas si les deux colonnes étaient VARCHAR, mais si au moins une colonne est NVARCHAR, la fonction CONCAT empêche la requête de se terminer correctement.
  4. Il existe deux façons dont la requête peut se comporter incorrectement lorsque CONCAT est impliqué (au moins dans ce UNION ALL scénario):
    1. Si la colonne de la sous-requête (contenant le UNION ALL avec la différence de collation) n'est pas pas le premier paramètre de la fonction CONCAT, il se terminera par un message d'erreur trompeur, indiquant que l'erreur se trouve dans "l'opération de concaténation" au lieu d'être dans "l'opération UNION ALL" (oh, et n'oublions pas le nom du classement des ordures dans le message d'erreur!)
    2. Si la colonne de la sous-requête (contenant le UNION ALL avec la différence de collation) est le premier paramètre de la fonction CONCAT, alors il sera en fait réussit , en utilisant le classement par défaut de la base de données pour la valeur renvoyée par la fonction CONCAT. (voir le cas de test final ci-dessous)
  5. La fonction intégrée CONCAT a été corrigée à partir de SQL Server 2014 car ni ce comportement, ni le comportement incorrect indiqué dans le "réponse d'origine" (voir ci-dessous) n'est reproductible à partir de cela version (et j'ai testé avec 2014, 2016, 2017 et 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.
*/

est-ce un bug connu

Je ne sais pas si cela a été noté dans un forum public, mais cela doit avoir été remarqué en interne (pour Microsoft) car il a été corrigé à partir de la prochaine version (c'est-à-dire SQL Server 2014), mais pas dans aucun Service Pack comme je l'ai testé sur SQL Server 2012, SP4 GDR (11.0.7462.6).

Comment résoudre correctement ce problème

Si vous, ou quelqu'un d'autre, utilisez toujours SQL Server 2012 et que vous l'exécutez, il est préférable de résoudre le conflit de classement à la source, qui se trouve dans le UNION ALL opération. Choisissez simplement la table avec la colonne qui a le classement que vous voulez pas voulu, et appliquez-y la clause COLLATE de telle sorte que UNION ALL l'opération réussit d'elle-même, même si aucune fonction CONCAT n'est utilisée. C'est mieux que de spécifier la COLLATE dans la fonction CONCAT car c'est après le fait de la UNION ALL, qui dans ce cas devrait échouer, mais est autorisé à réussir en raison du bogue dans CONCAT.


RÉPONSE ORIGINALE

Le problème de classement (distinct du message d'erreur résultant du problème de classement) est dû au fait que la fonction intégrée CONCAT ne respecte pas la priorité de classement et, par conséquent, que tous les paramètres d'entrée doivent être du même classement. De toute évidence, vous avez un paramètre d'entrée qui n'est pas du même classement que les autres. Ce paramètre est cte.Name, que vous avez actuellement corrigé via le mot clé COLLATE.

Nous pouvons simuler ce scénario comme suit. Vous pouvez l'exécuter à partir de n'importe quelle base de données. Le classement par défaut de la base de données dans laquelle j'exécute le code suivant est: 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.
*/

Et les deux requêtes suivantes montrent même que le classement est évalué pour chaque paramètre d'entrée, le premier paramètre d'entrée définissant le classement à utiliser:

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.
*/

Ce conflit peut être résolu de deux manières:

  1. N'utilisez pas la fonction intégrée CONCAT. Le principal avantage de CONCAT est qu'il ne nécessite pas que chaque paramètre soit un type chaîne; il gérera la conversion en chaîne en interne. C'est pratique si vous avez plusieurs éléments non-chaîne à concaténer. Mais si vous n'avez que des chaînes, cela n'apporte aucun avantage et nuit probablement aux performances pour transmettre tout cela à la fonction. ET, en n'utilisant pas CONTACT, la priorité de classement prendra le relais et, dans la plupart des cas, résoudra automatiquement le conflit.

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

    Dans ce cas, la priorité de classement déterminera que le classement du tmp.Col1 La colonne remplace le classement du littéral de chaîne (qui utilise le classement par défaut de la base de données "actuelle").

  2. Utilisez la clause COLLATE (comme vous le faites déjà). Il n'y a rien de mal à cette approche car c'est l'une des utilisations du mot clé COLLATE.

    -- 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
    

    Dans ces deux cas, le classement à utiliser est déterminé par le premier paramètre d'entrée, et celui-ci doit être explicitement défini sur le classement du deuxième paramètre (exemple inférieur) qui, dans ce cas, est tiré de la définition des colonnes. Ou, le deuxième paramètre doit être explicitement défini pour correspondre au classement du premier paramètre (exemple supérieur) qui, dans ce cas, est tiré de la valeur par défaut des bases de données car il s'agit d'un littéral de chaîne.

4
Solomon Rutzky

Si vous cherchez une solution rapide, alors COLLATE DATABASE_DEFAULT vous aidera à faire bouger les choses à nouveau.

1
pacreely