PESANAN OLEH dan perbandingan untaian huruf dan angka

9

Kita perlu melakukan beberapa pelaporan pada nilai-nilai yang biasanya dicampur string angka dan huruf yang perlu diurutkan 'secara alami'. Hal-hal seperti, misalnya "P7B18" atau "P12B3". @ String sebagian besar akan menjadi urutan huruf kemudian angka bergantian. Namun, jumlah segmen ini dan panjang masing-masing dapat bervariasi.

Kami ingin bagian numerik ini diurutkan dalam urutan numerik. Jelas, jika saya hanya menangani nilai-nilai string secara langsung dengan ORDER BY, maka "P12B3" akan datang sebelum "P7B18", karena "P1" lebih awal dari "P7", tetapi saya ingin sebaliknya, karena "P7" secara alami mendahului "P12".

Saya juga ingin dapat melakukan perbandingan jarak, misalnya @bin < 'P13S6'atau semacamnya. Saya tidak harus menangani floating point atau angka negatif; ini akan menjadi bilangan bulat non-negatif yang kita hadapi. Panjang string dan jumlah segmen berpotensi arbitrer, tanpa batas atas yang tetap.

Dalam kasus kami, casing string tidak penting, meskipun jika ada cara untuk melakukan ini dengan cara sadar-collation, orang lain mungkin menemukan itu berguna. Bagian terburuk dari semua ini adalah saya ingin dapat melakukan keduanya memesan, dan berbagai penyaringan dalam WHEREklausa.

Jika saya melakukan ini dalam C #, itu akan menjadi tugas yang cukup sederhana: melakukan parsing untuk memisahkan alfa dari angka, mengimplementasikan IComparable, dan pada dasarnya Anda sudah selesai. SQL Server, tentu saja, tampaknya tidak menawarkan fungsionalitas serupa, setidaknya sejauh yang saya ketahui.

Adakah yang tahu ada trik bagus untuk membuat ini bekerja? Apakah ada sedikit kemampuan yang dipublikasikan untuk membuat tipe CLR khusus yang mengimplementasikan IComparable dan melakukan hal ini seperti yang diharapkan? Saya juga tidak menentang Trik XML Bodoh (lihat juga: daftar concatenation), dan saya punya fungsi pencocokan / ekstraksi / penggantian pembungkus CLR yang tersedia di server juga.

EDIT: Sebagai contoh yang sedikit lebih rinci, saya ingin data berperilaku seperti ini.

SELECT bin FROM bins ORDER BY bin

bin
--------------------
M7R16L
P8RF6JJ
P16B5
PR7S19
PR7S19L
S2F3
S12F0

yaitu memecah string menjadi token semua huruf atau semua angka, dan mengurutkannya baik secara alfabet atau numerik, dengan token paling kiri menjadi istilah pengurutan yang paling signifikan. Seperti yang saya sebutkan, sepotong kue di. NET jika Anda mengimplementasikan IComparable, tapi saya tidak tahu bagaimana (atau jika) Anda dapat melakukan hal semacam itu di SQL Server. Ini jelas bukan sesuatu yang pernah saya temui dalam 10 tahun atau lebih bekerja dengannya.

db2
sumber
Anda bisa melakukan ini dengan beberapa jenis kolom yang dihitung terindeks, mengubah string menjadi integer. Jadi P7B12bisa menjadi P 07 B 12, lalu (via ASCII) 80 07 65 12, jadi80076512
Philᵀᴹ
Saya sarankan Anda membuat kolom terhitung yang membungkus setiap komponen numerik dengan panjang yang besar (yaitu 10 nol). Karena formatnya cukup arbitrer, Anda memerlukan ekspresi inline yang cukup besar tetapi bisa dilakukan. Kemudian Anda dapat mengindeks / memesan berdasarkan / di mana pada kolom sebanyak yang Anda suka.
Nick.McDermaid
Silakan lihat tautan yang baru saja saya tambahkan ke bagian atas jawaban saya :)
Solomon Rutzky
1
@srutzky Bagus, saya memilih itu.
db2
Hai db2: karena Microsoft pindah dari Hubungkan ke UserVoice dan tidak benar-benar menjaga penghitungan suara (mereka memasukkannya dalam komentar tetapi tidak yakin mereka melihatnya), Anda mungkin perlu memilih kembali untuk itu: Mendukung "penyortiran alami" / DIGITSASNUMBERS sebagai opsi Collation . Terima kasih!
Solomon Rutzky

