Mengapa mencari LIKE N '% %' cocok dengan karakter Unicode dan = N' 'cocok dengan banyak?

21
DECLARE @T TABLE(
  Col NCHAR(1));

INSERT INTO @T
VALUES      (N'A'),
            (N'B'),
            (N'C'),
            (N'Ƕ'),
            (N'Ƿ'),
            (N'Ǹ');

SELECT *
FROM   @T
WHERE  Col LIKE N'%�%'

Kembali

Col
A
B
C
Ƕ
Ƿ
Ǹ

SELECT *
FROM   @T
WHERE  Col = N'�' 

Kembali

Col
Ƕ
Ƿ
Ǹ

Menghasilkan setiap "karakter" byte ganda yang mungkin dengan di bawah ini menunjukkan bahwa =versi tersebut cocok dengan 21.229 dari mereka dan LIKE N'%�%'versi semuanya (Saya sudah mencoba beberapa pemeriksaan non biner dengan hasil yang sama).

WITH T(I, N)
AS 
(
SELECT TOP 65536 ROW_NUMBER() OVER (ORDER BY @@SPID),
                 NCHAR(ROW_NUMBER() OVER (ORDER BY @@SPID))
FROM master..spt_values v1, 
     master..spt_values v2
)
SELECT I, N 
FROM T
WHERE N = N'�'  

Adakah yang bisa menjelaskan apa yang terjadi di sini?

Menggunakan COLLATE Latin1_General_BINkemudian cocok dengan karakter tunggal NCHAR(65533)- tetapi pertanyaannya adalah untuk memahami aturan apa yang digunakannya dalam kasus lain. Apa yang istimewa dari 21.229 karakter yang cocok dengan itu =dan mengapa semuanya cocok dengan wildcard? Saya kira ada beberapa alasan di balik itu bahwa saya hilang.

nchar(65534)[dan 21k lainnya] berfungsi sama baiknya nchar(65533). Pertanyaannya bisa saja diutarakan menggunakan nchar(502) sama seperti - itu berperilaku sama seperti LIKE N'%Ƕ%'(cocok dengan semuanya) dan dalam =kasus ini. Itu mungkin petunjuk yang cukup besar.

Mengubah SELECTkueri terakhir untuk SELECT I, N, RANK() OVER(ORDER BY N)menunjukkan bahwa SQL Server tidak dapat memberi peringkat karakter. Tampaknya setiap karakter yang tidak ditangani oleh pemeriksaan dianggap setara.

Basis data dengan Latin1_General_100_CS_ASsusunan menghasilkan 5840 kecocokan. Latin1_General_100_CS_ASmemotong =pertandingan cukup banyak, tetapi tidak mengubah LIKEperilaku. Tampaknya ada pot karakter yang semakin kecil dalam susunan selanjutnya yang semuanya sama dan diabaikan dalam LIKEpenelusuran wildcard .

Saya menggunakan SQL Server 2016. Simbol ini adalah karakter pengganti Unicode, tetapi satu-satunya karakter yang tidak valid dalam pengkodean UCS-2 adalah 55296 - 57343 AFAIK dan jelas cocok dengan titik kode yang benar-benar valid seperti N'Ԛ'yang tidak ada dalam kisaran ini.

Semua karakter ini berperilaku seperti string kosong untuk LIKEdan =. Mereka bahkan mengevaluasi sebagai setara. N'' = N'�'itu benar, dan Anda dapat menjatuhkannya dalam LIKEperbandingan ruang tunggal LIKE '_' + nchar(65533) + '_'tanpa efek. LENperbandingan menghasilkan hasil yang berbeda, jadi itu mungkin hanya fungsi string tertentu.

Saya pikir LIKEperilaku ini benar untuk kasus ini; itu berperilaku seperti nilai yang tidak diketahui (yang bisa berupa apa saja). Ini juga terjadi pada karakter lain ini:

  • nchar(11217) (Tanda Ketidakpastian)
  • nchar(65532) (Karakter Penggantian Objek)
  • nchar(65533) (Penggantian Karakter)
  • nchar(65534) (Bukan Karakter)

