web-dev-qa-db-ja.com

最後に特殊文字を含むOracleソートvarchar2列

OracleでVarchar2またはNVarchar2列を独自のカスタム定義の順序に並べ替えるにはどうすればよいですか。または、最初に文字、次に数字、次にすべての特殊文字を配置する既存のオプションがあります。

最初のアプローチは、文字を数字に手動でマッピングする関数を使用することでした。

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

特殊なソート関数は、各文字を2桁の数値にマップし、戻り値はソートに使用されます。これは本当に高額な連結であるように見え、間違っているように感じます。

        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;

別の方法を考え出すのに苦労しています。このアプローチにはどのような問題があるのか​​知りたい。おそらく私たちには代替手段はありません。

補遺1:

ソートされたデータの例を追加します。一般に、すべての英字は大文字と小文字を区別せず、次に0〜9の数字、次に特殊文字を任意の順序で使用します。

これは昇順リストのサンプルです。特殊文字は交換可能であり、すべて文字と数字の後に置く必要があることに注意してください。バイナリソートでは、一部の特殊文字が文字の前にあります(つまり、 ')

私の希望する注文、

AB1 $
aCC#
交流'
BZ

Oracleバイナリ順序

AB1 $
BZ
交流'
acc#

8
Andy

指定するソート順がOracleですでにサポートされている場合は、NLSSORT関数で並べ替えることでこれを行うことができます。

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

サポートされているソート順のリスト ここ を見つけることができます。

5
user10933

いくつかのオプション:

  1. ソートされたバージョンのデータをトリガーを介してテーブルに永続化し、それを使用します。

  2. Oracle Locale Builder を使用して、カスタムの並べ替え順序を作成します。 (注意:私はこれを使用したことがないので、そこに何が存在するかわからない。)次に、そのカスタムソート順でNLSSORT関数を使用できます。

5
Adam Musch

あなたの説明に基づいて、それは[〜#〜] translate [〜#〜]があなたのために仕事をすることができるようです。 Jeffrey Kempが示唆するように、関数ベースのインデックスを作成できます。

セットアップ:

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

デモンストレーション:

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

出力:

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 

すべての文字の順序を確認します。

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)));
4
Leigh Riffel

別のアプローチは、FN_SPECIAL_SORT_KEY(sorted_column,'asc')に関数ベースのインデックスを追加することです。追加の列とトリガーの必要性を回避し、クエリを変更する必要はありません。

4
Jeffrey Kemp
_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    
!    
*/
_

_order by_を使用したクエリでの並べ替えを回避するためにデータにインデックスを付ける場合は、次のようにします。

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

-編集

@Leighがコメントしたように、代替の、より簡潔なアプローチは、(変更された)正規表現を連結する単一の関数を持つことです:regexp_replace(lower(foo), '[^a-z]', '~')||regexp_replace(foo, '[^a-zA-Z0-9]', '~')||foo

どちらの場合も最後に_||foo_を含めると、順序が確定的(反復可能)になり、質問で明確に要求されなくても、これは良いことかもしれません。