La comparaison du même caractère dans VARCHAR et NVARCHAR diffère entre CP1 / CP1252 et CP850 en fonction du classment DB

Voici mes deux variables:

DECLARE @First VARCHAR(254) = '5'-Phosphate Analogs Freedom to Operate' DECLARE @Second NVARCHAR(254) = CONVERT(NVARCHAR(254), @First) 

J'ai deux bases de données, appelons-les "Database1" et "Database2". Database1 a un classment par défaut de SQL_Latin1_General_CP850_CI_AS ; Database2 est SQL_Latin1_General_CP1_CI_AS . Les deux bases de données ont un niveau de compatibilité de SQL Server 2008 (100) .

Je me connecte d'abord à Database1 et exécute les requêtes suivantes:

 SELECT CASE WHEN @First COLLATE SQL_Latin1_General_CP1_CI_AS = @Second COLLATE SQL_Latin1_General_CP1_CI_AS THEN 'Equal' ELSE 'Not Equal' END SELECT CASE WHEN @First COLLATE SQL_Latin1_General_CP850_CI_AS = @Second COLLATE SQL_Latin1_General_CP850_CI_AS THEN 'Equal' ELSE 'Not Equal' END 

Les résultats sont:

 Equal Equal 

Ensuite, je me connecte à Database2 et exécute les requêtes; les résultats sont:

 Equal Not Equal 

Notez que je n'ai pas changé les requêtes elles-mêmes, seulement la connection db, et je spécifie les classments à utiliser plutôt que de leur permettre d'utiliser les classments par défaut des bases de données. Par conséquent, je crois comprendre que le classment par défaut de la database ne devrait pas avoir d'importance, c'est-à-dire que les résultats des requêtes devraient être les mêmes, quelle que soit la database à laquelle je suis connecté.

J'ai trois questions:

  1. Pourquoi ai-je des résultats différents lorsque la seule chose que je change est la database à laquelle je suis connecté, étant donné que j'ai ignoré le classment par défaut de la database en spécifiant explicitement le mien?

  2. Pour le test sur Database 2, pourquoi la comparaison réussit-elle avec le classment SQL_Latin1_General_CP1_CI_AS et échoue avec le classment SQL_Latin1_General_CP850_CI_AS ? Quelle est la différence entre les deux classments qui expliquent cela?

  3. Le plus perplexe: Si le classment par défaut de la database à laquelle je suis connecté est important, comme il semblerait, et le classment par défaut de Database1 est SQL_Latin1_General_CP850_CI_AS (qui, callbackez-vous de mon premier test a abouti à Equal , Equal ) pourquoi la seconde requête, qui spécifie explicitement le même classment échouer ( Not Equal ) lors de la connection à Database2?

Simplement parce que c'est ainsi que fonctionnent datatables non-Unicode. Les données non Unicode (c'est-à-dire Extended ASCII 8 bits) utilisent les mêmes caractères pour les 128 premières valeurs, mais des caractères différents pour le second jeu de 128 caractères, en fonction de la page de codes. Le caractère que vous testez – ' – existe dans le code Page 1252 mais pas dans la page de code 850.

Oui, le classment par défaut de la database "actuelle" est absolument important pour les littéraux de string et les variables locales. Lorsque vous êtes dans une database avec un classment par défaut qui utilise la page de code 850, ce littéral de string non Unicode (c'est-à-dire une string qui n'est pas préfixée par N ) convertit automatiquement la valeur en un équivalent existant dans la page de code. ce caractère existe en effet dans la page de code 1252, il n'est donc pas nécessaire de le convertir.

Alors pourquoi est-ce que "n'est pas égal" quand dans une database en utilisant un classment associé à Cod Page 1252 entre la string non-Unicode et la string Unicode? Parce que lors de la conversion de la string non-Unicode en Unicode, une autre conversion a lieu qui traduit le caractère en sa valeur Unicode vrai, qui est supérieure à la valeur décimale 256.

Exécutez ce qui suit dans les deux bases de données et vous verrez ce qui se passe:

 SELECT ASCII(''') AS [AsciiValue], UNICODE(''') AS [CodePoint]; SELECT ASCII(''' COLLATE SQL_Latin1_General_CP1_CI_AS) AS [AsciiValue], UNICODE(''' COLLATE SQL_Latin1_General_CP1_CI_AS) AS [CodePoint]; SELECT ASCII(''' COLLATE SQL_Latin1_General_CP850_CI_AS) AS [AsciiValue], UNICODE(''' COLLATE SQL_Latin1_General_CP850_CI_AS) AS [CodePoint]; 

Résultats lorsque la database "en cours" utilise un classment associé à la page de codes 850 (les trois requêtes renvoient la même chose):

 AsciiValue CodePoint 39 39 

Comme vous pouvez le voir à partir de ce qui précède, spécifier COLLATE sur un littéral de string est après le fait de la façon dont cette string a déjà été interprétée par rapport au classment par défaut de la database "en cours".

