Mengapa non-digit LIKE [0-9]?

13

Susunan standar server saya adalah Latin1_General_CI_AS, sebagaimana ditentukan oleh kueri ini:

SELECT SERVERPROPERTY('Collation') AS Collation;

Saya terkejut menemukan bahwa dengan susunan ini saya dapat mencocokkan karakter non-digit dalam string menggunakan predikat LIKE '[0-9]'.

Mengapa dalam susunan standar apakah ini terjadi? Saya tidak bisa memikirkan kasus di mana ini akan berguna. Saya tahu saya bisa mengatasi perilaku menggunakan binary collation, tetapi sepertinya cara yang aneh untuk mengimplementasikan collation default.

Memfilter digit menghasilkan karakter non-digit

Saya bisa mendemonstrasikan perilaku dengan membuat kolom yang berisi semua kemungkinan nilai karakter byte tunggal dan memfilter nilai dengan predikat pencocokan digit.

Pernyataan berikut membuat tabel sementara dengan 256 baris, satu untuk setiap titik kode di halaman kode saat ini:

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;

Setiap baris berisi nilai integer dari titik kode, dan nilai karakter dari titik kode. Tidak semua nilai karakter dapat ditampilkan - beberapa titik kode sepenuhnya mengontrol karakter. Berikut adalah contoh selektif dari output 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 ÿ

Saya berharap dapat memfilter pada kolom Simbol untuk menemukan karakter digit menggunakan predikat LIKE dan menentukan rentang karakter '0' hingga '9':

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

Ini menghasilkan output yang mengejutkan:

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 ¾

Himpunan poin kode 48 hingga 57 adalah yang saya harapkan. Yang mengejutkan saya adalah simbol untuk superskrip dan fraksi juga termasuk dalam set hasil!

Mungkin ada alasan matematis untuk menganggap eksponen dan pecahan sebagai angka, tetapi tampaknya salah menyebut angka.

Menggunakan binary collation sebagai solusinya

Saya mengerti bahwa untuk mendapatkan hasil yang saya harapkan, saya bisa memaksa pemeriksaan biner yang sesuai Latin1_General_BIN:

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

Set hasil hanya mencakup poin kode 48 hingga 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
sumber

Jawaban:

22

[0-9] bukan beberapa jenis ekspresi reguler yang didefinisikan hanya untuk mencocokkan digit.

Kisaran dalam a LIKE pola cocok dengan karakter antara karakter awal dan akhir sesuai dengan susunan pengurutan.

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 

Kembali

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

Jadi Anda mendapatkan hasil ini karena di bawah susunan default Anda, karakter-karakter ini diurutkan setelahnya 0 tetapi sebelumnya 9.

Tampaknya seolah-olah collation didefinisikan untuk benar-benar mengurutkan mereka dalam urutan matematika dengan fraksi dalam urutan yang benar 0 dan 1.

Anda juga bisa menggunakan satu set daripada rentang. Untuk menghindari 2kecocokan, ²Anda akan memerlukan CScollation

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

Latin1 adalah kode halaman 1252, di mana 178 adalah 'SUPERSCRIPT TWO' . Ini adalah superscript Unicode : adalah karakter "2" sebagai superscript . Menurut Standar Teknis Unicode # 10 , harus sama dengan 2, lihat 8.1 Collation Folding :

Setara dengan kompatibilitas peta (tersier), seperti karakter full-width dan superscript , untuk karakter yang representatif

Bugnya adalah jika superscript 2 akan berbeda dari 2! Sebelum Anda mengatakan 'tetapi kolom saya bukan Unicode', yakinlah: menurut MSDN (lihat Windows Collations) semua perbandingan string dan penyortiran dilakukan sesuai dengan aturan Unicode, bahkan ketika representasi pada disk CHAR.

Adapun karakter lain dalam contoh Anda, suka VULGAR FRACTION ONE QUARTERdan sejenisnya mereka tidak membandingkan sama dengan angka apa pun, tetapi, seperti yang sudah ditunjukkan Mark, mereka mengurutkan dengan benar antara 0 dan 9.

Dan, tentu saja, jika Anda akan mengubah halaman kode Anda akan mendapatkan hasil yang berbeda. Misalnya. dengan Greek_CS_AS( kode halaman 1253 ) Anda akan mendapatkan karakter dengan kode 178, 179 dan 189.

Remus Rusanu
sumber