2016-09-13 1 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, то порядок сортировки на основе числового значения каждого символа, так что это зависит от характера базы данных установить

Это может иметь что-то делать с набором символов I» м, но я не уверен, что с ним не так. Запуск SELECT value$ FROM sys.props$ WHERE name = 'NLS_CHARACTERSET'; дает мне значение AL32UTF8. Который кажется немного расширенной версией UTF8 (исправьте меня, если я ошибаюсь). Я работаю на базе Oracle версии 11.2.0.4.0.

Так может ли кто-нибудь сказать мне, что я делаю неправильно или что мне не хватает?

Заранее спасибо.

+0

Почему это естественно для того, чтобы быть 1,2,3, ..., а не 1,10,100? Он смотрит на строковый символ по символу (по существу), поэтому он * правильно помещает '1.' перед' 10'. Значение второго символа не влияет на сортировку первого символа. Посмотрите на строку этого 'nlssort (name)' return. Почему не является регулярным выражением? –

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

... но вы все еще есть проблемы, если у вас, скажем, два предложения в имени, но первое не может быть преобразовано в число. Вы можете реализовать «безопасную» функцию to_number(), которая выдает ORA-01722, если это произойдет.

Было бы проще и безопаснее использовать регулярное выражение, например .:

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

Отличное объяснение того, как работает двоичный файл. Не знал о том, как осуществляется сортировка. Я попробую это сделать –

2

Добавление к превосходному сообщению Alex Poole, вот простой трюк, который я узнал от должности Тома Kyte (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:

Этот подход является более сложным, но охватывает ситуацию Alex Poole (опять же, кредит Тому Ките):

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 Кроме того, если вы посмотрите на пост Тома Ките, в нем обсуждается случай, который вы поднимаете дальше по почте.Если я получу шанс, я обновлю этот пост, используя этот подход. – tbone

Смежные вопросы