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

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

Retour

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 LIKE N'%�%'version tous (j'ai essayé quelques classements 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 COLLATE Latin1_General_BINcorrespond 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 à =et pourquoi tout correspond-il 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) également as - elle se comporte de la même manière que LIKE N'%Ƕ%'(correspond à tout) et dans le =cas. C'est probablement un indice assez important.

La modification de la SELECTdans la dernière requête SELECT I, N, RANK() OVER(ORDER BY N)indique 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 Latin1_General_100_CS_ASclassement produit 5840 correspondances. Latin1_General_100_CS_ASréduit considérablement les =correspondances, mais ne change pas le LIKEcomportement. Il semble qu'il y ait un pot de caractères qui a diminué dans les classements ultérieurs qui se comparent tous égaux et qui sont alors ignorés dans les LIKErecherches génériques .

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 ceux N'Ԛ'qui ne se trouvent pas dans cette plage.

Tous ces caractères se comportent comme la chaîne vide pour LIKEet =. Ils évaluent même comme équivalents. N'' = N'�'est vrai, et vous pouvez le supprimer dans une LIKEcomparaison d'espaces individuels LIKE '_' + nchar(65533) + '_'sans effet. LENles comparaisons donnent cependant des résultats différents, il ne s'agit donc probablement que de certaines fonctions de chaîne.

Je pense que le LIKEcomportement 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 personnage)

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, le classement et le support Unicode .

Martin Smith
la source

Réponses:

9

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 NVARCHARdonnées et pour les VARCHARdonné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 VARCHARdonné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:

  1. La commande / poids par défaut indiquée dans le allkeys.txtfichier (notée 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.-à-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 100sé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 VARCHARdonné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\.0002renvoie 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 COLLATEclause 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_AIclassement):

�
Ԩ
ԩ
Ԫ

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 30poids 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 0x01010101004529 correspondances retournées. Cela ne correspond pas exactement au 5840 renvoyé dans SQL Server (lors de l'utilisation du Latin1_General_100_CS_AS_WSclassement), 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 LIKEcondition 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 0x00clé 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 ).

Solomon Rutzky
la source
Merci - dans le lien allkeys.txt, il semble que rien d'autre n'ait le même poids que FFFD(la recherche de *0F12.0020.0002.FFFDne 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.
Martin Smith
1
@MartinSmith A fait des recherches en utilisant ICU Collation Demo , et en mettant � A a \u24D0et quelques autres qui étaient dans le jeu de résultats 5839 correspondances. Il semble que vous ne puissiez pas sauter le premier poids, et ce caractère de remplacement est le seul à commencer 0F12. Beaucoup d'autres avaient également un premier poids unique, et beaucoup manquaient complètement dans le fichier allkeys. Cela pourrait donc être un bogue d'implémentation dû à une erreur humaine. J'ai vu ce caractère dans le groupe "non pris en charge" sur le site Unicode dans leurs classements. Sera plus demain.
Solomon Rutzky
Rextester utilise 4.5. Je vois en fait moins de correspondances sur cette version (3385). Peut-être que je mets une option différente de vous? rextester.com/JBWIN31407
Martin Smith
BTW dont la clé de tri 01 01 01 01 00est mentionnée ici archives.miloush.net/michkap/archive/2007/09/10/4847780.html (ressemble à des CompareInfo.InternalGetSortKeyappels LCMapStringEx)
Martin Smith
@MartinSmith J'ai joué un peu avec, mais je ne sais pas encore quelle est la différence. Le système d'exploitation sur lequel .NET fonctionne prend en compte. Je chercherai plus demain si j'ai le temps. Quel que soit le nombre de correspondances, cela semble au moins confirmer la raison du comportement, surtout maintenant que nous avons un aperçu de la structure des clés de tri grâce au blog auquel vous avez lié et à d'autres qui y sont liés. La page CharUnicodeInfo à laquelle j'ai lié mentionne les appels de classement sous-jacents, qui est la base de ma suggestion ici: connect.microsoft.com/SQLServer/feedback/details/2932336 :-)
Solomon Rutzky