Jawaban:

8

Ingin cara yang masuk akal, efisien menyortir angka dalam string sebagai angka aktual? Pertimbangkan memilih untuk saran Microsoft Connect saya: Mendukung "penyortiran alami" / DIGITSASNUMBERS sebagai opsi Kolasi


Tidak ada cara yang mudah dan terintegrasi untuk melakukan ini, tetapi ada kemungkinan:

Normalisasikan string dengan memformatnya kembali ke segmen dengan panjang tetap:

  • Buat kolom semacam ketik VARCHAR(50) COLLATE Latin1_General_100_BIN2. Panjang maksimal 50 mungkin perlu disesuaikan berdasarkan jumlah segmen maksimum dan panjang maksimum potensial mereka.
  • Sementara normalisasi dapat dilakukan di lapisan aplikasi lebih efisien, menangani ini dalam database menggunakan T-SQL UDF akan memungkinkan untuk menempatkan skalar UDF ke AFTER [or FOR] INSERT, UPDATEPemicu sehingga Anda dijamin dengan benar mengatur nilai untuk semua catatan, bahkan mereka datang melalui permintaan ad hoc, dll. Tentu saja, UDF skalar itu juga dapat ditangani melalui SQLCLR, tetapi perlu diuji untuk menentukan mana yang sebenarnya lebih efisien. **
  • UDF (terlepas dari T-SQL atau SQLCLR) harus:
    • Memproses jumlah segmen yang tidak diketahui dengan membaca setiap karakter dan berhenti ketika jenis beralih dari alpha ke numeric atau numeric ke alpha.
    • Per setiap segmen itu harus mengembalikan string dengan panjang tetap yang disetel ke karakter / digit maksimum yang mungkin dari segmen mana pun (atau mungkin maks + 1 atau 2 untuk memperhitungkan pertumbuhan di masa depan).
    • Segmen alfa harus dibenarkan kiri dan diisi dengan spasi.
    • Segmen numerik harus dibenarkan-kanan dan diisi dengan kiri dengan nol.
    • Jika karakter alfa dapat masuk sebagai case campuran tetapi urutannya harus case-insensitive, terapkan UPPER()fungsi pada hasil akhir dari semua segmen (sehingga hanya perlu dilakukan sekali dan tidak per segmen). Ini akan memungkinkan penyortiran yang tepat mengingat susunan biner dari kolom sortir.
  • Buat AFTER INSERT, UPDATEPemicu pada tabel yang memanggil UDF untuk mengatur kolom sortir. Untuk meningkatkan kinerja, gunakan UPDATE()fungsi untuk menentukan apakah kolom kode ini bahkan dalam SETklausa UPDATEpernyataan (hanya RETURNjika salah), dan kemudian bergabung dengan INSERTEDdan DELETEDtabel pseudo pada kolom kode untuk hanya memproses baris yang memiliki perubahan dalam nilai kode . Pastikan untuk menentukan COLLATE Latin1_General_100_BIN2kondisi GABUNG tersebut untuk memastikan keakuratan dalam menentukan apakah ada perubahan.
  • Buat Indeks pada kolom sortir baru.

Contoh:

P7B18   -> "P     000007B     000018"
P12B3   -> "P     000012B     000003"
P12B3C8 -> "P     000012B     000003C     000008"

Dalam pendekatan ini, Anda dapat mengurutkan melalui:

ORDER BY tbl.SortColumn

Dan Anda dapat melakukan pemfilteran rentang melalui:

WHERE tbl.SortColumn BETWEEN dbo.MyUDF('P7B18') AND dbo.MyUDF('P12B3')

atau:

DECLARE @RangeStart VARCHAR(50),
        @RangeEnd VARCHAR(50);
SELECT @RangeStart = dbo.MyUDF('P7B18'),
       @RangeEnd = dbo.MyUDF('P12B3');

WHERE tbl.SortColumn BETWEEN @RangeStart AND @RangeEnd

Baik ORDER BYdan WHEREfilter harus menggunakan pemeriksaan biner yang ditentukan SortColumnkarena Colled Precedence.

Perbandingan kesetaraan masih akan dilakukan pada kolom nilai asli.


