Por que os dígitos não são COMO [0-9]?

13

O agrupamento padrão do meu servidor é Latin1_General_CI_AS, conforme determinado por esta consulta:

SELECT SERVERPROPERTY('Collation') AS Collation;

Fiquei surpreso ao descobrir que, com esse agrupamento, posso combinar caracteres que não são dígitos em cadeias usando o predicado LIKE '[0-9]'.

Por que no agrupamento padrão isso acontece? Não consigo pensar em um caso em que isso seria útil. Eu sei que posso solucionar o comportamento usando um agrupamento binário, mas parece uma maneira estranha de implementar o agrupamento padrão.

A filtragem de dígitos produz caracteres que não são de dígitos

Eu posso demonstrar o comportamento criando uma coluna que contém todos os valores possíveis de caracteres de byte único e filtrando os valores com o predicado de correspondência de dígitos.

A instrução a seguir cria uma tabela temporária com 256 linhas, uma para cada ponto de código na página de código atual:

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 linha contém o valor inteiro do ponto de código e o valor de caractere do ponto de código. Nem todos os valores de caracteres são exibidos - alguns dos pontos de código são estritamente caracteres de controle. Aqui está uma amostra seletiva da saída 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 ÿ

Eu esperaria poder filtrar na coluna Symbol para encontrar caracteres de dígito usando um predicado LIKE e especificando o intervalo de caracteres '0' a '9':

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

Produz uma saída surpreendente:

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 ¾

O conjunto de pontos de código de 48 a 57 são os que eu espero. O que me surpreende é que os símbolos para sobrescritos e frações também estão incluídos no conjunto de resultados!

Pode haver uma razão matemática para pensar em expoentes e frações como números, mas parece errado chamá-los de dígitos.

Usando agrupamento binário como solução alternativa

Entendo que, para obter o resultado esperado, posso forçar o agrupamento binário correspondente Latin1_General_BIN:

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

O conjunto de resultados inclui apenas os pontos 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 Elder
fonte

Respostas:

22

[0-9] não é algum tipo de expressão regular definida para corresponder apenas a dígitos.

Qualquer intervalo em um LIKEpadrão corresponde a caracteres entre os caracteres inicial e final, de acordo com a ordem de classificação dos agrupamentos.

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 

Devoluções

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

Portanto, você obtém esses resultados porque, no agrupamento padrão, esses caracteres são classificados depois, 0mas antes 9.

Parece que o agrupamento está definido para realmente classificá-los em ordem matemática com as frações na ordem correta entre 0e 1.

Você também pode usar um conjunto em vez de um intervalo. Para evitar a 2correspondência, ²você precisaria de um CSagrupamento

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

Latin1 é a página de código 1252, na qual 178 é 'SUPERSCRIPT DOIS' . Este é um sobrescrito Unicode : é o caractere "2" como sobrescrito . De acordo com o Padrão Técnico Unicode # 10, ele deve comparar igual a 2, consulte 8.1 Dobramento de agrupamento :

Mapeie equivalentes de compatibilidade (terciários), como caracteres de largura total e sobrescritos , a caracteres representativos

O erro seria se o sobrescrito 2 comparasse diferente de 2! Antes de você dizer 'mas minha coluna não é Unicode', tenha certeza: de acordo com o MSDN (consulte Windows Collations), toda a comparação e classificação de cadeias de caracteres são feitas de acordo com as regras Unicode, mesmo quando a representação em disco é CHAR.

Quanto aos outros caracteres no seu exemplo, like VULGAR FRACTION ONE QUARTERe similares, eles não se comparam a nenhum número, mas, como Mark já mostrou, eles se classificam corretamente entre 0 e 9.

E, é claro, se você mudasse a página de código, obteria resultados diferentes. Por exemplo. com Greek_CS_AS( página de código 1253 ), você obteria os caracteres com os códigos 178, 179 e 189.

Remus Rusanu
fonte