Colonne varchar2 de tri Oracle avec les derniers caractères spéciaux

8

Comment puis-je trier dans Oracle une colonne Varchar2 ou NVarchar2 pour qu'elle soit dans mon propre ordre personnalisé défini. Ou existe-t-il des options existantes qui placeront les lettres en premier, puis les chiffres, puis tous les caractères spéciaux.

Notre première approche consistait à utiliser une fonction qui mappe manuellement les caractères aux nombres.

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

La fonction de tri spéciale mappe chaque caractère à un nombre à 2 chiffres et la valeur de retour est utilisée pour le tri. Cela semble être une concaténation vraiment très chère, et cela semble 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;

J'ai du mal à trouver une approche alternative. Je veux savoir quels problèmes existent avec cette approche. Peut-être que nous n'avons pas d'alternatives.

Addendum 1:

Ajout d'un exemple de données triées. En général, tous les caractères alpha ne respectent pas la casse, puis les chiffres 0 à 9, puis les caractères spéciaux dans n'importe quel ordre.

Voici un exemple de liste ascendante triée. Gardez à l'esprit que les caractères spéciaux sont interchangeables, ils devraient tous être après des lettres et des chiffres. En tri binaire, certains caractères spéciaux sont avant les lettres (par exemple ')

Ma commande souhaitée,

AB1 $
aCC #
ac '
BZ

Ordre binaire Oracle

AB1 $
BZ
ac '
acc #

Andy
la source

Réponses:

5

Si l'ordre de tri que vous souhaitez spécifier est déjà pris en charge par Oracle, vous pouvez le faire en classant par la fonction NLSSORT - comme ceci:

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

Vous pouvez trouver une liste des ordres de tri pris en charge ici .


la source
Puisqu'il s'agit de cas et de signes diacritiques, y en a-t-il vraiment un qui fonctionnerait dans ce cas?
Leigh Riffel
5

Quelques options:

  1. Conservez la version triée de vos données dans une table via un déclencheur et utilisez-la.

  2. Utilisez Oracle Locale Builder pour créer un ordre de tri personnalisé. (Mise en garde: je n'ai jamais utilisé cela, donc je ne sais pas ce qui peut exister là-bas.) Vous pouvez alors utiliser la fonction NLSSORT avec cet ordre de tri personnalisé.

Adam Musch
la source
4

Une autre approche consiste à ajouter un index basé sur une fonction sur FN_SPECIAL_SORT_KEY(sorted_column,'asc'). Évite la nécessité d'une colonne + déclencheur supplémentaire, et vous n'aurez pas besoin de modifier vos requêtes.

Jeffrey Kemp
la source
4

D'après votre description, TRANSLATE peut faire le travail pour vous. Comme le suggère Jeffrey Kemp, un index basé sur les fonctions pourrait être créé pour cela.

Installer:

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

Manifestation:

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

Production:

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 

Vérifiez l'ordre de tous les personnages:

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
la source
Comme l'a souligné Jack Douglas, l'ordre de ces résultats n'est pas prévisible en ce qui concerne les caractères spéciaux traduits. Ainsi, bien que cette réponse résout la question de l'OP, elle peut ne pas être utile si vous avez besoin d'un ordre de symboles cohérent.
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 vous souhaitez indexer les données pour éviter un tri dans une requête avec un order by, vous pouvez le faire comme ceci:

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

-- Éditer

Comme l'a expliqué @Leigh, une approche alternative, plus nette, consiste à avoir une seule fonction concaténant les expressions rationnelles (modifiées): regexp_replace(lower(foo), '[^a-z]', '~')||regexp_replace(foo, '[^a-zA-Z0-9]', '~')||foo

l'inclusion ||foode la fin dans les deux cas rend l'ordonnancement déterministe (répétable), ce qui pourrait être une bonne chose même si la question ne le demande pas spécifiquement.

Jack dit d'essayer topanswers.xyz
la source
1
Un moyen de rendre cette solution utilisable avec un index basé sur une fonction (à fonction unique) est de concaténer l'ordre par. Cela nous donne regexp_replace(lower(foo), '[^a-z]', '~') || regexp_replace(foo, '[^0-9]', '~') || foo. Le problème est que cela trie différemment de votre solution d'origine. C'est donc la version modifiée qui a besoin de cette correction, pas l'original. L'ordre de tri peut être fixé en modifiant la deuxième expression rationnelle, qui donne un ordre par de regexp_replace(lower(foo), '[^a-z]', '~') || regexp_replace(foo, '[^0-9a-zA-Z]', '~') || foo.
Leigh Riffel