Jadi jika saya ingin menemukan semua karakter yang mewakili ketidakpastian dengan tanda yang sama saya akan menggunakan susunan yang mendukung karakter tambahan seperti Latin1_General_100_CI_AS_SC.

Saya kira ini adalah kelompok "karakter tidak berbobot" yang disebutkan dalam dokumentasi, Dukungan Kolasi dan Unicode .

Martin Smith
sumber

Jawaban:

9

Bagaimana satu "karakter" (yang dapat terdiri dari beberapa Poin Kode: pasangan pengganti, menggabungkan karakter, dll) dibandingkan dengan yang lain didasarkan pada seperangkat aturan yang agak rumit. Sangat rumit karena harus memperhitungkan semua berbagai (dan kadang-kadang "aneh") aturan yang ditemukan di semua bahasa yang diwakili dalam spesifikasi Unicode . Sistem ini berlaku untuk Collations non-biner untuk semua NVARCHARdata, dan untuk VARCHARdata yang menggunakan Windows Collation dan bukan SQL Server Collation (satu yang dimulai dengan SQL_). Sistem ini tidak berlaku untuk VARCHARdata yang menggunakan SQL Server Collation karena mereka menggunakan pemetaan sederhana.

Sebagian besar aturan didefinisikan dalam Algoritma Kolom Unicode (UCA) . Beberapa aturan itu, dan beberapa tidak tercakup dalam UCA, adalah:

  1. Pemesanan default / berat yang diberikan dalam allkeys.txtfile (tercantum di bawah)
  2. Sensitivitas dan opsi mana yang digunakan (mis. Apakah sensitif huruf atau tidak sensitif ?, dan jika sensitif, maka apakah huruf besar lebih dulu atau lebih rendah?)
  3. Setiap penimpaan berbasis lokal.
  4. Versi standar Unicode sedang digunakan.
  5. Faktor "manusia" (yaitu Unicode adalah spesifikasi, bukan perangkat lunak, dan dengan demikian diserahkan kepada masing-masing vendor untuk mengimplementasikannya)

Saya menekankan bahwa poin terakhir mengenai faktor manusia semoga menjelaskan bahwa kita tidak boleh berharap SQL Server untuk selalu berperilaku 100% sesuai dengan spesifikasi.

Faktor utama di sini adalah bobot yang diberikan untuk masing-masing Poin Kode, dan fakta bahwa beberapa Poin Kode dapat berbagi spesifikasi bobot yang sama. Anda dapat menemukan bobot dasar (tanpa pengesampingan khusus lokal) di sini (saya percaya 100seri Collations adalah Unicode v 5.0 - konfirmasi informal dalam komentar pada item Microsoft Connect ):

http://www.unicode.org/Public/UCA/5.0.0/allkeys.txt

Poin Kode dalam pertanyaan - U + FFFD - didefinisikan sebagai:

FFFD  ; [*0F12.0020.0002.FFFD] # REPLACEMENT CHARACTER

Notasi itu didefinisikan di bagian 9.1 Format File Allkeys dari UCA:

<entry>       := <charList> ';' <collElement>+ <eol>
<charList>    := <char>+
<collElement> := "[" <alt> <weight> "." <weight> "." <weight> ("." <weight>)? "]"
<alt>         := "*" | "."

Collation elements marked with a "*" are variable.

Baris terakhir itu penting karena Poin Kode yang kita lihat memiliki spesifikasi yang memang dimulai dengan "*". Di bagian 3.6 Pembobotan Variabel ada empat perilaku yang mungkin ditentukan, berdasarkan nilai konfigurasi Collation yang kami tidak memiliki akses langsung ke (ini adalah hard-coded ke dalam implementasi Microsoft setiap Collation, seperti apakah case-sensitive menggunakan huruf kecil terlebih dahulu atau huruf besar terlebih dahulu, properti yang berbeda antara VARCHARdata menggunakan SQL_Collations dan semua variasi lainnya).

