2016-09-13 7 views
1

最近の数日間は、自然順序で英数字テキストのリストをソートしようとしていました.NLS_SORTオプションを使用すると、リストを正しく並べ替えることができました(see this answer) 。しかし、その解決策を試してみると、それは違いがないことが分かりました。リストは、通常のORDER BYクエリと同様に表示されました。私にとっては、solution involving regexは選択肢ではありません。自然なソートとして動作しない英数字テキストのバイナリソート

私はテーブルを作ってそれにいくつかのデータを埋めました。 SELECT name FROM test ORDER BY name ASCを実行しているとき、私は次のような結果を得る:

enter image description here

見ての通りの順序が不自然です。それは1, 2, 3, 4, 5, 6, 7, 8, 9, 10のようなものです。

私が試した解決策は、nls_sortオプションを設定することでした。

ALTER SESSION SET nls_sort='BINARY'; -- or BINARY_AI 
SELECT name FROM test ORDER BY NLSSORT(name,'NLS_SORT=BINARY') -- or BINARY_AI 

ASCII tableで述べたように、それは、各文字の十進コードに基づいて、リスト内のテキストを注文する必要があります。だから私はそれが正しい方法(テーブルの順序は 'スペース'、 'ドット'、数字、文字)であることを期待していましたが、何も変わっていませんでした。注文はまだ画像と同じです。それはその後、ソート順序は、各文字の数値に基づいているBINARYであるので、それはデータベースの文字に依存していた場合

設定それは私が「の文字セットとは何かを持っているかもしれません

私は使用していますが、私は何が間違っているのか分かりません。 SELECT value$ FROM sys.props$ WHERE name = 'NLS_CHARACTERSET';を実行すると私には値AL32UTF8が与えられます。 UTF8のやや拡張されたバージョンのようです(私が間違っていれば私を修正してください)。私はOracleデータベースのバージョン11.2.0.4.0で動作しています。

だから誰でも私が間違っていることや迷っていることを教えてもらえますか?

ありがとうございます。

+0

なぜ順序は1,2,3であるために、それは、自然である...というよりも1,10,100?文字列(文字列)を文字列で見るので、 '10'の前に' 1'を正しく置いています。 2番目の文字の値は、最初の文字のソート方法には影響しません。 'nlssort(name)'が返す文字列を見てください。なぜ正規表現はオプションではないのですか? –

+0

ソフトウェア面では正常です。しかし、私は人間の判読可能な方法で(通常、申し訳ありませんが、それを宣言していたはずです) '普通の'ソートを意味しました。たとえば、Windowsエクスプローラを取る。そのファイルのバージョンとして機能する番号を含むファイルを並べ替えると、昇順に並べ替えるとき(または降順を使用するときは先頭に)、最も高いバージョンがリストの一番下にくることが期待されます。 –

答えて

2

バイナリソートで複数の文字を一度に見ることが期待されるようです。それはしません。それは効果的に最初の文字でソートされます(したがって、1で始まるものはすべて2で始まるものの前に来ます)。 2番目の文字(期間が0の前に来る) - が10の前に来ることを意味するだけでなく、10(または100000)も2の前に来ることを意味します。並べ替え動作のその部分を変更することはできません。あなたがリンクしていた以前の質問では、最初の文字だけが数字だったようですが、それはやや異なる状況です。

From the documentation:文字値がORDER BY句の言語的に比較した場合

は、それらが最初に照合キーに変換し、次いでRAW値のように比較されます。照合キーは、NLSSORTで指定された明示的に生成されるか、NLSSORTが使用する同じメソッドを暗黙的に使用して生成されます。

あなたは、ソートのために使用されるバイト順序見ることができます:あなたは、生のNLSRORT結果(照合キー)が論理的な順序であることがわかります

with t (name) as (
    select level - 1 || '. test' from dual connect by level < 13 
    union all select '20. test' from dual 
    union all select '100. test' from dual 
) 
select name, nlssort(name, 'NLS_SORT=BINARY') as sort_bytes 
from t 
order by name; 