Pikiran lain:

  • Gunakan SQLCLR UDT. Ini mungkin bisa berhasil, meskipun tidak jelas apakah ini menyajikan keuntungan bersih dibandingkan dengan pendekatan yang dijelaskan di atas.

    Ya, SQLCLR UDT dapat membuat operator pembandingnya ditimpa dengan algoritme khusus. Ini menangani situasi di mana nilainya dibandingkan dengan nilai lain yang sudah jenis kustom yang sama, atau yang perlu dikonversi secara implisit. Ini harus menangani filter rentang dalam WHEREkondisi.

    Berkenaan dengan mengurutkan UDT sebagai jenis kolom biasa (bukan kolom yang dihitung), ini hanya mungkin jika UDT adalah "byte dipesan". Menjadi "byte dipesan" berarti bahwa representasi biner dari UDT (yang dapat didefinisikan dalam UDT) secara alami mengurutkan dalam urutan yang sesuai. Dengan asumsi bahwa representasi biner ditangani mirip dengan pendekatan yang dijelaskan di atas untuk kolom VARCHAR (50) yang memiliki segmen panjang tetap yang diisi, yang akan memenuhi syarat. Atau, jika tidak mudah untuk memastikan bahwa representasi biner secara alami akan dipesan dengan cara yang tepat, Anda bisa mengekspos metode atau properti UDT yang menghasilkan nilai yang akan dipesan dengan benar, dan kemudian membuat PERSISTEDkolom yang dikomputasi pada metode atau properti. Metode harus deterministik dan ditandai sebagai IsDeterministic = true.

    Manfaat dari pendekatan ini adalah:

    • Tidak perlu bidang "nilai asli".
    • Tidak perlu memanggil UDF untuk memasukkan data atau membandingkan nilai. Dengan asumsi bahwa Parsemetode UDT mengambil P7B18nilai dan mengubahnya, maka Anda harus dapat dengan mudah memasukkan nilai-nilai secara alami P7B18. Dan dengan metode konversi implisit yang diatur dalam UDT, kondisi WHERE juga memungkinkan untuk menggunakan P7B18` saja.

    Konsekuensi dari pendekatan ini adalah:

    • Cukup memilih bidang akan mengembalikan representasi biner, jika menggunakan byte yang dipesan UDT sebagai datatype kolom. Atau jika menggunakan PERSISTEDkolom yang dihitung pada properti atau metode UDT, maka Anda akan mendapatkan representasi dikembalikan oleh properti atau metode. Jika Anda menginginkan nilai asli P7B18, maka Anda perlu memanggil metode atau properti UDT yang dikodekan untuk mengembalikan representasi itu. Karena Anda tetap harus mengganti ToStringmetode, itu adalah kandidat yang baik untuk menyediakan ini.
    • Tidak jelas (setidaknya bagi saya saat ini karena saya belum menguji bagian ini) seberapa mudah / sulit akan membuat perubahan pada representasi biner. Mengubah representasi yang tersimpan dan dapat disortir mungkin perlu dijatuhkan dan ditambahkan kembali bidang. Juga, menjatuhkan Majelis yang berisi UDT akan gagal jika digunakan dengan cara apa pun, jadi Anda ingin memastikan bahwa tidak ada yang lain di Majelis selain UDT ini. Anda dapat ALTER ASSEMBLYmengganti definisi, tetapi ada beberapa batasan untuk itu.

      Di sisi lain, VARCHAR()bidang adalah data yang terputus dari algoritma sehingga hanya perlu memperbarui kolom. Dan jika ada puluhan juta baris (atau lebih) maka itu dapat dilakukan dalam pendekatan batch.

  • Terapkan perpustakaan ICU yang sebenarnya memungkinkan untuk melakukan pengurutan alfanumerik ini. Meskipun sangat fungsional, perpustakaan hanya tersedia dalam dua bahasa: C / C ++ dan Java. Yang berarti Anda mungkin perlu melakukan beberapa tweak untuk membuatnya berfungsi di Visual C ++, atau ada kemungkinan kode Java dapat dikonversi ke MSIL menggunakan IKVM . Ada satu atau dua proyek sampingan .NET yang ditautkan di situs itu yang menyediakan antarmuka COM yang dapat diakses dalam kode yang dikelola, tetapi saya percaya mereka belum diperbarui dalam beberapa saat dan saya belum mencobanya. Taruhan terbaik di sini adalah menangani ini di lapisan aplikasi dengan tujuan menghasilkan kunci sortir. Kunci sortir kemudian akan disimpan ke dalam kolom sortir baru.

    Ini mungkin bukan pendekatan yang paling praktis. Namun, masih sangat keren bahwa kemampuan seperti itu ada. Saya memberikan walk-through yang lebih rinci dari contoh ini dalam jawaban berikut:

    Apakah ada susunan untuk menyortir string berikut dalam urutan berikut 1,2,3,6,10,10A, 10B, 11?

    Tetapi pola yang dibahas dalam pertanyaan itu sedikit lebih sederhana. Untuk contoh yang menunjukkan bahwa jenis pola yang ditangani dalam Pertanyaan ini juga berfungsi, silakan kunjungi halaman berikut:

    Demo Pengumpulan ICU

    Di bawah "Pengaturan", atur opsi "numerik" ke "pada" dan semua yang lain harus diatur ke "default". Selanjutnya, di sebelah kanan tombol "sortir", hapus centang opsi untuk "kekuatan diff" dan centang opsi untuk "kunci sortir". Kemudian ganti daftar item dalam area teks "Input" dengan daftar berikut:

    P12B22
    P7B18
    P12B3
    as456456hgjg6786867
    P7Bb19
    P7BA19
    P7BB19
    P007B18
    P7Bb20
    P7Bb19z23

    Klik tombol "sortir". Area teks "Output" akan menampilkan yang berikut:

    as456456hgjg6786867
        29 4D 0F 7A EA C8 37 35 3B 35 0F 84 17 A7 0F 93 90 , 0D , , 0D .
    P7B18
        47 0F 09 2B 0F 14 , 08 , FD F1 , DC C5 DC 05 .
    P007B18
        47 0F 09 2B 0F 14 , 08 , FD F1 , DC C5 DC 05 .
    P7BA19
        47 0F 09 2B 29 0F 15 , 09 , FD FF 10 , DC C5 DC DC 05 .
    P7Bb19
        47 0F 09 2B 2B 0F 15 , 09 , FD F2 , DC C5 DC 06 .
    P7BB19
        47 0F 09 2B 2B 0F 15 , 09 , FD FF 10 , DC C5 DC DC 05 .
    P7Bb19z23
        47 0F 09 2B 2B 0F 15 5B 0F 19 , 0B , FD F4 , DC C5 DC 08 .
    P7Bb20
        47 0F 09 2B 2B 0F 16 , 09 , FD F2 , DC C5 DC 06 .
    P12B3
        47 0F 0E 2B 0F 05 , 08 , FD F1 , DC C5 DC 05 .
    P12B22
        47 0F 0E 2B 0F 18 , 08 , FD F1 , DC C5 DC 05 .

    Harap dicatat bahwa kunci pengurutan adalah struktur dalam beberapa bidang, dipisahkan oleh koma. Setiap bidang perlu diurutkan secara independen, sehingga menyajikan masalah kecil lain untuk dipecahkan jika perlu mengimplementasikan ini dalam SQL Server.


** Jika ada kekhawatiran tentang kinerja terkait penggunaan Fungsi Buatan Pengguna, harap dicatat bahwa pendekatan yang diusulkan memanfaatkannya secara minimal. Bahkan, alasan utama untuk menyimpan nilai yang dinormalisasi adalah untuk menghindari memanggil UDF per setiap baris setiap permintaan. Dalam pendekatan utama, UDF digunakan untuk mengatur nilai SortColumn, dan itu hanya dilakukan pada INSERTdan UPDATEmelalui Pemicu. Memilih nilai jauh lebih umum daripada menyisipkan dan memperbarui, dan beberapa nilai tidak pernah diperbarui. Untuk setiap SELECTkueri yang menggunakan SortColumnfilter rentang untuk dalam WHEREklausa, UDF hanya diperlukan satu kali per masing-masing nilai range_start dan range_end untuk mendapatkan nilai yang dinormalisasi; UDF tidak disebut per-baris.

Sehubungan dengan UDT, penggunaannya sebenarnya sama dengan UDF skalar. Berarti, memasukkan dan memperbarui akan memanggil metode normalisasi sekali per setiap baris untuk menetapkan nilai. Kemudian, metode normalisasi akan dipanggil sekali per kueri per setiap range_start dan range_value dalam filter rentang, tetapi tidak per baris.

Suatu titik yang mendukung penanganan normalisasi sepenuhnya dalam SQLCLR UDF adalah bahwa mengingat tidak melakukan akses data apa pun dan bersifat deterministik, jika ditandai sebagai IsDeterministic = true, maka ia dapat berpartisipasi dalam rencana paralel (yang mungkin membantu INSERTdan UPDATEberoperasi) sedangkan T-SQL UDF akan mencegah rencana paralel digunakan.

Solomon Rutzky
sumber