Как сортировать и отображать смешанные списки букв и цифр, как ожидают пользователи?
-
23-08-2019 - |
Вопрос
Наше приложение имеет CustomerNumber
поле.У нас есть сотни разных людей, использующих систему (у каждого есть свой собственный логин и свой собственный список CustomerNumber
ы).У отдельного пользователя может быть не более 100 000 клиентов.У многих их меньше 100.
Некоторые люди вводят только фактические цифры в поля своих номеров клиентов, в то время как другие используют смесь вещей.Система допускает 20 символов, которые могут быть A-Z, 0-9 или тире, и сохраняет их в VARCHAR2(20).Все, что записано в нижнем регистре, перед сохранением делается заглавным.
Теперь предположим, что у нас есть простой отчет, в котором перечислены все клиенты для конкретного пользователя, отсортированные по номеру клиента.например ,
SELECT CustomerNumber,CustomerName
FROM Customer
WHERE User = ?
ORDER BY CustomerNumber;
Это наивное решение, поскольку люди, которые используют только цифры, не хотят видеть простую алфавитную сортировку (где "10" стоит перед "9").
Я не хочу задавать пользователю никаких ненужных вопросов об их данных.
Я использую Oracle, но я думаю, было бы интересно увидеть некоторые решения для других баз данных.Пожалуйста, укажите, в какой базе данных работает ваш ответ.
Как вы думаете, каков наилучший способ реализовать это?
Решение
В Oracle 10g:
SELECT cust_name
FROM t_customer c
ORDER BY
REGEXP_REPLACE(cust_name, '[0-9]', ''), TO_NUMBER(REGEXP_SUBSTR(cust_name, '[0-9]+'))
Это приведет к сортировке по первому появлению числа, а не по его позиции, i.e.:
customer1 < customer2 < customer10
cust1omer ? customer1
cust8omer1 ? cust8omer2
, где a ?
означает, что порядок не определен.
Этого достаточно для большинства случаев.
Принудительный порядок сортировки по регистру 2
, вы можете добавить REGEXP_INSTR(cust_name, '[0-9]', n)
Для ORDER BY
Список n
раз, наводя порядок при первом появлении n
-че (2nd
, 3rd
и т.д.) группа цифр.
Принудительный порядок сортировки по регистру 3
, вы можете добавить TO_NUMBER(REGEXP_SUBSTR(cust_name, '[0-9]+', n))
Для ORDER BY
Список n
раз, форсируя порядок n
-й.группа цифр.
На практике запроса, который я написал, достаточно.
Вы можете создать индекс на основе функции на основе этих выражений, но вам нужно будет принудительно использовать подсказку и однопроходный SORT ORDER BY
будет выполняться в любом случае, поскольку CBO
недостаточно доверяет индексам на основе функций, чтобы позволить ORDER BY
на них.
Другие советы
Вероятно, вам лучше всего предварительно рассчитать отдельный столбец и использовать его для оформления заказа, а также использовать номер клиента для отображения.Вероятно, это включало бы заполнение 0 любыми внутренними целыми числами фиксированной длины.
Другая возможность заключается в том, чтобы выполнить сортировку после выбора по возвращенным результатам.
Джефф Этвуд создал блог публикация о том, как некоторые люди вычисляют удобные для человека порядки сортировки.
У вас мог бы быть числовой столбец [CustomerNumberInt], который используется только тогда, когда CustomerNumber является чисто числовым (в противном случае NULL[1]), тогда
ORDER BY CustomerNumberInt, CustomerNumber
[1] в зависимости от того, как ваша версия SQL обрабатывает нули по порядку, вы можете установить значение по умолчанию равным нулю (или бесконечности!)
У меня похожая ужасная ситуация, и я разработал подходящую ужасную функцию для ее решения (SQLServer)
В моей ситуации у меня есть таблица "единиц измерения" (это система отслеживания работы студентов, поэтому единица измерения в данном контексте представляет курс, который они проходят).Единицы измерения имеют код, который по большей части является чисто числовым, но по разным причинам его сделали varchar, и они решили добавить к некоторым префиксам до 5 символов.Таким образом, они ожидают, что 53,123,237,356 будут отсортированы нормально, но также T53, T123, T237, T356
Код единицы измерения - это nvarchar(30)
Вот тело функции:
declare @sortkey nvarchar(30)
select @sortkey =
case
when @unitcode like '[^0-9][0-9]%' then left(@unitcode,1) + left('000000000000000000000000000000',30-(len(@unitcode))) + right(@unitcode,len(@unitcode)-1)
when @unitcode like '[^0-9][^0-9][0-9]%' then left(@unitcode,2) + left('000000000000000000000000000000',30-(len(@unitcode))) + right(@unitcode,len(@unitcode)-2)
when @unitcode like '[^0-9][^0-9][^0-9][0-9]%' then left(@unitcode,3) + left('000000000000000000000000000000',30-(len(@unitcode))) + right(@unitcode,len(@unitcode)-3)
when @unitcode like '[^0-9][^0-9][^0-9][^0-9][0-9]%' then left(@unitcode,4) + left('000000000000000000000000000000',30-(len(@unitcode))) + right(@unitcode,len(@unitcode)-4)
when @unitcode like '[^0-9][^0-9][^0-9][^0-9][^0-9][0-9]%' then left(@unitcode,5) + left('000000000000000000000000000000',30-(len(@unitcode))) + right(@unitcode,len(@unitcode)-5)
when @unitcode like '%[^0-9]%' then @unitcode
else left('000000000000000000000000000000',30-len(@unitcode)) + @unitcode
end
return @sortkey
Я хотел выстрелить себе в лицо после того, как написал это, однако это работает и, кажется, не убивает сервер при запуске.
Я использовал это в SQL SERVER и отлично работал:Здесь решение состоит в том, чтобы дополнить числовые значения символом спереди, чтобы все они имели одинаковую длину строки.
Вот пример использования этого подхода:
select MyCol
from MyTable
order by
case IsNumeric(MyCol)
when 1 then Replicate('0', 100 - Len(MyCol)) + MyCol
else MyCol
end
100 следует заменить фактической длиной этого столбца.