Dlaczego cyfry niepodobne do LIKE [0–9]?

13

Domyślnym zestawieniem mojego serwera jest Latin1_General_CI_AS, zgodnie z tym zapytaniem:

SELECT SERVERPROPERTY('Collation') AS Collation;

Byłem zaskoczony, gdy odkryłem, że dzięki temu zestawieniu mogę dopasowywać znaki nie będące cyframi w ciągach znaków, używając predykatu LIKE '[0-9]'.

Dlaczego w domyślnym zestawieniu tak się dzieje? Nie mogę wymyślić przypadku, w którym byłoby to przydatne. Wiem, że mogę obejść to zachowanie za pomocą sortowania binarnego, ale wydaje się to dziwnym sposobem na wdrożenie domyślnego sortowania.

Filtrowanie cyfr powoduje powstanie znaków innych niż cyfry

Mogę zademonstrować to zachowanie, tworząc kolumnę zawierającą wszystkie możliwe jednobajtowe wartości znaków i filtrując wartości za pomocą predykatu dopasowującego cyfry.

Poniższa instrukcja tworzy tymczasową tabelę z 256 wierszami, po jednym dla każdego punktu kodowego na bieżącej stronie kodowej:

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;

Każdy wiersz zawiera wartość całkowitą punktu kodowego oraz wartość znakową punktu kodowego. Nie wszystkie wartości znaków są wyświetlane - niektóre punkty kodowe są znakami ściśle kontrolującymi. Oto wybiórcza próbka wyników 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 ÿ

Oczekiwałbym, że będę mógł filtrować kolumnę Symbol, aby znaleźć cyfry przy użyciu predykatu LIKE i określając zakres znaków od „0” do „9”:

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

Daje zaskakującą wydajność:

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 ¾

Zestaw punktów kodowych od 48 do 57 to te, których oczekuję. Zaskakuje mnie to, że symbole dla indeksu górnego i ułamków są również zawarte w zestawie wyników!

Może istnieć matematyczny powód, by wykładniki i ułamki traktować jak liczby, ale nazywanie ich cyframi wydaje się niewłaściwe.

Korzystanie z sortowania binarnego jako obejścia

Rozumiem, że aby uzyskać oczekiwany wynik, mogę wymusić odpowiednie sortowanie binarne Latin1_General_BIN:

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

Zestaw wyników zawiera tylko punkty kodowe 48 do 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
źródło

Odpowiedzi:

22

[0-9] nie jest rodzajem wyrażenia regularnego zdefiniowanego tak, aby pasowało do cyfr.

Dowolny zakres we LIKEwzorcu dopasowuje znaki między początkowym i końcowym znakiem zgodnie z porządkiem sortowania sortowania.

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 

Zwroty

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

Otrzymujesz więc te wyniki, ponieważ w domyślnym układzie sortowania znaki te są sortowane później, 0ale wcześniej 9.

Wygląda na to, że sortowanie jest zdefiniowane tak, aby faktycznie sortować je w kolejności matematycznej z ułamkami we właściwej kolejności między 0i 1.

Możesz także użyć zestawu zamiast zakresu. Aby uniknąć 2dopasowania ², potrzebujesz CSsortowania

SELECT CodePoint, Symbol
FROM #CodePage
WHERE Symbol LIKE '[0123456789]' COLLATE Latin1_General_CS_AS
Martin Smith
źródło
6

Latin1 to strona kodowa 1252, w której 178 to „SUPERSCRIPT TWO” . To jest indeks górny Unicode : to znak „2” jako indeks górny . Zgodnie ze standardem technicznym Unicode nr 10 powinien on być równy 2, patrz 8.1 Składanie zestawień :

Ekwiwalenty kompatybilności map (trzeciorzędowe), takie jak znaki o pełnej szerokości i indeks górny , do reprezentatywnych znaków

Błąd polegałby na tym, że indeks górny 2 byłby inny niż 2! Zanim powiesz „ale moja kolumna nie jest Unicode”, zapewniamy: zgodnie z MSDN (patrz Windows Collations) wszystkie porównania i sortowanie ciągów odbywa się zgodnie z regułami Unicode, nawet jeśli reprezentacja na dysku ma CHAR.

Jeśli chodzi o inne znaki w twoim przykładzie, znaki podobne VULGAR FRACTION ONE QUARTERi podobne nie są równe żadnej liczbie, ale, jak już pokazał Mark, sortują odpowiednio od 0 do 9.

I oczywiście, jeśli zmienisz stronę kodową, uzyskasz inne wyniki. Na przykład. z Greek_CS_AS( strona kodowa 1253 ) otrzymasz znaki o kodach 178, 179 i 189.

Remus Rusanu
źródło