Oracle sort varchar2 Spalte mit Sonderzeichen zuletzt


8

Wie kann ich in Oracle eine Varchar2- oder NVarchar2-Spalte so sortieren, dass sie in meiner benutzerdefinierten Reihenfolge vorliegt? Oder sind vorhandene Optionen verfügbar, bei denen zuerst Buchstaben, dann Zahlen und dann alle Sonderzeichen eingefügt werden?

Unser erster Ansatz war die Verwendung einer Funktion, mit der Zeichen manuell Zahlen zugeordnet werden können.

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

Die spezielle Sortierfunktion ordnet jedes Zeichen einer zweistelligen Zahl zu, und der Rückgabewert wird zum Sortieren verwendet. Dies scheint nur eine sehr teure Verkettung zu sein, und es fühlt sich falsch an.

        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;

Es fällt mir schwer, einen alternativen Ansatz zu finden. Ich möchte wissen, welche Probleme mit diesem Ansatz bestehen. Vielleicht haben wir keine Alternativen.

Nachtrag 1:

Beispiel für sortierte Daten hinzufügen. Im Allgemeinen wird bei Großbuchstaben die Groß- und Kleinschreibung nicht berücksichtigt, dann die Zahlen 0 bis 9 und dann die Sonderzeichen in beliebiger Reihenfolge.

Hier ist eine sortierte aufsteigende Beispielliste. Beachten Sie, dass Sonderzeichen austauschbar sind. Sie sollten alle nach Buchstaben und Zahlen stehen. Bei der binären Sortierung stehen einige Sonderzeichen vor Buchstaben (dh ')

Meine gewünschte Bestellung,

AB1 $
aCC #
ac '
BZ

Oracle binäre Reihenfolge

AB1 $
BZ
ac '
acc #

Antworten:


5

Wenn die Sortierreihenfolge, die Sie angeben möchten, bereits von Oracle unterstützt wird, können Sie dies tun, indem Sie nach der NLSSORT-Funktion bestellen - wie folgt:

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

Eine Liste der unterstützten Sortierreihenfolgen finden Sie hier .


Da es sich um Groß- und Kleinschreibung handelt, gibt es wirklich eine, die in diesem Fall funktionieren würde?
Leigh Riffel

5

Einige Optionen:

  1. Behalten Sie die sortierte Version Ihrer Daten per Trigger in einer Tabelle bei und verwenden Sie sie.

  2. Verwenden Sie Oracle Locale Builder , um eine benutzerdefinierte Sortierreihenfolge zu erstellen. (Vorsichtsmaßnahme: Ich habe dies noch nie verwendet, daher weiß ich nicht, welche Fallstricke dort vorhanden sein können.) Sie können dann die NLSSORT-Funktion mit dieser benutzerdefinierten Sortierreihenfolge verwenden.


4

Ein anderer Ansatz besteht darin, einen funktionsbasierten Index hinzuzufügen FN_SPECIAL_SORT_KEY(sorted_column,'asc'). Vermeidet die Notwendigkeit einer zusätzlichen Spalte + eines Auslösers, und Sie müssen Ihre Abfragen nicht ändern.


4

Basierend auf Ihrer Beschreibung scheint es, dass TRANSLATE die Arbeit für Sie erledigen kann. Wie Jeffrey Kemp vorschlägt, könnte hierfür ein funktionsbasierter Index erstellt werden.

Installieren:

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

Demonstration:

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

Ausgabe:

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 

Überprüfen Sie die Reihenfolge aller Zeichen:

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

Wie Jack Douglas betonte, ist die Reihenfolge dieser Ergebnisse in Bezug auf übersetzte Sonderzeichen nicht vorhersehbar. Obwohl diese Antwort die Frage des OP löst, ist sie möglicherweise nicht hilfreich, wenn Sie eine konsistente Symbolreihenfolge benötigen.
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    
!    
*/

Wenn Sie die Daten indizieren möchten, um eine Sortierung in einer Abfrage mit einem zu vermeiden order by, können Sie dies folgendermaßen tun:

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

- bearbeiten

Wie @Leigh kommentiert hat, besteht ein alternativer, übersichtlicherer Ansatz darin, eine einzige Funktion zu haben, die die (modifizierten) regulären Ausdrücke verkettet: regexp_replace(lower(foo), '[^a-z]', '~')||regexp_replace(foo, '[^a-zA-Z0-9]', '~')||foo

die einschließlich ||fooam Ende in jedem Fall macht die Bestellung deterministisch (wiederholbaren) , die eine gute Sache selbst sein könnte , wenn die Frage speziell für sie nicht fragen.


1
Eine Möglichkeit, diese Lösung mit einem funktionsbasierten Index (für eine einzelne Funktion) nutzbar zu machen, besteht darin, die Reihenfolge um zu verketten. Das gibt uns regexp_replace(lower(foo), '[^a-z]', '~') || regexp_replace(foo, '[^0-9]', '~') || foo. Das Problem ist, dass dies anders sortiert als Ihre ursprüngliche Lösung. Es ist also die geänderte Version, die diese Korrektur benötigt, nicht Ihr Original. Die Sortierreihenfolge kann durch Ändern des zweiten regulären Ausdrucks festgelegt werden, der eine Reihenfolge von von ergibt regexp_replace(lower(foo), '[^a-z]', '~') || regexp_replace(foo, '[^0-9a-zA-Z]', '~') || foo.
Leigh Riffel
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.