¿Por qué los no dígitos son como [0-9]?

13

La clasificación predeterminada de mi servidor es Latin1_General_CI_AS, según lo determinado por esta consulta:

SELECT SERVERPROPERTY('Collation') AS Collation;

Me sorprendió descubrir que con esta clasificación puedo unir caracteres que no sean dígitos en cadenas usando el predicado LIKE '[0-9]'.

¿Por qué en la clasificación predeterminada sucede esto? No puedo pensar en un caso en el que esto sea útil. Sé que puedo evitar el comportamiento utilizando una intercalación binaria, pero parece una forma extraña de implementar la intercalación predeterminada.

Filtrar dígitos produce caracteres que no son dígitos

Puedo demostrar el comportamiento creando una columna que contiene todos los valores posibles de caracteres de un solo byte y filtrando los valores con el predicado de coincidencia de dígitos.

La siguiente instrucción crea una tabla temporal con 256 filas, una para cada punto de código en la página de códigos actual:

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;

Cada fila contiene el valor entero del punto de código y el valor de carácter del punto de código. No todos los valores de caracteres son visualizables; algunos de los puntos de código son estrictamente caracteres de control. Aquí hay una muestra selectiva de la salida 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 ÿ

Esperaría poder filtrar en la columna Símbolo para encontrar caracteres de dígitos usando un predicado LIKE y especificando el rango de caracteres '0' a '9':

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

Produce una salida sorprendente:

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 ¾

El conjunto de puntos de código 48 a 57 son los que espero. ¡Lo que me sorprende es que los símbolos para superíndices y fracciones también se incluyen en el conjunto de resultados!

Puede haber una razón matemática para pensar en exponentes y fracciones como números, pero parece incorrecto llamarlos dígitos.

Usar la intercalación binaria como solución alternativa

Entiendo que para obtener el resultado que espero, puedo forzar la colación binaria correspondiente Latin1_General_BIN:

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

El conjunto de resultados incluye solo los puntos de código 48 a 57:

CodePoint   Symbol
48  0
49  1
50  2
51  3
52  4
53  5
54  6
55  7
56  8
57  9
Iain Samuel McLean Élder
fuente

Respuestas:

22

[0-9] no es un tipo de expresión regular definida para coincidir solo con dígitos.

Cualquier rango en un LIKEpatrón hace coincidir los caracteres entre el carácter inicial y final según el orden de clasificación.

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 

Devoluciones

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

Entonces obtienes estos resultados porque bajo tu intercalación predeterminada estos caracteres se ordenan después 0pero antes 9.

Parece que la intercalación está definida para clasificarlos realmente en orden matemático con las fracciones en el orden correcto entre 0y 1.

También podría usar un conjunto en lugar de un rango. Para evitar 2coincidencias ², necesitaría una CSintercalación

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

Latin1 es la página de códigos 1252, en la que 178 es 'SUPERSCRIPT TWO' . Este es un superíndice Unicode : es el carácter "2" como superíndice . De acuerdo con el Estándar Técnico # 10 de Unicode , debe ser igual a 2, ver 8.1 Plegado de clasificación :

Mapee equivalentes (terciarios) de compatibilidad, como caracteres de ancho completo y superíndice , a caracteres representativos

¡El error sería si el superíndice 2 fuera diferente de 2! Antes de decir 'pero mi columna no es Unicode', puede estar seguro: de acuerdo con MSDN (consulte las intercalaciones de Windows), toda la comparación y clasificación de cadenas se realiza de acuerdo con las reglas de Unicode, incluso cuando la representación en disco es CHAR.

En cuanto a los otros caracteres en su ejemplo, like VULGAR FRACTION ONE QUARTERy like no se comparan igual a ningún número, pero, como Mark ya mostró, se ordenan correctamente entre 0 y 9.

Y, por supuesto, si cambiara la página de códigos obtendría resultados diferentes. P.ej. con Greek_CS_AS( página de códigos 1253 ) obtendría los caracteres con los códigos 178, 179 y 189.

Remus Rusanu
fuente