DECLARE @T TABLE(
Col NCHAR(1));
INSERT INTO @T
VALUES (N'A'),
(N'B'),
(N'C'),
(N'Ƕ'),
(N'Ƿ'),
(N'Ǹ');
SELECT *
FROM @T
WHERE Col LIKE N'%�%'
Col
A
B
C
Ƕ
Ƿ
Ǹ
SELECT *
FROM @T
WHERE Col = N'�'
Retour
Col
Ƕ
Ƿ
Ǹ
La génération de chaque "caractère" double octet possible avec ce qui suit montre que la version =
Correspond à 21 229 d'entre eux et la version LIKE N'%�%'
À tous (j'ai essayé quelques collations non binaires avec le même résultat).
WITH T(I, N)
AS
(
SELECT TOP 65536 ROW_NUMBER() OVER (ORDER BY @@SPID),
NCHAR(ROW_NUMBER() OVER (ORDER BY @@SPID))
FROM master..spt_values v1,
master..spt_values v2
)
SELECT I, N
FROM T
WHERE N = N'�'
Quelqu'un est-il capable de faire la lumière sur ce qui se passe ici?
L'utilisation de COLLATE Latin1_General_BIN
Correspond alors au caractère unique NCHAR(65533)
- mais la question est de comprendre quelles règles il utilise dans l'autre cas. Quelle est la particularité de ces 21 229 caractères qui correspondent au =
Et pourquoi tout correspond au caractère générique? Je suppose qu'il y a une raison derrière cela que je manque.
nchar(65534)
[et 21k autres] fonctionnent aussi bien que nchar(65533)
. La question aurait pu être formulée en utilisant nchar(502)
de la même manière que �
- elle se comporte de la même manière que LIKE N'%Ƕ%'
(Correspond à tout) et dans le cas =
. C'est probablement un assez gros indice.
La modification de SELECT
dans la dernière requête en SELECT I, N, RANK() OVER(ORDER BY N)
montre que SQL Server ne peut pas classer les caractères. Il semble que tout caractère non géré par le classement soit considéré comme équivalent.
Une base de données avec un classement Latin1_General_100_CS_AS
Produit 5840 correspondances. Latin1_General_100_CS_AS
Réduit considérablement les correspondances de =
, Mais ne modifie pas le comportement de LIKE
. Il semble qu'il y ait un pot de caractères qui est devenu plus petit dans les classements ultérieurs qui se comparent tous égaux et sont alors ignorés dans les recherches génériques LIKE
.
J'utilise SQL Server 2016. Le symbole �
Est le caractère de remplacement Unicode, mais les seuls caractères non valides dans le codage UCS-2 sont 55296 - 57343 AFAIK et il correspond clairement à des points de code parfaitement valides tels que - N'Ԛ'
qui ne sont pas dans cette plage.
Tous ces caractères se comportent comme la chaîne vide pour LIKE
et =
. Ils évaluent même comme équivalents. N'' = N'�'
Est vrai, et vous pouvez le déposer dans une comparaison LIKE
d'espaces simples LIKE '_' + nchar(65533) + '_'
sans effet. LEN
les comparaisons donnent des résultats différents, donc ce n'est probablement que certaines fonctions de chaîne.
Je pense que le comportement LIKE
est correct pour ce cas; il se comporte comme une valeur inconnue (qui pourrait être n'importe quoi). Cela arrive aussi pour ces autres personnages:
nchar(11217)
(signe d'incertitude)nchar(65532)
(caractère de remplacement d'objet)nchar(65533)
(caractère de remplacement)nchar(65534)
(Pas un caractère)Donc, si je veux trouver tous les caractères qui représentent l'incertitude avec le signe égal, j'utiliserais un classement qui prend en charge des caractères supplémentaires comme Latin1_General_100_CI_AS_SC
.
Je suppose que ce sont le groupe de "caractères non pondérés" mentionné dans la documentation, Collation and Unicode Support .
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 nicode . Ce système s'applique aux classements non binaires pour toutes les données NVARCHAR
et pour les données VARCHAR
qui utilisent un classement Windows et non un classement SQL Server (un commençant par SQL_
). Ce système ne s'applique pas aux données VARCHAR
utilisant un classement SQL Server car celles-ci utilisent des mappages simples.
La plupart des règles sont définies dans nicode Collation Algorithm (UCA) . Certaines de ces règles, et d'autres non couvertes par l'UCA, sont:
allkeys.txt
fichier (noté ci-dessous)J'ai souligné ce dernier point concernant le facteur humain pour, espérons-le, préciser qu'il ne faut 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 le 100
la série de classements est Unicode v 5.0 - confirmation informelle dans les commentaires sur le é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 Allkeys File Format 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 .6 Variable Weighting 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 le cas- sensitive utilise d'abord les minuscules ou les majuscules d'abord, une propriété différente entre les données VARCHAR
utilisant SQL_
Classements et toutes 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 très probablement ce ne sont que les niveaux 2 et 3 qui sont utilisés (ie . 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. 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 l'un d'eux, pas nécessairement M. �
).
Vous pouvez voir avec la requête ci-dessous et changer la clause COLLATE
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 N'' = N'�'
a également la valeur true.
J'ai pu faire un peu plus de recherche et voici ce que j'ai trouvé:
Comment cela devrait "probablement" fonctionner
En utilisant ICU Collation Demo , 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é dans ce qui suit 4 caractères que j'ai copiés à partir des 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 = - montrant seulement 1 match. Et, dans le fichier allkeys.txt , le poids de tri de niveau 1 est indiqué par 0F12
, et il n'y a qu'une seule correspondance pour cela.
Pour nous assurer que nous interprétons correctement le comportement, j'ai regardé un autre caractère: LETTRE MAJUSCULE GRECQUE OMICRON AVEC VARIA Ὸ
à http://unicode.org/cldr/utility/character.jsp?a=1FF8 qui a un "uca" (c'est-à-dire un poids de tri de niveau 1/un é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 - montrant 30 correspondances, dont 20 dans la plage 0 - 65535 (c'est-à-dire U + 0000 - U + FFFF). En regardant dans le fichier allkeys.txt pour Code Point 1FF8 , nous voyons un poids de tri de niveau 1 de 12E0
. Faire un "Count" dans Notepad ++ sur 12E0.
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, revenir à la page ICU Collation Demo, et remplacer les caractères dans la zone "Input" par les 3 caractères suivants tirés de la liste des 20 résultats de SQL Server :
Ὂ
????
Ὸ
montre qu'ils ont en effet tous les mêmes 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 caractère particulier devrait pas correspondre à autre chose.
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 passant uniquement le caractère U + FFFD, il retourne une clé de tri de 0x0101010100
. Ensuite, parcourez tous les caractères compris entre 0 et 65 535 pour voir lesquels ont une clé de tri de 0x0101010100
a renvoyé 4529 correspondances. Cela ne correspond pas exactement au 5840 renvoyé dans SQL Server (lors de l'utilisation de Latin1_General_100_CS_AS_WS
Collation), 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 pour le 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 c'est pas Comportement spécifique à SQL Server, et que, qu'il soit intentionnel ou par inadvertance avec l'implémentation effectuée par Microsoft, le caractère U + FFFD correspond en effet à quelques caractères, même s'il ne devrait pas être conforme à 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.
[~ # ~] également [~ # ~]
Concernant la différence de comportement dans le =
requête vs LIKE N'%�%'
requête, cela a à voir avec les caractères génériques et les poids manquants (je suppose) pour ces derniers (c'est-à-dire � Ƕ Ƿ Ǹ
) personnages. Si la condition LIKE
est remplacée par simplement LIKE N'�'
puis il retourne les 3 mêmes lignes que le =
état. 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 pourrait ê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).