Oracle mengurutkan kolom varchar2 dengan karakter khusus yang terakhir

8

Bagaimana saya bisa menyortir kolom Oracle a Varchar2 atau NVarchar2 agar sesuai pesanan saya sendiri. Atau apakah ada opsi yang ada yang akan mengutamakan huruf, lalu angka, lalu semua karakter khusus.

Pendekatan pertama kami menggunakan fungsi yang melakukan pemetaan karakter secara manual ke angka.

select id, sorted_column
from some_table
order FN_SPECIAL_SORT_KEY(sorted_column,'asc')

Fungsi sortasi khusus memetakan setiap karakter ke angka 2 digit, dan nilai kembali digunakan untuk menyortir. Ini tampaknya hanya gabungan yang sangat mahal, dan rasanya salah.

        for i in 1..length(sorted_text)
        loop
            v_result:=v_result ||  case substr(sorted_text,i,1)
                WHEN ' '   THEN 82 WHEN  '!'   THEN 81 WHEN '"'    THEN 80 WHEN  '#'   THEN 79 WHEN  '$'
                ..............
                WHEN 'u'   THEN 15 WHEN  'U'   THEN 15 WHEN  'v'   THEN 14 WHEN  'V'   THEN 14 WHEN  'w'   THEN 13 WHEN  'W'   THEN 13 WHEN  'x'
                ....
                else 90 end;
        end loop;

Saya mengalami kesulitan untuk datang dengan pendekatan alternatif. Saya ingin tahu masalah apa yang ada dengan pendekatan ini. Mungkin kita tidak punya alternatif.

Tambahan 1:

Menambahkan contoh data yang diurutkan. Secara umum, semua huruf alfa tidak peka huruf besar, kemudian angka 0-9, kemudian karakter khusus dalam urutan apa pun.

