Coluna Oracle varchar2 de classificação com caracteres especiais por último

8

Como posso classificar no Oracle uma coluna Varchar2 ou NVarchar2 para estar na minha própria ordem definida personalizada. Ou existem opções disponíveis que colocarão primeiro as letras, os números e todos os caracteres especiais.

Nossa primeira abordagem foi usar uma função que faz um mapeamento manual de caracteres para números.

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

A função de classificação especial mapeia cada caractere para um número de 2 dígitos e o valor de retorno é usado para classificação. Isso parece ser apenas uma concatenação realmente cara e parece errado.

        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;

Estou tendo dificuldades para propor uma abordagem alternativa. Quero saber que problemas existem com essa abordagem. Talvez não tenhamos alternativas.

Adenda 1:

Adicionando exemplo de dados classificados. Em geral, todos os caracteres alfa não fazem distinção entre maiúsculas e minúsculas e, em seguida, números de 0 a 9, depois caracteres especiais em qualquer ordem.

Aqui está um exemplo de lista ascendente classificada. Lembre-se de que caracteres especiais são intercambiáveis; todos devem estar depois de letras e números. No tipo binário, alguns caracteres especiais estão antes das letras (ie ')

Meu pedido desejado,

AB1 $
aCC #
ac '
BZ

Ordem binária do Oracle

AB1 $
BZ
ac '
acc #

Andy
fonte

Respostas:

5

Se a ordem de classificação que você deseja especificar já é suportada pelo Oracle, você pode fazer isso ordenando pela função NLSSORT - da seguinte maneira:

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

Você pode encontrar uma lista de ordens de classificação suportadas aqui .


fonte
Já que eles lidam com casos e diacríticos, existe realmente um que funcionaria nesse caso?
Leigh Riffel
5

Algumas opções:

  1. Persista a versão classificada de seus dados para uma tabela via gatilho e use-a.

  2. Use o Oracle Locale Builder para criar uma ordem de classificação personalizada. (Advertência: nunca usei isso, então não sei quais truques podem existir lá.) Em seguida, você poderia usar a função NLSSORT com essa ordem de classificação personalizada.

Adam Musch
fonte
4

Outra abordagem é adicionar um índice baseado em função FN_SPECIAL_SORT_KEY(sorted_column,'asc'). Evita a necessidade de uma coluna extra + gatilho, e você não precisará modificar suas consultas.

Jeffrey Kemp
fonte
4

Com base na sua descrição, parece que o TRANSLATE pode fazer o trabalho por você. Como Jeffrey Kemp sugere, um índice baseado em função pode ser criado para isso.

Configuração:

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

Demonstração:

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

Resultado:

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 a ordem de todos os 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)));
Leigh Riffel
fonte
Como Jack Douglas apontou, a ordenação desses resultados não é previsível em relação aos caracteres especiais traduzidos. Portanto, embora essa resposta resolva a pergunta do OP, pode não ser útil se você precisar de uma ordenação consistente de símbolos.
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 você deseja indexar os dados para evitar uma classificação em uma consulta com um order by, faça o seguinte:

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 o @Leigh comentou, uma abordagem alternativa, mais pura, é ter uma única função concatenando os regexs (modificados): regexp_replace(lower(foo), '[^a-z]', '~')||regexp_replace(foo, '[^a-zA-Z0-9]', '~')||foo

a inclusão de ||foono final, em ambos os casos, torna a ordem determinística (repetível), o que pode ser uma coisa boa, mesmo que a pergunta não a peça especificamente.

Jack diz que tenta topanswers.xyz
fonte
11
Uma maneira de tornar essa solução utilizável com um índice baseado em função (função única) é concatenar a ordem por. Isso nos dá regexp_replace(lower(foo), '[^a-z]', '~') || regexp_replace(foo, '[^0-9]', '~') || foo. O problema é que isso é diferente da sua solução original. Portanto, é a versão alterada que precisa dessa correção, não o original. A ordem de classificação pode ser corrigida alterando o segundo regex, que fornece uma ordem por de regexp_replace(lower(foo), '[^a-z]', '~') || regexp_replace(foo, '[^0-9a-zA-Z]', '~') || foo.
Leigh Riffel