Saya tidak punya waktu untuk melakukan penelitian penuh ke jalur mana yang diambil dan untuk menyimpulkan opsi mana yang digunakan sehingga bukti yang lebih solid dapat diberikan, tetapi aman untuk mengatakan bahwa dalam setiap spesifikasi Poin Kode, apakah sesuatu dianggap "sama" tidak akan selalu menggunakan spesifikasi lengkap. Dalam hal ini, kami memiliki "0F12.0020.0002.FFFD" dan kemungkinan besar hanya level 2 dan 3 yang digunakan (yaitu .0020.0002. ). Melakukan "Hitung" di Notepad ++ untuk ".0020.0002." menemukan 12.581 pertandingan (termasuk karakter tambahan yang belum kami tangani). Melakukan "Hitung" pada "[*" mengembalikan 4049 pertandingan. Melakukan RegEx "Cari" / "Hitung" menggunakan pola\[\*\d{4}\.0020\.0002mengembalikan 832 hasil. Jadi di suatu tempat dalam kombinasi ini, plus kemungkinan beberapa aturan lain yang tidak saya lihat, ditambah beberapa detail implementasi khusus Microsoft, adalah penjelasan lengkap tentang perilaku ini. Dan untuk menjadi jelas, perilakunya sama untuk semua karakter yang cocok karena mereka semua cocok satu sama lain karena mereka semua memiliki bobot yang sama begitu aturan diterapkan (artinya, pertanyaan ini bisa ditanyakan tentang salah satu dari mereka, bukan tentu Pak ).

Anda dapat melihat dengan kueri di bawah ini dan mengubah COLLATEklausa sesuai hasil di bawah kueri bagaimana berbagai sensitivitas bekerja di dua versi Collations:

;WITH cte AS
(
  SELECT     TOP (65536) ROW_NUMBER() OVER (ORDER BY (SELECT 0)) - 1 AS [Num]
  FROM       [master].sys.columns col
  CROSS JOIN [master].sys.objects obj
)
SELECT cte.Num AS [Decimal],
       CONVERT(VARBINARY(2), cte.Num) AS [Hex],
       NCHAR(cte.Num) AS [Character]
FROM   cte
WHERE  NCHAR(cte.Num) = NCHAR(0xFFFD) COLLATE Latin1_General_100_CS_AS_WS --N'�'
ORDER BY cte.Num;

Berbagai jumlah karakter yang cocok pada berbagai koleksi ada di bawah ini.

Latin1_General_100_CS_AS_WS   =   5840
Latin1_General_100_CS_AS      =   5841 (The "extra" character is U+3000)
Latin1_General_100_CI_AS      =   5841
Latin1_General_100_CI_AI      =   6311

Latin1_General_CS_AS_WS       = 21,229
Latin1_General_CS_AS          = 21,230
Latin1_General_CI_AS          = 21,230
Latin1_General_CI_AI          = 21,537

Dalam semua koleksi yang tercantum di atas N'' = N'�'juga mengevaluasi true.

MEMPERBARUI

Saya dapat melakukan sedikit riset lebih lanjut dan inilah yang saya temukan:

Bagaimana "mungkin" seharusnya bekerja

Dengan menggunakan ICU Collation Demo , saya mengatur lokal ke "en-US-u-va-posix", mengatur kekuatan menjadi "primer", memeriksa acara "mengurutkan kunci", dan menempelkan 4 karakter berikut yang saya salin dari hasil kueri di atas (menggunakan Latin1_General_100_CI_AICollation):

�
Ԩ
ԩ
Ԫ

dan itu mengembalikan:

Ԫ
    60 2E 02 .
Ԩ
    60 7A .
ԩ
    60 7A .
�
    FF FD .

Kemudian, periksa properti karakter untuk " " di http://unicode.org/cldr/utility/character.jsp?a=fffd dan lihat bahwa kunci sortir level 1 (yaitu FF FD) cocok dengan properti "uca". Mengklik pada properti "uca" itu akan membawa Anda ke halaman pencarian - http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%3Auca%3DFFFD%3A%5D - hanya menampilkan 1 pertandingan. Dan, dalam file allkeys.txt , berat jenis level 1 ditampilkan sebagai 0F12, dan hanya ada 1 kecocokan untuk itu.

Untuk memastikan bahwa kami menafsirkan perilaku dengan benar, saya melihat karakter lain: SURAT MODAL YUNANI OMICRON DENGAN VARIA di http://unicode.org/cldr/utility/character.jsp?a=1FF8 yang memiliki "uca" ( yaitu level 1 sortir berat / elemen penyusun) dari 5F30. Mengklik "5F30" membawa kita ke halaman pencarian - http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%3Auca%3D5F30%3A%5D - menampilkan 30 pertandingan, 20 dari mereka berada dalam kisaran 0 - 65535 (yaitu U + 0000 - U + FFFF). Mencari di file allkeys.txt untuk Code Point 1FF8 , kita melihat berat jenis level 1 12E0. Melakukan "Hitung" di Notepad ++ on12E0. memperlihatkan 30 pertandingan (ini cocok dengan hasil dari Unicode.org, meskipun tidak dijamin karena file tersebut untuk Unicode v 5.0 dan situs ini menggunakan data Unicode v 9.0).

Di SQL Server, kueri berikut mengembalikan 20 kecocokan, sama seperti pencarian Unicode.org saat menghapus 10 karakter tambahan:

;WITH cte AS
(
  SELECT TOP (65535) ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS [Num]
  FROM   [master].sys.columns col
  CROSS JOIN [master].sys.objects obj
)
SELECT cte.Num AS [Decimal],
       CONVERT(VARCHAR(50), CONVERT(VARBINARY(2), cte.Num), 2) AS [Hex],
       NCHAR(cte.Num) AS [Character]
FROM cte
WHERE NCHAR(cte.Num) = NCHAR(0x1FF8) COLLATE Latin1_General_100_CI_AI
ORDER BY cte.Num;

Dan, untuk memastikan, kembali ke halaman Demo ICU Collation, dan mengganti karakter di kotak "Input" dengan 3 karakter berikut diambil dari daftar 20 hasil dari SQL Server:


𝜪

menunjukkan bahwa mereka, memang, semua memiliki 5F 30bobot jenis level 1 yang sama (cocok dengan bidang "uca" pada halaman properti karakter).

JADI, sepertinya memang karakter khusus ini tidak cocok dengan yang lain.

Bagaimana cara kerjanya sebenarnya (setidaknya di Microsoft-land)

Tidak seperti dalam SQL Server, .NET memiliki sarana untuk menunjukkan kunci pengurutan untuk string melalui Metode CompareInfo.GetSortKey . Menggunakan metode ini dan meneruskan hanya karakter U + FFFD, ia mengembalikan semacam kunci 0x0101010100. Kemudian, ulangi semua karakter dalam kisaran 0 - 65535 untuk melihat mana yang memiliki kunci semacam 0x01010101004545 pertandingan yang dikembalikan. Ini tidak persis cocok dengan 5840 yang dikembalikan dalam SQL Server (saat menggunakan Latin1_General_100_CS_AS_WSCollation), tetapi ini adalah yang terdekat yang bisa kita dapatkan (untuk saat ini) mengingat bahwa saya menjalankan Windows 10 dan .NET Framework versi 4.6.1, yang menggunakan Unicode v 6.3.0 sesuai dengan bagan untuk Kelas CharUnicodeInfo(di "Catatan untuk Penelepon", di bagian "Keterangan"). Untuk saat ini saya menggunakan fungsi SQLCLR sehingga tidak dapat mengubah versi Kerangka target. Ketika saya mendapat kesempatan, saya akan membuat aplikasi konsol dan menggunakan versi Kerangka target 4,5 karena yang menggunakan Unicode v 5.0, yang harus cocok dengan 100 series Collations.

Apa yang ditunjukkan oleh tes ini adalah bahwa, bahkan tanpa jumlah yang sama persis antara .NET dan SQL Server untuk U + FFFD, cukup jelas bahwa ini bukan perilaku khusus SQL Server, dan apakah disengaja atau keliru dengan implementasi yang dilakukan oleh Microsoft, karakter U + FFFD memang cocok dengan beberapa karakter, bahkan jika tidak sesuai dengan spesifikasi Unicode. Dan, mengingat bahwa karakter ini cocok dengan U + 0000 (nol), itu mungkin hanya masalah bobot yang hilang.

JUGA

Mengenai perbedaan perilaku dalam =kueri vs LIKE N'%�%'kueri, itu ada hubungannya dengan wildcard dan bobot yang hilang (saya asumsikan) untuk � Ƕ Ƿ Ǹkarakter (yaitu ) ini. Jika LIKEkondisi diubah menjadi sederhana LIKE N'�'maka mengembalikan 3 baris yang sama dengan =kondisi. Jika masalah dengan wildcard bukan karena bobot "hilang" (tidak ada 0x00kunci pengurutan yang dikembalikan oleh CompareInfo.GetSortKey, btw) maka mungkin karena karakter ini berpotensi memiliki properti yang memungkinkan kunci sortir bervariasi berdasarkan konteks (yaitu karakter di sekitarnya ).

Solomon Rutzky
sumber
Terima kasih - di allkeys.txt ditautkan sepertinya tidak ada yang lain dengan bobot yang sama FFFD(mencari *0F12.0020.0002.FFFDhanya mengembalikan satu hasil). Dari pengamatan @ Forrest bahwa mereka semua cocok dengan string kosong dan sedikit lebih banyak membaca tentang subjek sepertinya berat yang mereka bagikan dalam berbagai non binary collations sebenarnya nol saya percaya.
Martin Smith
1
@MartinSmith Melakukan riset menggunakan ICU Collation Demo , dan memasukkan � A a \u24D0serta beberapa lainnya yang ada di set hasil pertandingan 5839. Sepertinya Anda tidak dapat melewati bobot pertama, dan char pengganti ini adalah satu-satunya yang dimulai dengan 0F12. Banyak yang lain juga memiliki bobot pertama yang unik, dan banyak yang hilang dari file allkey sepenuhnya. Jadi ini bisa menjadi bug implementasi karena kesalahan manusia. Saya memang melihat arang ini di grup "tidak didukung" di situs Unicode di bagan Collations mereka. Akan terlihat lebih banyak besok.
Solomon Rutzky
Rextester menggunakan 4.5. Sebenarnya saya melihat lebih sedikit kecocokan pada versi itu (3385). Mungkin saya menetapkan beberapa opsi berbeda untuk Anda? rextester.com/JBWIN31407
Martin Smith
BTW kunci semacam 01 01 01 01 00itu disebutkan di sini archives.miloush.net/michkap/archive/2007/09/10/4847780.html (seperti CompareInfo.InternalGetSortKeypanggilan LCMapStringEx)
Martin Smith
@ MartinSmith Saya bermain dengannya sedikit tetapi tidak yakin apa bedanya. OS yang dijalankan oleh .NET tidak memperhitungkan. Saya akan melihat lebih banyak besok jika saya punya waktu. Namun, terlepas dari jumlah kecocokan, setidaknya ini tampaknya mengkonfirmasi alasan perilaku tersebut, terutama sekarang karena kami memiliki wawasan tentang struktur kunci semacam ini berkat blog yang Anda tautkan dan beberapa orang lain yang tertaut di dalamnya. Halaman CharUnicodeInfo yang saya tautkan dengan menyebutkan panggilan Collation yang mendasarinya, yang merupakan dasar untuk saran saya di sini: connect.microsoft.com/SQLServer/feedback/details/2932336 :-)
Solomon Rutzky