Berikut adalah contoh daftar ascending yang diurutkan. Perlu diingat karakter khusus dapat dipertukarkan, mereka semua harus setelah huruf dan angka. Dalam pengurutan biner, beberapa karakter khusus sebelum huruf (yaitu ')

Pesanan saya yang diinginkan,

AB1 $
aCC #
ac '
BZ

Urutan biner Oracle

AB1 $
BZ
ac '
acc #

Andy
sumber

Jawaban:

5

Jika urutan yang ingin Anda tentukan sudah didukung oleh Oracle, Anda bisa melakukan ini dengan memesan oleh fungsi NLSSORT - seperti:

ORDER BY NLSSORT(sorted_column, 'NLS_SORT = XDanish') -- Replace XDanish as appropriate

Anda dapat menemukan daftar pesanan sortir yang didukung di sini .


sumber
Karena ini berkaitan dengan kasus dan diakritik apakah benar-benar ada yang akan berhasil dalam kasus ini?
Leigh Riffel
5

Beberapa opsi:

  1. Tahan versi data Anda yang diurutkan ke tabel melalui pemicu, dan gunakan.

  2. Gunakan Oracle Locale Builder untuk membuat pesanan penyortiran khusus. (Peringatan: Saya belum pernah menggunakan ini, jadi saya tidak tahu apa yang mungkin ada di sana.) Anda kemudian dapat menggunakan fungsi NLSSORT dengan urutan pengurutan khusus itu.

Adam Musch
sumber
4

Pendekatan lain adalah menambahkan indeks berbasis fungsi pada FN_SPECIAL_SORT_KEY(sorted_column,'asc'). Hindari perlunya kolom + pemicu tambahan, dan Anda tidak perlu mengubah kueri Anda.

Jeffrey Kemp
sumber
4

Berdasarkan uraian Anda, sepertinya TRANSLATE dapat melakukan pekerjaan untuk Anda. Seperti yang Jeffrey Kemp sarankan, indeks berbasis fungsi dapat dibuat untuk ini.

Mendirikan:

drop table t1;

create table t1 as (
   select 'AB$$' c1 from dual
   union all select 'AB1$' from dual
   union all select 'ABz$' from dual
   union all select 'BZ'   from dual
   union all select 'ac''' from dual
   union all select 'acc#' from dual
   union all select 'aCC#' from dual
);

Demonstrasi:

select * from t1 order by c1;

SELECT c1 FROM t1 
ORDER BY translate(c1
  ,'abcdefghijklmnopqrstuvwxyz0123456789`-=[]\;'',./~!@#$%^&*()_+{}|:"<>?'
  ,'ABCDEFGHIJKLMNOPQRSTUVWXYZ' || rpad(chr(123),10,chr(123)) 
     || rpad(chr(124),31,chr(124)));

Keluaran:

C1 
----
AB$$ 
AB1$ 
ABz$ 
BZ   
aCC# 
ac'                                       '(For Syntax Highlighter)
acc#   
 7 rows selected 

C1 
----
ABz$ 
AB1$ 
AB$$ 
aCC# 
acc# 
ac'  
BZ       
 7 rows selected 

Periksa urutan semua karakter:

SELECT 32+level Value, CHR(32 + level), ascii(CHR(32 + level)) CV FROM dual 
CONNECT BY level <= 255-32 
ORDER BY TRANSLATE(CHR(32 + level)
   , 'abcdefghijklmnopqrstuvwxyz0123456789`-=[]\;'',./~!@#$%^&*()_+{}|:"<>?'
   , 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' || rpad(chr(123),10,chr(123)) 
       || rpad(chr(124),31,chr(124)));
Leigh Riffel
sumber
Seperti yang ditunjukkan Jack Douglas, urutan hasil ini tidak dapat diprediksi sehubungan dengan karakter khusus yang diterjemahkan. Jadi, meskipun jawaban ini memecahkan pertanyaan OP, mungkin tidak berguna jika Anda memerlukan pengurutan simbol yang konsisten.
Leigh Riffel
3
with w as ( select 'AB1$' as foo from dual
  union all select 'aCC#' from dual
  union all select 'ac' from dual
  union all select 'BZ' from dual
  union all select '1' from dual
  union all select 'a' from dual
  union all select '!' from dual )
select foo
from w
order by regexp_replace(lower(foo), '[^a-z]', '~'), regexp_replace(foo, '[^0-9]', '~'), foo;
/*
FOO  
---- 
a    
AB1$ 
ac   
aCC# 
BZ   
1    
!    
*/

Jika Anda ingin mengindeks data untuk menghindari pengurutan dalam kueri dengan order by, Anda dapat melakukannya seperti ini:

create table bar(foo varchar(100) not null, 
                 foo_o1 as (substr(regexp_replace(lower(foo), '[^a-z]', '~'),1,100)), 
                 foo_o2 as (substr(regexp_replace(foo, '[^0-9]', '~'),1,100)));
create index bar_i on bar (foo_o1, foo_o2, foo);
insert into bar(foo)
select 'AB1$' as foo from dual
union all select 'aCC#' from dual
union all select 'ac' from dual
union all select 'BZ' from dual
union all select '1' from dual
union all select 'a' from dual
union all select '!' from dual;
commit;

explain plan for select foo_o1 from bar order by foo_o1, foo_o2, foo;
select * from table(dbms_xplan.display);
/*
--------------------------------------------------------------------------
| Id  | Operation        | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT |       |     7 |  1092 |     1   (0)| 00:00:01 |
|   1 |  INDEX FULL SCAN | BAR_I |     7 |  1092 |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------
*/

- edit

Seperti yang dikomentari @Leigh, pendekatan alternatif, lebih rapi, adalah memiliki fungsi tunggal yang menggabungkan regex (yang dimodifikasi): regexp_replace(lower(foo), '[^a-z]', '~')||regexp_replace(foo, '[^a-zA-Z0-9]', '~')||foo

termasuk ||foopada bagian akhir dalam kedua kasus membuat penentuan deterministik (berulang) yang mungkin merupakan hal yang baik meskipun pertanyaan tidak secara khusus memintanya.

Jack mengatakan coba topanswers.xyz
sumber
1
Cara untuk membuat solusi ini dapat digunakan dengan indeks berbasis fungsi (fungsi tunggal) adalah dengan menyatukan urutannya. Ini memberi kita regexp_replace(lower(foo), '[^a-z]', '~') || regexp_replace(foo, '[^0-9]', '~') || foo. Masalahnya adalah ini agak berbeda dari solusi asli Anda. Jadi, ini adalah versi yang diubah yang perlu koreksi ini, bukan yang asli. Urutan pengurutan dapat diperbaiki dengan mengubah regex kedua, yang memberikan pesanan oleh dari regexp_replace(lower(foo), '[^a-z]', '~') || regexp_replace(foo, '[^0-9a-zA-Z]', '~') || foo.
Leigh Riffel