NAME  SORT_BYTES   
---------- -------------------- 
0. test 302E207465737400  
1. test 312E207465737400  
10. test 31302E207465737400 
100. test 3130302E207465737400 
11. test 31312E207465737400 
2. test 322E207465737400  
20. test 32302E207465737400 
3. test 332E207465737400  
4. test 342E207465737400  
5. test 352E207465737400  
6. test 362E207465737400  
7. test 372E207465737400  
8. test 382E207465737400  
9. test 392E207465737400 

を。

正規表現を使用しない場合は、substr()instr()を使用して、ピリオド/スペースより前の部分を取得し、それを数値に変換します。

with t (name) as (
    select level - 1 || '. test' from dual connect by level < 13 
    union all select '20. test' from dual 
    union all select '100. test' from dual 
) 
select name 
from t 
order by to_number(substr(name, 1, instr(name, '. ') - 1)), 
    substr(name, instr(name, '. ')); 

NAME  
---------- 
0. test 
1. test 
2. test 
3. test 
4. test 
5. test 
6. test 
7. test 
8. test 
9. test 
10. test 
11. test 
20. test 
100. test 

は、あなたがそれをチェックでき期間/スペースがない可能性がある場合:それは前提としていてもフォーマットが固​​定されている

select name 
from t 
order by case when instr(name, '. ') > 0 then to_number(substr(name, 1, instr(name, '. ') - 1)) else 0 end, 
    case when instr(name, '. ') > 0 then substr(name, instr(name, '. ')) else name end; 

...しかし、あなたが持っていた場合、あなたはまだ問題を抱えています、名前の中に2つの文章がありますが、最初の文章は数字に変換できません。 ORA-01722が発生した場合は、安全なto_number()関数を実装することができます。

正規表現を使用するように簡単かつ安全になり、例えば:アレックス・プールの優れた記事に追加

select name 
from t 
order by to_number(regexp_substr(name, '^\d+', 1)), name; 
+0

バイナリソートの仕組みについての素晴らしい説明。並べ替えがどのように達成されたか分からなかった。私はこれを試してみます –

2

、ここで私はトム・カイトポスト(here)から学んだ簡単なトリックです。とにかく、このような状況では動作します:

-- padding with spaces ala Tom Kyte approach 
with t (name) as (
    select level - 1 || '. test' from dual connect by level < 13 
    union all select '20. test' from dual 
    union all select '100. test' from dual 
) 
select name 
from t 
order by lpad(name, 20); 

出力:

0. test 
1. test 
2. test 
3. test 
4. test 
5. test 
6. test 
7. test 
8. test 
9. test 
10. test 
11. test 
20. test 
100. test 

希望

EDITを支援します。

このアプローチは、より複雑ですが、状況アレックス・プールをカバー(再び、トム・キテの功績):

with t (name) as (
    select level - 1 || '. test' from dual connect by level < 13 
    union all select '20. hello' from dual 
    union all select '100. test' from dual 
) 
select 
    --substr(name,1,length(name)-nvl(length(replace(translate(name,'','0000000000'),'0','')),0)), 
    --substr(name,1+length(name)-nvl(length(replace(translate(name,'','0000000000'),'0','')),0)) , 
    name 
from t 
order by 
    to_number(substr(name,1,length(name)-nvl(length(replace(translate(name,'','0000000000'),'0','')),0))), 
       substr(name,1+length(name)-nvl(length(replace(translate(name,'','0000000000'),'0','')),0)) NULLS FIRST; 

出力:

0. test 
1. test 
2. test 
3. test 
4. test 
5. test 
6. test 
7. test 
8. test 
9. test 
10. test 
11. test 
20. hello 
100. test 
+0

Mmm、それはサンプルデータで動作しますが、非数値部分は各行の長さが同じであるためです。たとえば、20のテキストを 'test'から 'hello'に変更すると、100の後にソートされます。これは純粋な数値のトリックです(文字列ではありません)。数字とテキストの混在? –

+0

間違いなく魔法の弾丸ですが、それは関係するデータに応じていくつかの状況で動作します。 – tbone

+0

@AlexPooleまた、Tom Kyteの投稿を見ると、投稿をさらに詳しく調べることができます。チャンスがあれば、その方法を使ってこの投稿を更新します。 – tbone

関連する問題