web-dev-qa-db-fra.com

Pourquoi la recherche de LIKE N '% �%' correspond-elle à un caractère Unicode et = N'� 'correspond-elle à plusieurs?

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'%�%'

retourne

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 .

24
Martin Smith

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:

  1. L'ordre/poids par défaut indiqué dans le allkeys.txt fichier (noté ci-dessous)
  2. 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?)
  3. Tout remplacement basé sur les paramètres régionaux.
  4. La version de la norme Unicode est utilisée.
  5. Le facteur "humain" (c'est-à-dire que l'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, 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.

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

12
Solomon Rutzky