Для коротких строк, небольшое количество чисел
Если количество числовых значений и максимальная длина ограничены, существует решение на основе регулярных выражений.
Идея такова:
Предположения:
- Максимальная длина чисел известна заранее (например, 20)
- Все числовые значения могут быть дополнены (другими словами,
lpad('1 ', 3000, '1 ')
потерпит неудачу из-за невозможности вписать дополненные числовые значения в varchar2(4000)
)
Следующий запрос оптимизирован для случая «коротких чисел» (см. *?
) и занимает 0,4 секунды. Тем не менее, при использовании такого подхода, вам нужно заранее определить длину отступа.
select * from (
select dbms_random.string('X', 30) val from xmltable('1 to 1000')
)
order by regexp_replace(regexp_replace(val, '(\d+)', lpad('0', 20, '0')||'\1')
, '0*?(\d{21}(\D|$))', '\1');
"Умный" подход
Несмотря на то, что отдельная функция natural_sort
может быть полезна, существует малоизвестный прием для этого в чистом SQL.
Ключевые идеи:
- Уберите начальные нули из всех чисел так, чтобы
02
был упорядочен между 1
и 3
: regexp_replace(val, '(^|\D)0+(\d+)', '\1\2')
. Примечание: это может привести к «неожиданной» сортировке 10.02
> 10.1
(поскольку 02
преобразуется в 2
), однако нет единого ответа, как сортировать такие вещи, как 10.02.03
- Преобразовать
"
в ""
, чтобы текст с кавычками работал правильно
- Преобразовать входную строку в формат с разделителями-запятыми:
'"'||regexp_replace(..., '([^0-9]+)', '","\1","')||'"'
- Конвертировать csv в список предметов через
xmltable
- Увеличивать числовые элементы, чтобы сортировка строк работала правильно
- Используйте
length(length(num))||length(num)||num
вместо lpad(num, 10, '0')
, так как последний менее компактен и не поддерживает 11+ цифр.
Примечание:
Время ответа составляет около 3-4 секунд для сортировки списка из 1000 случайных строк длиной 30 (генерация случайных строк занимает само 0,2 секунды).
Основным потребителем времени является xmltable
, который разбивает текст на строки.
При использовании PL / SQL вместо xmltable
для разделения строки на строки время отклика сокращается до 0,4 с для тех же 1000 строк.
Следующий запрос выполняет естественную сортировку 100 случайных буквенно-цифровых строк (примечание: в Oracle 11.2.0.4 он дает неправильные результаты и работает в 12.1.0.2):
select *
from (
select (select listagg(case when regexp_like(w, '^[0-9]')
then length(length(w))||length(w)||w else w
end
) within group (order by ord)
from xmltable(t.csv columns w varchar2(4000) path '.'
, ord for ordinality) q
) order_by
, t.*
from (
select '"'||regexp_replace(replace(
regexp_replace(val, '(^|\D)0+(\d+)', '\1\2')
, '"', '""')
, '([^0-9]+)', '","\1","')||'"' csv
, t.*
from (
select dbms_random.string('X', 30) val from xmltable('1 to 100')
) t
) t
) t
order by order_by;
Самое интересное в том, что это order by
может быть выражено без подзапросов, так что это удобный инструмент, который сводит вашего рецензента с ума (работает как в 11.2.0.4, так и в 12.1.0.2):
select *
from (select dbms_random.string('X', 30) val from xmltable('1 to 100')) t
order by (
select listagg(case when regexp_like(w, '^[0-9]')
then length(length(w))||length(w)||w else w
end
) within group (order by ord)
from xmltable('$X'
passing xmlquery(('"'||regexp_replace(replace(
regexp_replace(t.val, '(^|\D)0+(\d+)', '\1\2')
, '"', '""')
, '([^0-9]+)', '","\1","')||'"')
returning sequence
) as X
columns w varchar2(4000) path '.', ord for ordinality) q
);