Oracle ordena la columna varchar2 con caracteres especiales al final


8

¿Cómo puedo ordenar en Oracle una columna Varchar2 o NVarchar2 para que esté en mi propio orden definido personalizado? O hay opciones disponibles que pondrán letras primero, luego números, luego todos los caracteres especiales.

Nuestro primer enfoque fue utilizar una función que hace un mapeo manual de caracteres a números.

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

La función especial de clasificación asigna cada carácter a un número de 2 dígitos, y el valor de retorno se utiliza para la clasificación. Esto parece ser una concatenación realmente costosa, y se siente mal.

        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;

Me está costando trabajo encontrar un enfoque alternativo. Quiero saber qué problemas existen con este enfoque. Quizás no tenemos alternativas.

Anexo 1:

Agregar ejemplo de datos ordenados. En general, todos los caracteres alfa distinguen entre mayúsculas y minúsculas, luego los números 0-9, luego los caracteres especiales en cualquier orden.

Aquí hay una lista ordenada ascendente de muestra. Tenga en cuenta que los caracteres especiales son intercambiables, todos deben ir después de letras y números. En orden binario, algunos caracteres especiales están antes de las letras (es decir, ')

Mi pedido deseado,

AB1 $
aCC #
ac '
BZ

Orden binario de Oracle

AB1 $
BZ
ac '
acc #

Respuestas:


5

Si Oracle ya admite el orden de clasificación que desea especificar, puede hacerlo ordenando mediante la función NLSSORT, de la siguiente manera:

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

Puede encontrar una lista de órdenes admitidas aquí .


Dado que estos tratan con casos y signos diacríticos, ¿hay realmente uno que funcione en este caso?
Leigh Riffel

5

Algunas opciones:

  1. Persista la versión ordenada de sus datos en una tabla a través del activador y utilícela.

  2. Use Oracle Locale Builder para crear un orden de clasificación personalizado. (Advertencia: nunca he usado esto, así que no sé qué trucos pueden existir allí). Luego, podría usar la función NLSSORT con ese orden de clasificación personalizado.


4

Otro enfoque es agregar un índice basado en funciones en FN_SPECIAL_SORT_KEY(sorted_column,'asc'). Evita la necesidad de una columna adicional + disparador, y no necesitará modificar sus consultas.


4

Según su descripción, parece que TRANSLATE puede hacer el trabajo por usted. Como sugiere Jeffrey Kemp, se podría crear un índice basado en funciones para esto.

Preparar:

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

Demostración:

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

Salida:

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 

Verifique el orden de todos los caracteres:

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

Como señaló Jack Douglas, el orden de estos resultados no es predecible con respecto a los caracteres especiales traducidos. Entonces, aunque esta respuesta resuelve la pregunta del OP, puede no ser útil si requiere un orden de símbolos consistente.
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    
!    
*/

Si desea indexar los datos para evitar una ordenación en una consulta con un order by, puede hacerlo así:

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

- editar

Como ha comentado @Leigh, un enfoque alternativo y más ordenado es tener una sola función que concatene las expresiones regulares (modificadas): regexp_replace(lower(foo), '[^a-z]', '~')||regexp_replace(foo, '[^a-zA-Z0-9]', '~')||foo

incluir el ||foofinal en cualquier caso hace que el ordenamiento sea determinista (repetible), lo que podría ser algo bueno aunque la pregunta no lo solicite específicamente.


1
Una forma de hacer que esta solución sea utilizable con un índice basado en funciones (función única) es concatenar el orden por. Esto nos da regexp_replace(lower(foo), '[^a-z]', '~') || regexp_replace(foo, '[^0-9]', '~') || foo. El problema es que esto se clasifica de manera diferente a su solución original. Por lo tanto, es la versión alterada la que necesita esta corrección, no la original. El orden de clasificación se puede corregir cambiando la segunda expresión regular, que da un orden de por regexp_replace(lower(foo), '[^a-z]', '~') || regexp_replace(foo, '[^0-9a-zA-Z]', '~') || foo.
Leigh Riffel
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.