Нам нужно сделать несколько отчетов о значениях, которые обычно представляют собой смешанные строки из цифр и букв, которые должны быть отсортированы «естественно». Такие вещи, как, например, «P7B18» или «P12B3». @ Строки в основном состоят из последовательности букв, а затем цифр. Число этих сегментов и длина каждого могут варьироваться.
Мы бы хотели, чтобы их числовые части сортировались в числовом порядке. Очевидно, что если я просто обработаю эти строковые значения напрямую ORDER BY
, то «P12B3» будет предшествовать «P7B18», поскольку «P1» раньше, чем «P7», но я бы хотел обратного, поскольку «P7» естественным образом предшествует "P12".
Я также хотел бы иметь возможность делать сравнения диапазонов, например, @bin < 'P13S6'
или что-то подобное. Мне не нужно обрабатывать числа с плавающей запятой или отрицательные числа; это будут строго неотрицательные целые числа, с которыми мы имеем дело. Длина строки и количество сегментов могут быть произвольными без фиксированных верхних границ.
В нашем случае строковый регистр не важен, хотя, если есть способ сделать это с учетом параметров сортировки, другие могут найти это полезным. Самая уродливая часть всего этого - я бы хотел иметь возможность упорядочивать и фильтровать диапазон в WHERE
предложении.
Если бы я делал это в C #, это было бы довольно простой задачей: выполнить некоторый анализ, чтобы отделить альфа от числового значения, реализовать IComparable, и вы в основном сделали. Разумеется, SQL Server, похоже, не обладает подобной функциональностью, по крайней мере, насколько мне известно.
Кто-нибудь знает какие-нибудь хорошие приемы, чтобы сделать эту работу? Есть ли какая-то малоизвестная возможность создания пользовательских типов CLR, которые реализуют IComparable и ведут себя так, как ожидается? Я также не против глупых хитростей XML (см. Также: конкатенация списков), и у меня также есть функции оболочки соответствия / извлечения / замены регулярных выражений CLR, доступные на сервере.
РЕДАКТИРОВАТЬ: В качестве более подробного примера я хотел бы, чтобы данные вели себя примерно так.
SELECT bin FROM bins ORDER BY bin
bin
--------------------
M7R16L
P8RF6JJ
P16B5
PR7S19
PR7S19L
S2F3
S12F0
то есть разбить строки на токены всех букв или всех чисел и отсортировать их по алфавиту или по номерам соответственно, причем наиболее левый токен является наиболее значимым термином сортировки. Как я уже упоминал, в .NET легко, если вы реализуете IComparable, но я не знаю, как (или если) вы можете делать такие вещи в SQL Server. Это, конечно, не то, с чем я когда-либо сталкивался за 10 или около того лет работы с ним.
P7B12
могло бы статьP 07 B 12
, то (через ASCII)80 07 65 12
, так80076512
Ответы:
Хотите разумный, эффективный способ сортировки чисел в строках как фактические числа? Рассмотрите возможность голосования за мое предложение Microsoft Connect: поддержка "естественной сортировки" / DIGITSASNUMBERS в качестве параметра сортировки
Нет простых встроенных способов сделать это, но есть возможность:
Нормализуйте строки, переформатируя их в сегменты фиксированной длины:
VARCHAR(50) COLLATE Latin1_General_100_BIN2
. Максимальная длина 50, возможно, должна быть скорректирована на основе максимального количества сегментов и их потенциальной максимальной длины.AFTER [or FOR] INSERT, UPDATE
триггер, так что вы гарантированно правильно установите значение для всех записей, даже тех, которые входящие с помощью специальных запросов и т. д. Конечно, этот скалярный UDF также может обрабатываться с помощью SQLCLR, но его нужно будет проверить, чтобы определить, какой из них действительно более эффективен. **UPPER()
функцию к конечному результату всех сегментов (так что это нужно сделать только один раз, а не для каждого сегмента). Это позволит правильно сортировать данные с учетом двоичного сопоставления столбца сортировки.AFTER INSERT, UPDATE
в таблице триггер, который вызывает UDF для установки столбца сортировки. Чтобы повысить производительность, используйтеUPDATE()
функцию, чтобы определить, находится ли этот столбец кода даже вSET
предложенииUPDATE
оператора (простоRETURN
если false), а затем присоедините псевдотаблицыINSERTED
иDELETED
к столбцу кода, чтобы обрабатывать только те строки, которые имеют изменения в значении кода. , Обязательно укажитеCOLLATE Latin1_General_100_BIN2
в этом условии JOIN, чтобы обеспечить точность определения, есть ли изменение.Пример:
При таком подходе вы можете сортировать по:
И вы можете сделать фильтрацию диапазона с помощью:
или:
И фильтр,
ORDER BY
иWHERE
фильтр должны использовать двоичное сопоставление, определенное дляSortColumn
приоритета сопоставления .Сравнение равенства все равно будет выполнено в исходном столбце значений.
Другие мысли:
Используйте SQLCLR UDT. Это может сработать, хотя неясно, представляет ли это чистый выигрыш по сравнению с подходом, описанным выше.
Да, в SQLCLR UDT операторы сравнения могут быть переопределены пользовательскими алгоритмами. Это обрабатывает ситуации, когда значение сравнивается либо с другим значением того же пользовательского типа, либо с тем, которое необходимо неявно преобразовать. Это должно обрабатывать фильтр диапазона в
WHERE
условии.Что касается сортировки UDT как обычного типа столбца (не вычисляемого столбца), это возможно только в том случае, если UDT имеет «байтовый порядок». Быть «упорядоченным в байтах» означает, что двоичное представление UDT (которое может быть определено в UDT) естественно сортируется в соответствующем порядке. Предполагая, что двоичное представление обрабатывается аналогично подходу, описанному выше для столбца VARCHAR (50), который имеет сегменты фиксированной длины, которые дополняются, это будет соответствовать. Или, если было непросто убедиться, что двоичное представление естественным образом упорядочено надлежащим образом, вы можете предоставить метод или свойство UDT, которые выводят значение, которое будет правильно упорядочено, и затем создать
PERSISTED
вычисляемый столбец для этого. метод или свойство. Метод должен быть детерминированным и помечен какIsDeterministic = true
.Преимущества этого подхода:
Parse
метод UDT принимаетP7B18
значение и преобразует его, вы можете просто вставить значения естественным образом какP7B18
. А с помощью метода неявного преобразования, установленного в UDT, условие WHERE также позволяет использовать просто P7B18`.Последствия такого подхода:
PERSISTED
вычисляемый столбец для свойства или метода UDT, вы получите представление, возвращаемое свойством или методом. Если вам нужно исходноеP7B18
значение, то вам нужно вызвать метод или свойство UDT, которое закодировано, чтобы вернуть это представление. Так как выToString
все равно должны переопределить метод, это хороший вариант для этого.Неясно (по крайней мере для меня сейчас, поскольку я не тестировал эту часть), насколько легко / сложно было бы внести какие-либо изменения в двоичное представление. Изменение сохраненного сортируемого представления может потребовать удаления и повторного добавления поля. Кроме того, удаление сборки, содержащей UDT, не удастся, если используется каким-либо образом, поэтому вы должны убедиться, что в сборке не было ничего кроме этого UDT. Вы можете
ALTER ASSEMBLY
заменить определение, но на это есть некоторые ограничения.С другой стороны,
VARCHAR()
поле - это данные, которые отсоединены от алгоритма, поэтому потребуется только обновить столбец. И если есть десятки миллионов строк (или больше), то это можно сделать в пакетном режиме.Реализовать ICU библиотека , которая фактически позволяет для делать это буквенно - цифровой сортировки. Несмотря на высокую функциональность, библиотека поставляется только на двух языках: C / C ++ и Java. Это означает, что вам может потребоваться внести некоторые изменения, чтобы заставить его работать в Visual C ++, или есть вероятность, что код Java может быть преобразован в MSIL с использованием IKVM . На этом сайте есть один или два проекта стороны .NET, которые предоставляют интерфейс COM, к которому можно получить доступ в управляемом коде, но я считаю, что они не обновлялись в течение некоторого времени, и я не пробовал их. Лучше всего было бы обрабатывать это на уровне приложения с целью генерации ключей сортировки. Ключи сортировки будут сохранены в новом столбце сортировки.
Это может быть не самый практичный подход. Тем не менее, все еще очень круто, что такая способность существует. Я предоставил более подробный обзор этого примера в следующем ответе:
Существует ли сортировка для сортировки следующих строк в следующем порядке 1,2,3,6,10,10A, 10B, 11?
Но шаблон, который рассматривается в этом вопросе, немного проще. Для примера, показывающего, что тип шаблона, рассматриваемый в этом Вопросе, также работает, перейдите на следующую страницу:
ICU Collation Demo
В разделе «Настройки» установите «числовой» параметр на «вкл.», А для всех остальных - «по умолчанию». Далее, справа от кнопки «Сортировка», снимите флажок с «Сила различий» и установите флажок «Ключи сортировки». Затем замените список элементов в текстовой области «Ввод» следующим списком:
Нажмите кнопку «Сортировка». В текстовой области «Вывод» должно отображаться следующее:
Обратите внимание, что ключи сортировки имеют структуру в нескольких полях, разделенных запятыми. Каждое поле должно быть отсортировано независимо, поэтому возникает еще одна небольшая проблема, которую необходимо решить, если необходимо реализовать это в SQL Server.
** Если есть какие-либо сомнения по поводу эффективности использования пользовательских функций, обратите внимание, что предлагаемые подходы используют их минимально. Фактически, главная причина хранения нормализованного значения состояла в том, чтобы избежать вызова UDF для каждой строки каждого запроса. В первичном подходе UDF используется для установки значения
SortColumn
, и это делается только послеINSERT
иUPDATE
через триггер. Выбор значений гораздо более распространен, чем вставка и обновление, и некоторые значения никогда не обновляются. Для каждогоSELECT
запроса, который используетSortColumn
фильтр диапазона вWHERE
предложении, UDF требуется только один раз для каждого из значений range_start и range_end, чтобы получить нормализованные значения; UDF не называется для каждой строки.Что касается UDT, то использование фактически такое же, как и для скалярного UDF. Значение, вставка и обновление будет вызывать метод нормализации один раз для каждой строки, чтобы установить значение. Затем метод нормализации будет вызываться один раз для каждого запроса для каждого range_start и range_value в фильтре диапазона, но не для каждой строки.
Точка в пользу обработки нормализации полностью в SQLCLR UDF является то , что дано это не делает никакого доступа к данным и является детерминированным, если он помечен как
IsDeterministic = true
, то он может участвовать в параллельных планах (которые могли бы помочьINSERT
иUPDATE
операции) , в то время как T-SQL UDF предотвратит использование параллельного плана.источник