Oracle сортирует столбец varchar2 со специальными символами последними

8

Как я могу отсортировать в Oracle столбцы Varchar2 или NVarchar2 в моем собственном порядке. Или доступны какие-либо существующие опции, в которых сначала ставятся буквы, затем цифры, а затем все специальные символы.

Наш первый подход заключался в использовании функции, которая вручную отображает символы в числа.

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

Специальная функция сортировки отображает каждый символ в двузначное число, а возвращаемое значение используется для сортировки. Кажется, это просто очень дорогая конкатенация, и она кажется неправильной.

        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 #
ac '
BZ

Двоичный порядок Oracle

AB1 $
BZ
ac '
acc #

Энди
источник

Ответы:

5

Если порядок сортировки, который вы хотите указать, уже поддерживается Oracle, вы можете сделать это, упорядочив функцией NLSSORT, например:

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

Вы можете найти список поддерживаемых порядков сортировки здесь .


источник
Так как они имеют дело с падежом и диакритическими знаками, есть ли действительно один, который будет работать в этом случае?
Ли Риффель
5

Некоторые варианты:

  1. Сохраните отсортированную версию ваших данных в таблице с помощью триггера и используйте ее.

  2. Используйте Oracle Locale Builder для создания пользовательского порядка сортировки. (Предостережение: я никогда не использовал это, поэтому я не знаю, какие ошибки могут существовать там.) Затем вы можете использовать функцию NLSSORT с этим пользовательским порядком сортировки.

Адам Муш
источник
4

Другой подход - добавить индекс на основе функции FN_SPECIAL_SORT_KEY(sorted_column,'asc'). Избегает необходимости в дополнительном столбце + триггер, и вам не нужно будет изменять ваши запросы.

Джеффри Кемп
источник
4

На основании вашего описания кажется, что TRANSLATE может сделать всю работу за вас. Как предполагает Джеффри Кемп, для этого может быть создан индекс на основе функций.

Настроить:

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)));
Ли Риффель
источник
Как отметил Джек Дуглас, упорядочение этих результатов не предсказуемо в отношении переведенных специальных символов. Таким образом, хотя этот ответ решает вопрос ОП, он может оказаться бесполезным, если вам требуется последовательное упорядочение символов.
Ли Риффель
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    
!    
*/

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

Джек говорит, попробуйте topanswers.xyz
источник
1
Способ сделать это решение пригодным для использования с индексом, основанным на функции (одной функции), - объединить порядок с помощью. Это дает нам regexp_replace(lower(foo), '[^a-z]', '~') || regexp_replace(foo, '[^0-9]', '~') || foo. Проблема в том, что это отличается от вашего первоначального решения. Таким образом, исправленная версия нуждается в исправлении, а не в оригинале. Порядок сортировки можно исправить, изменив второе регулярное выражение, которое дает порядок на regexp_replace(lower(foo), '[^a-z]', '~') || regexp_replace(foo, '[^0-9a-zA-Z]', '~') || foo.
Ли Риффель