Résultats lorsque la database "en cours" utilise un classment associé à la page de code 1252:

 -- no COLLATE clause AsciiValue CodePoint 146 8217 -- COLLATE SQL_Latin1_General_CP1_CI_AS AsciiValue CodePoint 146 8217 -- COLLATE SQL_Latin1_General_CP850_CI_AS AsciiValue CodePoint 39 39 

Mais pourquoi la conversion de 146 à 8217 si le personnage est disponible dans le code Page 1252? Parce que les premiers 256 caractères dans Unicode ne sont pas la Page de code 1252, mais sont plutôt ISO-8859-1 . Ces deux pages de code sont généralement les mêmes, mais diffèrent par plusieurs caractères dans la gamme 128 – 255. Dans la page de code ISO-8859-1, ces valeurs sont des caractères de contrôle. Microsoft a jugé préférable de ne pas gaspiller 16 caractères (ou autant de caractères) sur les caractères de contrôle non imprimables lorsque la limite était déjà de 256 caractères. Ils ont donc échangé les caractères de contrôle pour des caractères plus utilisables, et donc Code Page 1252. Mais le groupe Unicode a utilisé la norme ISO-8859-1 pour les 256 premiers caractères.

Pourquoi est-ce important? Parce que le caractère que vous testez est l'un des rares chanceux qui est dans Code Page 1252 mais pas dans ISO-8859-1, par conséquent, il ne peut pas restr 146 lorsqu'il est converti en NVARCHAR, et est traduit à sa valeur Unicode, qui est 8217 . Vous pouvez voir ce comportement en exécutant les opérations suivantes:

 SELECT '~' + CHAR(146) + '~', N'~' + NCHAR(146) + N'~'; -- ~'~ ~~ 

Tout ce qui est expliqué ci-dessus explique la plupart des comportements observés, mais n'explique pas pourquoi @First et @Second , lorsque spécifié avec COLLATE SQL_Latin1_General_CP850_CI_AS mais s'exécutant dans une database ayant un classment par défaut associé au code Page 1252, s'save comme "Non égal". Si l'utilisation de la page de codes les traduit en ASCII 39, ils devraient toujours être égaux, n'est-ce pas?

Cela est dû à la fois à la séquence d'events et au fait que les pages de codes ne sont pas pertinentes pour datatables Unicode (c'est-à-dire tout contenu stocké dans NCHAR , NVARCHAR et le type NTEXT obsolète que personne ne devrait utiliser). Briser ce qui se passe:

  1. Commencez par déclarer et initialiser DECLARE @First VARCHAR(1) = '''; (c'est-à-dire DECLARE @First VARCHAR(1) = '''; ). C'est un type VARCHAR , utilisant ainsi une page de code, et donc utilisant la page de code associée au classment par défaut de la database "courante".
  2. Le classment par défaut de la database "en cours" est associé à la page de code 1252, donc cette valeur n'est pas traduite en ASCII 39, mais existe heureusement en ASCII 146.
  3. Next @Second est déclaré et initialisé (ie DECLARE @Second NVARCHAR(1) = @First; – pas besoin de CONVERT explicite car ce n'est pas du code de production et il sera converti implicitement). C'est un type NVARCHAR qui, comme nous l'avons vu, a le caractère, mais convertit la valeur de ASCII 146 en code U + 2019 (Decimal 8217 = 0x2019).
  4. Dans la comparaison, l'utilisation de @First COLLATE SQL_Latin1_General_CP850_CI_AS commence par ASCII 146 car @First est une donnée VARCHAR utilisant la page de code spécifiée par le @First par défaut de la database "actuelle". Mais alors, comme ce caractère n'existe pas dans la page de code (comme spécifié par le classment utilisé dans la clause COLLATE ), il est traduit en ASCII 39 (comme nous l'avons vu plus haut).
  5. Pourquoi @Second COLLATE SQL_Latin1_General_CP850_CI_AS n'a-t-il pas également traduit ce caractère en ASCII 39 afin qu'ils puissent s'inscrire comme "Equal"? Car:

    • @Second est NVARCHAR et n'utilise pas de pages de codes car tous les caractères sont représentés dans un jeu de caractères unique (c'est-à-dire Unicode). Ainsi, la modification du classment ne peut que modifier les règles régissant la façon de comparer et de sortinger les caractères, mais ne modifiera pas les caractères tels que ce qui se passe parfois lorsque vous modifiez le classment des données VARCHAR (comme dans le cas de ' ). Par conséquent, ce côté de la comparaison est toujours le sharepoint code U + 2019.
    • @First , être VARCHAR sera implicitement converti en NVARCHAR pour la comparaison. MAIS, le caractère ' avait déjà été traduit en ASCII 39 par la clause COLLATE SQL_Latin1_General_CP850_CI_AS , et ASCII 39 est trouvé en Unicode dans la même position, soit comme Decimal 39 ou Code Point U + 0027 (de SELECT CONVERT(BINARY(2), 39) ).

    La comparaison résultante est entre: Code Point U + 2019 et Code Point U + 0027
    Ergo: Pas égal