La façon dont un «caractère» (qui peut être composé de plusieurs points de code: paires de substitution, combinaison de caractères, etc.) se compare à un autre est basée sur un ensemble de règles assez complexes. Il est si complexe en raison de la nécessité de prendre en compte toutes les différentes règles (et parfois "farfelues") trouvées dans toutes les langues représentées dans la spécification Unicode . Ce système s'applique aux classements non binaires pour toutes les NVARCHAR
données et pour les VARCHAR
données qui utilisent un classement Windows et non un classement SQL Server (un commençant par SQL_
). Ce système ne s'applique pas aux VARCHAR
données utilisant un classement SQL Server car celles-ci utilisent des mappages simples.
La plupart des règles sont définies dans l' algorithme de classement Unicode (UCA) . Certaines de ces règles, et d'autres non couvertes par l'UCA, sont:
- La commande / poids par défaut indiquée dans le
allkeys.txt
fichier (notée ci-dessous)
- Quelles sont les sensibilités et les options utilisées (par exemple, est-elle sensible à la casse ou insensible?, Et si elle est sensible, est-elle d'abord en majuscules ou en minuscules en premier?)
- Tout remplacement basé sur les paramètres régionaux.
- La version de la norme Unicode est utilisée.
- Le facteur «humain» (c.-à-d. Unicode est une spécification, pas un logiciel, et est donc laissé à chaque fournisseur pour l'implémenter)
J'ai souligné ce dernier point concernant le facteur humain pour, espérons-le, indiquer clairement que l'on ne devrait pas s'attendre à ce que SQL Server se comporte toujours à 100% selon les spécifications.
Le facteur primordial ici est la pondération donnée à chaque point de code et le fait que plusieurs points de code peuvent partager la même spécification de poids. Vous pouvez trouver les poids de base (pas de remplacements spécifiques aux paramètres régionaux) ici (je crois que la 100
série de classements est Unicode v 5.0 - confirmation informelle dans les commentaires sur l' élément Microsoft Connect ):
http://www.unicode.org/Public/UCA/5.0.0/allkeys.txt
Le point de code en question - U + FFFD - est défini comme:
FFFD ; [*0F12.0020.0002.FFFD] # REPLACEMENT CHARACTER
Cette notation est définie dans la section 9.1 Format de fichier Allkeys de l'UCA:
<entry> := <charList> ';' <collElement>+ <eol>
<charList> := <char>+
<collElement> := "[" <alt> <weight> "." <weight> "." <weight> ("." <weight>)? "]"
<alt> := "*" | "."
Collation elements marked with a "*" are variable.
Cette dernière ligne est importante car le point de code que nous examinons a une spécification qui commence en effet par "*". Dans la section 3.6 Pondération variable, il y a quatre comportements possibles définis, basés sur les valeurs de configuration du classement auxquelles nous n'avons pas d'accès direct (ceux-ci sont codés en dur dans l'implémentation Microsoft de chaque classement, par exemple, si la distinction majuscules / minuscules utilise d'abord les minuscules ou majuscule d'abord, une propriété qui est différente entre les VARCHAR
données utilisant des SQL_
classements et toutes les autres variantes).
Je n'ai pas le temps de faire des recherches complètes sur les chemins empruntés et de déduire quelles options sont utilisées de telle sorte qu'une preuve plus solide puisse être donnée, mais il est sûr de dire que dans chaque spécification de Point de Code, que quelque chose soit ou non quelque chose est considéré comme "égal" ne va pas toujours utiliser la spécification complète. Dans ce cas, nous avons "0F12.0020.0002.FFFD" et il est très probable que seuls les niveaux 2 et 3 soient utilisés (par exemple, 0020.0002. ). Faire un "Count" dans Notepad ++ pour ".0020.0002." trouve 12 581 correspondances (y compris des caractères supplémentaires que nous n'avons pas encore traités). Faire un "Count" sur "[*" renvoie 4049 correspondances. Faire un RegEx "Find" / "Count" en utilisant un modèle de\[\*\d{4}\.0020\.0002
renvoie 832 correspondances. Donc, quelque part dans cette combinaison, plus éventuellement d'autres règles que je ne vois pas, ainsi que des détails d'implémentation spécifiques à Microsoft, est l'explication complète de ce comportement. Et pour être clair, le comportement est le même pour tous les caractères correspondants car ils se correspondent tous car ils ont tous le même poids une fois que les règles sont appliquées (ce qui signifie que cette question aurait pu être posée à propos de n'importe lequel d'entre eux, pas nécessairement M. �
).
Vous pouvez voir avec la requête ci-dessous et modifier la COLLATE
clause selon les résultats sous la requête comment les différentes sensibilités fonctionnent sur les deux versions de Collations:
;WITH cte AS
(
SELECT TOP (65536) ROW_NUMBER() OVER (ORDER BY (SELECT 0)) - 1 AS [Num]
FROM [master].sys.columns col
CROSS JOIN [master].sys.objects obj
)
SELECT cte.Num AS [Decimal],
CONVERT(VARBINARY(2), cte.Num) AS [Hex],
NCHAR(cte.Num) AS [Character]
FROM cte
WHERE NCHAR(cte.Num) = NCHAR(0xFFFD) COLLATE Latin1_General_100_CS_AS_WS --N'�'
ORDER BY cte.Num;
Les différents décomptes de caractères correspondants à différents classements sont ci-dessous.
Latin1_General_100_CS_AS_WS = 5840
Latin1_General_100_CS_AS = 5841 (The "extra" character is U+3000)
Latin1_General_100_CI_AS = 5841
Latin1_General_100_CI_AI = 6311
Latin1_General_CS_AS_WS = 21,229
Latin1_General_CS_AS = 21,230
Latin1_General_CI_AS = 21,230
Latin1_General_CI_AI = 21,537
Dans tous les classements répertoriés ci-dessus, la valeur est N'' = N'�'
également vraie.
MISE À JOUR
J'ai pu faire un peu plus de recherche et voici ce que j'ai trouvé:
Comment cela devrait "probablement" fonctionner
En utilisant la démonstration de classement ICU , j'ai défini les paramètres régionaux sur "en-US-u-va-posix", défini la force sur "primaire", vérifié les "clés de tri", puis collé les 4 caractères suivants que j'ai copiés à partir du résultats de la requête ci-dessus (en utilisant le Latin1_General_100_CI_AI
classement):
�
Ԩ
ԩ
Ԫ
et cela renvoie:
Ԫ
60 2E 02 .
Ԩ
60 7A .
ԩ
60 7A .
�
FF FD .
Ensuite, vérifiez les propriétés des caractères pour " " sur http://unicode.org/cldr/utility/character.jsp?a=fffd et vérifiez que la clé de tri de niveau 1 (c'est-à-dire FF FD
) correspond à la propriété "uca". En cliquant sur cette propriété "uca", vous accédez à une page de recherche - http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%3Auca%3DFFFD%3A%5D - affichant une seule correspondance. Et, dans le fichier allkeys.txt , le poids de tri de niveau 1 est affiché comme 0F12
, et il n'y a qu'une seule correspondance pour cela.
Pour nous assurer que nous interprétons le comportement correctement, j'ai regardé un autre personnage: LETTRE MAJUSCULE GRECQUE OMICRON AVEC VARIA Ὸ
à http://unicode.org/cldr/utility/character.jsp?a=1FF8 qui a un "uca" ( c'est-à-dire le poids de tri de niveau 1 / élément d'assemblage) de 5F30
. Cliquer sur ce "5F30" nous amène à une page de recherche - http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%3Auca%3D5F30%3A%5D - affichant 30 correspondances, dont 20 sur elles étant comprises entre 0 et 65535 (c'est-à-dire U + 0000 - U + FFFF). En regardant dans le fichier allkeys.txt pour le point de code 1FF8 , nous voyons un poids de tri de niveau 1 12E0
. Faire un "Count" dans Notepad ++ sur12E0.
affiche 30 correspondances (cela correspond aux résultats d'Unicode.org, bien que cela ne soit pas garanti car le fichier est pour Unicode v 5.0 et le site utilise des données Unicode v 9.0).
Dans SQL Server, la requête suivante renvoie 20 correspondances, identiques à la recherche Unicode.org lors de la suppression des 10 caractères supplémentaires:
;WITH cte AS
(
SELECT TOP (65535) ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS [Num]
FROM [master].sys.columns col
CROSS JOIN [master].sys.objects obj
)
SELECT cte.Num AS [Decimal],
CONVERT(VARCHAR(50), CONVERT(VARBINARY(2), cte.Num), 2) AS [Hex],
NCHAR(cte.Num) AS [Character]
FROM cte
WHERE NCHAR(cte.Num) = NCHAR(0x1FF8) COLLATE Latin1_General_100_CI_AI
ORDER BY cte.Num;
Et, juste pour être sûr, de revenir à la page de démonstration du classement ICU et de remplacer les caractères de la zone "Entrée" par les 3 caractères suivants, extraits de la liste des 20 résultats de SQL Server:
Ὂ
𝜪
Ὸ
montre qu'ils ont tous, en effet, le même 5F 30
poids de tri de niveau 1 (correspondant au champ "uca" sur la page de propriétés du personnage).
Donc, il semble certainement que ce personnage particulier ne devrait correspondre à rien d'autre.
Comment cela fonctionne réellement (au moins dans Microsoft-land)
Contrairement à SQL Server, .NET permet d'afficher la clé de tri d'une chaîne via la méthode CompareInfo.GetSortKey . En utilisant cette méthode et en ne passant que le caractère U + FFFD, il renvoie une clé de tri de 0x0101010100
. Ensuite, en itérant sur tous les caractères compris entre 0 et 65 535 pour voir lesquels avaient une clé de tri de 0x0101010100
4529 correspondances retournées. Cela ne correspond pas exactement au 5840 renvoyé dans SQL Server (lors de l'utilisation du Latin1_General_100_CS_AS_WS
classement), mais c'est le plus proche que nous pouvons obtenir (pour l'instant) étant donné que j'utilise Windows 10 et .NET Framework version 4.6.1, qui utilise Unicode v 6.3.0 selon le graphique de la classe CharUnicodeInfo(dans "Note aux appelants", dans la section "Remarques"). Pour le moment, j'utilise une fonction SQLCLR et je ne peux donc pas changer la version cible du Framework. Lorsque j'en aurai l'occasion, je créerai une application console et utiliserai une version de Framework cible de 4.5 car elle utilise Unicode v 5.0, qui devrait correspondre aux classements de la série 100.
Ce que ce test montre, c'est que, même sans le même nombre exact de correspondances entre .NET et SQL Server pour U + FFFD, il est assez clair que ce n'est pas un comportement spécifique à SQL Server, et que ce soit intentionnel ou de surveillance avec la mise en œuvre effectuée par Microsoft, le caractère U + FFFD correspond en effet à pas mal de caractères, même s'il ne devrait pas selon la spécification Unicode. Et, étant donné que ce caractère correspond à U + 0000 (null), il s'agit probablement simplement d'un problème de poids manquants.
AUSSI
En ce qui concerne la différence de comportement dans la =
requête par rapport à la LIKE N'%�%'
requête, cela a à voir avec les caractères génériques et les poids manquants (je suppose) pour ces caractères (c'est-à-dire � Ƕ Ƿ Ǹ
). Si la LIKE
condition est remplacée par simplement, LIKE N'�'
elle renvoie les 3 mêmes lignes que la =
condition. Si le problème avec les caractères génériques n'est pas dû à des poids "manquants" (il n'y a pas de 0x00
clé de tri renvoyée par CompareInfo.GetSortKey
, btw), cela peut être dû au fait que ces caractères ont potentiellement une propriété qui permet à la clé de tri de varier en fonction du contexte (c'est-à-dire des caractères environnants ).
FFFD
(la recherche de*0F12.0020.0002.FFFD
ne renvoie qu'un seul résultat). D'après l'observation de @ Forrest qu'ils correspondent tous à la chaîne vide et un peu plus de lecture sur le sujet, il semble que le poids qu'ils partagent dans les différents classements non binaires soit en fait nul je crois.