web-dev-qa-db-fra.com

Pourquoi les non-chiffres sont-ils COMME [0-9]?

Le classement par défaut de mon serveur est Latin1_General_CI_AS, comme déterminé par cette requête:

SELECT SERVERPROPERTY('Collation') AS Collation;

J'ai été surpris de découvrir qu'avec ce classement, je peux faire correspondre des caractères non numériques dans des chaînes en utilisant le prédicat LIKE '[0-9]'.

Pourquoi dans le classement par défaut cela se produit-il? Je ne peux pas penser à un cas où cela serait utile. Je sais que je peux contourner le comportement à l'aide d'un classement binaire, mais cela semble être une étrange façon d'implémenter le classement par défaut.

Le filtrage des chiffres produit des caractères non numériques

Je peux démontrer le comportement en créant une colonne qui contient toutes les valeurs de caractères possibles sur un octet et en filtrant les valeurs avec le prédicat de correspondance de chiffres.

L'instruction suivante crée une table temporaire avec 256 lignes, une pour chaque point de code dans la page de codes actuelle:

WITH P0(_) AS (SELECT 0 UNION ALL SELECT 0),
P1(_) AS (SELECT 0 FROM P0 AS L CROSS JOIN P0 AS R),
P2(_) AS (SELECT 0 FROM P1 AS L CROSS JOIN P1 AS R),
P3(_) AS (SELECT 0 FROM P2 AS L CROSS JOIN P2 AS R),
Tally(Number) AS (
  SELECT -1 + ROW_NUMBER() OVER (ORDER BY (SELECT 0))
  FROM P3
)
SELECT Number AS CodePoint, CHAR(Number) AS Symbol
INTO #CodePage
FROM Tally
WHERE Number >= 0 AND Number <= 255;

Chaque ligne contient la valeur entière du point de code et la valeur de caractère du point de code. Toutes les valeurs de caractères ne sont pas affichables - certains des points de code sont strictement des caractères de contrôle. Voici un échantillon sélectif de la sortie de SELECT CodePoint, Symbol FROM #CodePage:

0   
1   
2   
...
32   
33  !
34  "
35  #
...
48  0
49  1
50  2
...
65  A
66  B
67  C
...
253 ý
254 þ
255 ÿ

Je m'attendrais à pouvoir filtrer sur la colonne Symbole pour trouver des caractères numériques en utilisant un prédicat LIKE et en spécifiant la plage de caractères de '0' à '9':

SELECT CodePoint, Symbol
FROM #CodePage
WHERE Symbol LIKE '[0-9]';

Il produit une sortie surprenante:

CodePoint   Symbol
48  0
49  1
50  2
51  3
52  4
53  5
54  6
55  7
56  8
57  9
178 ²
179 ³
185 ¹
188 ¼
189 ½
190 ¾

L'ensemble des points de code 48 à 57 sont ceux que j'attends. Ce qui me surprend, c'est que les symboles des exposants et des fractions sont également inclus dans le jeu de résultats!

Il peut y avoir une raison mathématique de considérer les exposants et les fractions comme des nombres, mais il semble erroné de les appeler des chiffres.

Utilisation du classement binaire comme solution de contournement

Je comprends que pour obtenir le résultat attendu, je peux forcer le classement binaire correspondant Latin1_General_BIN:

SELECT CodePoint, Symbol
FROM #CodePage
WHERE Symbol LIKE '[0-9]' COLLATE Latin1_General_BIN;

L'ensemble de résultats comprend uniquement les points de code 48 à 57:

CodePoint   Symbol
48  0
49  1
50  2
51  3
52  4
53  5
54  6
55  7
56  8
57  9
13

[0-9] n'est pas un type d'expression régulière défini pour correspondre uniquement aux chiffres.

Toute plage dans un modèle LIKE fait correspondre les caractères entre le caractère de début et de fin selon l'ordre de tri du classement.

SELECT CodePoint,
       Symbol,
       RANK() OVER (ORDER BY Symbol COLLATE Latin1_General_CI_AS) AS Rnk
FROM   #CodePage
WHERE  Symbol LIKE '[0-9]' COLLATE Latin1_General_CI_AS
ORDER  BY Symbol COLLATE Latin1_General_CI_AS 

Retour

CodePoint            Symbol Rnk
-------------------- ------ --------------------
48                   0      1
188                  ¼      2
189                  ½      3
190                  ¾      4
185                  ¹      5
49                   1      5
50                   2      7
178                  ²      7
179                  ³      9
51                   3      9
52                   4      11
53                   5      12
54                   6      13
55                   7      14
56                   8      15
57                   9      16

Vous obtenez donc ces résultats car sous votre classement par défaut, ces caractères sont triés après 0 mais avant 9.

Il semble que le classement soit défini pour les trier réellement dans l'ordre mathématique avec les fractions dans l'ordre correct entre 0 et 1.

Vous pouvez également utiliser un ensemble plutôt qu'une plage. Éviter 2 correspondant à ² vous auriez besoin d'un classement CS

SELECT CodePoint, Symbol
FROM #CodePage
WHERE Symbol LIKE '[0123456789]' COLLATE Latin1_General_CS_AS
22
Martin Smith

Latin1 est la page de code 1252, dans laquelle 178 est 'SUPERSCRIPT TWO' . Il s'agit d'un Unicode exposant : est le caractère "2" en exposant . Selon la norme technique Unicode n ° 1 il doit être égal à 2, voir 8.1 Pliage du classement :

Mappez les équivalents (tertiaires) de compatibilité tels que les caractères pleine largeur et en exposant , avec des caractères représentatifs

Le bogue serait que l'exposant 2 soit différent de 2! Avant de dire "mais ma colonne n'est pas Unicode", rassurez-vous: selon MSDN (voir les classements Windows), toutes les comparaisons de chaînes et le tri sont effectués conformément à la Règles Unicode, même lorsque la représentation sur disque est CHAR.

Quant aux autres personnages de votre exemple, comme VULGAR FRACTION ONE QUARTER et similaires, ils ne se comparent pas à n'importe quel nombre, mais, comme Mark l'a déjà montré, ils trient correctement entre 0 et 9.

Et, bien sûr, si vous changez la page de codes, vous obtiendrez des résultats différents. Par exemple. avec Greek_CS_AS ( page de codes 125 ) vous obtiendrez les caractères avec les codes 178, 179 et 189.

6
Remus Rusanu