Oracle ordina la colonna varchar2 con caratteri speciali per ultimi


8

Come posso ordinare in Oracle una colonna Varchar2 o NVarchar2 per essere nel mio ordine definito personalizzato. Oppure sono disponibili opzioni esistenti che inseriranno prima le lettere, quindi i numeri, quindi tutti i caratteri speciali.

Il nostro primo approccio consisteva nell'utilizzare una funzione che esegue manualmente la mappatura dei caratteri ai numeri.

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

La funzione di ordinamento speciale associa ciascun carattere a un numero di 2 cifre e il valore restituito viene utilizzato per l'ordinamento. Questa sembra essere solo una concatenazione davvero costosa e sembra sbagliata.

        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;

Sto facendo fatica a trovare un approccio alternativo. Voglio sapere quali problemi esistono con questo approccio. Forse non abbiamo alternative.

Addendum 1:

Aggiunta di esempio di dati ordinati. In generale, tutti i caratteri alfa non fanno distinzione tra maiuscole e minuscole, quindi numeri 0-9, quindi caratteri speciali in qualsiasi ordine.

Ecco un esempio di elenco crescente ordinato. Tieni presente che i caratteri speciali sono intercambiabili, tutti dovrebbero essere dopo lettere e numeri. In ordine binario, alcuni caratteri speciali sono prima delle lettere (es. ')

Il mio ordine desiderato,

AB1 $
aCC #
ac '
BZ

Ordine binario Oracle

AB1 $
BZ
ac '
acc #

Risposte:


5

Se l'ordinamento che si desidera specificare è già supportato da Oracle, è possibile farlo ordinando per la funzione NLSSORT - in questo modo:

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

Puoi trovare un elenco di ordinamenti supportati qui .


Dal momento che si occupano di casi e segni diacritici, ce n'è davvero uno che funzionerebbe in questo caso?
Leigh Riffel,

5

Alcune opzioni:

  1. Persistere la versione ordinata dei dati in una tabella tramite trigger e utilizzarla.

  2. Utilizzare Oracle Locale Builder per creare un ordinamento personalizzato. (Avvertenza: non l'ho mai usato, quindi non so quali gotcha possano esistere lì.) È quindi possibile utilizzare la funzione NLSSORT con quell'ordinamento personalizzato.


4

Un altro approccio è quello di aggiungere un indice basato sulle funzioni FN_SPECIAL_SORT_KEY(sorted_column,'asc'). Evita la necessità di una colonna aggiuntiva + trigger e non dovrai modificare le tue query.


4

Sulla base della tua descrizione sembra che TRANSLATE possa fare il lavoro per te. Come suggerisce Jeffrey Kemp, un indice basato sulle funzioni potrebbe essere creato per questo.

Impostare:

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
);

Dimostrazione:

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)));

Produzione:

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 

Controlla l'ordine di tutti i personaggi:

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)));

Come ha sottolineato Jack Douglas, l'ordinamento di questi risultati non è prevedibile per quanto riguarda i caratteri speciali tradotti. Pertanto, sebbene questa risposta risolva la domanda del PO, potrebbe non essere utile se si richiede un ordinamento coerente dei simboli.
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    
!    
*/

Se desideri indicizzare i dati per evitare un ordinamento in una query con un order by, puoi farlo in questo modo:

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 |
--------------------------------------------------------------------------
*/

-- modificare

Come ha commentato @Leigh, un approccio alternativo, più ordinato, è avere una singola funzione che concatena le regex (modificate): regexp_replace(lower(foo), '[^a-z]', '~')||regexp_replace(foo, '[^a-zA-Z0-9]', '~')||foo

includendo l' ||fooend alla fine in entrambi i casi rende l'ordinamento deterministico (ripetibile) che potrebbe essere una buona cosa anche se la domanda non lo richiede specificamente.


1
Un modo per rendere utilizzabile questa soluzione con un indice basato su una funzione (singola funzione) è concatenare l'ordine per. Questo ci dà regexp_replace(lower(foo), '[^a-z]', '~') || regexp_replace(foo, '[^0-9]', '~') || foo. Il problema è che questo è diverso rispetto alla soluzione originale. Quindi, è la versione modificata che richiede questa correzione, non l'originale. L'ordinamento può essere risolto modificando la seconda regex, che dà un ordine di regexp_replace(lower(foo), '[^a-z]', '~') || regexp_replace(foo, '[^0-9a-zA-Z]', '~') || foo.
Leigh Riffel,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.