2016-09-13 11 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 

IT는 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'을 넣고 있습니다. 두 번째 문자의 값은 첫 번째 문자의 정렬 방식에는 영향을주지 않습니다. 그'nlssort (name)'문자열을 보자. 정규식을 사용할 수없는 이유는 무엇입니까? –

+0

소프트웨어 측면에서는 정상입니다. 하지만 나는 인간이 읽을 수있는 방식으로 '정상적인'정렬을 의미했다. (미안하다.) 예를 들어 Windows 탐색기를 가져 가십시오. 해당 파일의 버전 역할을하는 숫자가 포함 된 파일을 정렬 할 때 오름차순으로 정렬하거나 내림차순으로 정렬 할 때 목록의 맨 아래에 상위 버전이 있어야합니다. –

답변

2

이진 정렬을 사용하면 여러 문자를 한 번에 볼 수 있습니다. 그렇지 않습니다. 그것은 첫 번째 문자에 의해 효과적으로 정렬됩니다 (그래서 1로 시작하는 모든 것은 2로 시작하는 것 앞에옵니다); 두 번째 문자 (따라서 마침표는 0 앞에옵니다) - 이는 1.10 앞에오고 또한 10 (또는 100000)이 2 앞에 오는 것이 맞음을 의미합니다. 정렬 동작의 측면을 변경할 수 없습니다. 당신이 이전에 질문에 링크 한 것처럼, 단지 첫 번째 문자가 숫자 인 것처럼 보입니다. 이것은 약간 다른 상황입니다.

From the documentation

는 :

문자 값이 ORDER BY 절 위해 언어 적 비교

들은 제 RAW 같은 값 비교 후, 조합 키로 변환하고있다. 조합 키는 NLSSORT에 명시된대로 명시 적으로 생성되거나 NLSSORT이 사용하는 것과 동일한 방법을 사용하여 암시 적으로 생성됩니다.

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 

당신은 원시 NLSRORT 결과 (정렬 키) 논리적 인 순서에있는 것을 볼 수 있습니다

당신은 정렬을 위해 사용되는 바이트 순서를 볼 수 있습니다.

정규식을 사용하지 않으려면 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; 

...하지만 당신이 있다면 당신은 여전히 ​​문제가, 이름에 두 문장이 있지만 첫 번째 문장은 숫자로 변환 될 수 없습니다. 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 

희망

편집을하는 데 도움이 :

이 방법은 더 복잡하지만 상황 알렉스 풀을 커버 (다시 톰 키테에게 공헌 함) :

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

음, 샘플 데이터와 함께 작동하지만 비 숫자 부분이 각 행에 대해 동일한 길이이기 때문입니다. 예를 들어, 20 개의 텍스트를 'test'에서 'hello'로 변경하면 100을 기준으로 정렬됩니다. 순전히 숫자 값에 대한 좋은 트릭입니다 (물론 문자열이 아니어야합니다!). 숫자와 텍스트의 혼합? –

+0

분명히 마법의 총알은 아니지만 관련 데이터에 따라 일부 상황에서 작동합니다. – tbone

+0

@AlexPoole 또한 Tom Kyte 게시글을 보면 게시자가 게시자의 게시물을 더 자세히 검토하는 경우에 대해 설명합니다.기회가 생기면이 방법을 사용하여이 게시물을 업데이트 할 것입니다. – tbone