ЗАМОВЛЕННЯ ЗА та порівняння змішаних рядків літер та цифр


9

Нам потрібно зробити деяку звітність про значення, які зазвичай є змішаними рядками цифр і букв, які потрібно сортувати "природним шляхом". Такі речі, як, наприклад, "P7B18" або "P12B3". @ Рядки здебільшого будуть послідовностями літер, а потім чергуються цифри. Кількість цих сегментів і довжина кожного можуть змінюватись.

Ми хотіли б, щоб числові їх частини були відсортовані в числовому порядку. Очевидно, що якщо я просто обробляю ці значення рядків безпосередньо ORDER BY, тоді "P12B3" буде перед "P7B18", оскільки "P1" є раніше, ніж "P7", але я хотів би, щоб зворотний бік, оскільки "P7" природно передує "P12".

Я також хотів би мати можливість порівнювати діапазон, наприклад, @bin < 'P13S6'чи щось подібне. Мені не доводиться обробляти плаваючу крапку або від’ємні числа; це будуть суто негативні цілі числа, з якими ми маємо справу. Довжина рядків і кількість сегментів потенційно можуть бути довільними, без фіксованих верхніх меж.

У нашому випадку обшивка струн не є важливою, хоча якщо є спосіб зробити це з урахуванням порівняння, інші можуть вважати це корисним. Найбідніша частина всього цього - я хотів би мати змогу виконати як замовлення, так і фільтрацію діапазону в WHEREпункті.

Якби я робив це в C #, це було б досить простою задачею: зробити кілька розборів, щоб відокремити альфа від числового, реалізувати IComparable, і ви в основному все зробили. Звичайно, SQL Server, схоже, не пропонує подібних функцій, принаймні, наскільки я знаю.

Хтось знає якісь хороші хитрощі, щоб зробити цю роботу? Чи є якась мало оприлюднена здатність створювати власні типи CLR, які реалізовують IComparable і ведуть це так, як очікувалося? Я також не проти Stupid XML Tricks (див. Також: об'єднання списків), і у мене є функції CLR-регексів відповідності / вилучення / заміни обгортки, доступні також на сервері.

EDIT: Як дещо докладніший приклад, я хотів би, щоб дані поводилися приблизно так.

SELECT bin FROM bins ORDER BY bin

bin
--------------------
M7R16L
P8RF6JJ
P16B5
PR7S19
PR7S19L
S2F3
S12F0

тобто розбийте рядки на лексеми всіх букв або всіх чисел і сортуйте їх за алфавітом чи цифрами відповідно, причому крайній лівий жетон є найзначнішим терміном сортування. Як я вже згадував, шматок пирога в .NET, якщо ви реалізуєте IComparable, але я не знаю, як (або якщо) ви можете зробити подібні речі в SQL Server. Це, звичайно, не те, що я коли-небудь стикався за десять років роботи з ним.


Ви можете зробити це за допомогою якогось індексованого обчисленого стовпця, перетворивши рядок у ціле число. Так P7B12могло б стати P 07 B 12тоді (через ASCII) 80 07 65 12, таким чином80076512
Philᵀᴹ

Я пропоную вам створити обчислений стовпчик, який прошиває кожен числовий компонент на велику довжину (тобто 10 нулів). Оскільки формат досить довільний, вам знадобиться досить великий вбудований вираз, але це можливо. Тоді ви можете проіндексувати / замовити по / де в цьому стовпчику скільки завгодно.
Nick.McDermaid

Перегляньте посилання, яке я щойно додав до верхньої частини моєї відповіді :)
Соломон Руцький

1
@srutzky Ніцца, я проголосував за це.
db2

Привіт, db2: через те, що Microsoft переходить з підключення до UserVoice і не точно підтримує підрахунок голосів (вони поміщають це в коментарі, але не впевнені, що це дивляться), можливо, вам доведеться повторно проголосувати за нього: Підтримка "природного сортування" / DIGITSASNUMBERS як варіант зіставлення . Дякую!
Соломон Руцький

Відповіді:


8

Хочете розумний, ефективний засіб сортування чисел у рядках за фактичними числами? Розгляньте голосування за мою пропозицію Microsoft Connect: Підтримка "природного сортування" / DIGITSASNUMBERS як варіант зіставлення


Немає легких вбудованих засобів для цього, але ось така можливість:

Нормалізуйте рядки, переформатувавши їх у сегменти фіксованої довжини:

  • Створіть стовпчик сортування типу VARCHAR(50) COLLATE Latin1_General_100_BIN2. Максимальну довжину 50, можливо, потрібно буде коригувати виходячи з максимальної кількості сегментів та їх потенційної максимальної довжини.
  • Хоча нормалізація може бути виконана на рівні додатків ефективніше, обробка цього в базі даних за допомогою T-SQL UDF дозволить розмістити скалярний UDF в AFTER [or FOR] INSERT, UPDATEтригері, щоб ви гарантували правильне встановлення значення для всіх записів, навіть тих Зрозуміло, що з скалярним UDF можна також обробляти за допомогою SQLCLR, але його потрібно перевірити, щоб визначити, який з них був насправді більш ефективним. **
  • UDF (незалежно від наявності в T-SQL або SQLCLR) повинен:
    • Обробляйте невідому кількість сегментів, читаючи кожен символ і зупиняючись, коли тип переходить з альфа на числовий або числовий на альфа.
    • На кожному сегменті він повинен повертати рядок фіксованої довжини, встановлений на максимально можливі символи / цифри будь-якого сегмента (а може бути, максимум 1 або 2 для врахування майбутнього зростання).
    • Альфа-сегменти мають бути обґрунтованими ліворуч та пробілами.
    • Числові сегменти повинні бути обґрунтованими праворуч і залишені нулями зліва.
    • Якщо альфа-символи можуть входити як змішані регістри, але впорядкування повинно бути нечутливим до регістру, застосуйте UPPER()функцію до кінцевого результату всіх сегментів (так що це потрібно зробити лише один раз, а не на сегмент). Це дозволить правильно сортувати, враховуючи двійкове порівняння стовпця сортування.
  • Створіть AFTER INSERT, UPDATEна столі тригер, який викликає UDF для встановлення стовпця сортування. Для підвищення продуктивності використовуйте UPDATE()функцію, щоб визначити, чи є цей стовпець коду навіть у SETпункті UPDATEзаяви (просто, RETURNякщо він помилковий), а потім приєднайтесь до INSERTEDта DELETEDпсевдо таблиць у кодовому стовпчику лише для обробки рядків, які мають зміни значення коду . Обов’язково вкажіть COLLATE Latin1_General_100_BIN2цю умову JOIN, щоб забезпечити точність визначення того, чи є зміни.
  • Створіть індекс у новому стовпці сортування.

Приклад:

P7B18   -> "P     000007B     000018"
P12B3   -> "P     000012B     000003"
P12B3C8 -> "P     000012B     000003C     000008"

У цьому підході ви можете сортувати за допомогою:

ORDER BY tbl.SortColumn

А ви можете виконати фільтрацію діапазону за допомогою:

WHERE tbl.SortColumn BETWEEN dbo.MyUDF('P7B18') AND dbo.MyUDF('P12B3')

або:

DECLARE @RangeStart VARCHAR(50),
        @RangeEnd VARCHAR(50);
SELECT @RangeStart = dbo.MyUDF('P7B18'),
       @RangeEnd = dbo.MyUDF('P12B3');

WHERE tbl.SortColumn BETWEEN @RangeStart AND @RangeEnd

І фільтр, ORDER BYі WHEREфільтр повинні використовувати бінарне зіставлення, визначене SortColumnзавдяки прецедентності збору.

Порівняння рівності все ще проводитиметься в стовпці початкового значення.


Інші думки:

  • Використовуйте UDT SQLCLR. Це могло б спрацювати, хоча незрозуміло, якщо він представляє чистий прибуток порівняно з описаним вище підходом.

    Так, UDT SQLCLR може мати операторів порівняння, які перекривають користувацькі алгоритми. Це обробляє ситуації, коли значення порівнюється або з іншим значенням, яке вже є тим самим спеціальним типом, або з таким, яке потрібно неявно перетворити. Це повинно працювати з фільтром діапазону в WHEREстані.

    Що стосується сортування UDT як звичайного типу стовпців (а не обчислених стовпців), то це можливо лише в тому випадку, якщо UDT є "упорядкованим байтом". Бути "упорядкованим байтом" означає, що двійкове представлення УДТ (яке можна визначити в УДТ) природно сортує у відповідному порядку. Якщо припустити, що бінарне представлення обробляється аналогічно описаному вище підходу для стовпця VARCHAR (50), який має вкладені сегменти фіксованої довжини, це може кваліфікувати. Або, якби було непросто переконатися, що бінарне представлення буде впорядковано належним чином, ви можете розкрити метод або властивість UDT, що виводить значення, яке було б належним чином упорядковано, а потім створити PERSISTEDобчислений стовпець на цьому метод або властивість. Метод повинен бути детермінованим і позначений як IsDeterministic = true.

    Переваги такого підходу:

    • Немає необхідності в полі "початкове значення".
    • Не потрібно викликати UDF для вставки даних або для порівняння значень. Якщо припустити, що Parseметод UDT приймає P7B18значення і перетворює його, то ви повинні мати можливість просто вставити значення як природні P7B18. І при непрямому методі перетворення, встановленому в UDT, умова WHERE також дозволить використовувати просто P7B18`.

    Наслідки такого підходу:

    • Простий вибір поля поверне бінарне представлення, якщо використовувати впорядкований байт UDT в якості типу даних стовпця. Або якщо ви використовуєте PERSISTEDобчислений стовпець у властивості або методі UDT, ви отримаєте представлення, повернене властивістю або методом. Якщо ви хочете вихідне P7B18значення, то вам потрібно викликати метод або властивість UDT, кодованих для повернення цього подання. Оскільки вам доведеться все-таки перекрити ToStringметод, це хороший кандидат для його надання.
    • Незрозуміло (принаймні, мені зараз, оскільки я не перевіряв цю частину), наскільки легко / важко було б внести будь-які зміни у бінарне представлення. Зміна збереженого сортового подання може зажадати відміни та повторного додавання поля. Крім того, скасування збірки, що містить UDT, не вдасться, якщо використовується будь-яким способом, тому ви хочете переконатися, що крім цього УДТ нічого іншого в Асамблеї не було. Ви можете ALTER ASSEMBLYзамінити визначення, але на це є деякі обмеження.

      З іншого боку, VARCHAR()поле - це дані, від'єднані від алгоритму, тому вимагатиме лише оновлення стовпця. І якщо є десятки мільйонів рядків (або більше), то це можна зробити за допомогою підхідного пакету.

  • Реалізуйте бібліотеку ICU, яка фактично дозволяє проводити це алфавітно-цифрове сортування. Хоча високофункціональна, бібліотека надходить лише двома мовами: C / C ++ та Java. Це означає, що вам може знадобитися зробити деякі налаштування, щоб змусити його працювати у Visual C ++, або є ймовірність виключення того, що код Java може бути перетворений у MSIL за допомогою IKVM . На цьому веб-сайті пов’язані один або два .NET-побічні проекти, які надають COM-інтерфейс, до якого можна отримати доступ у керованому коді, але я вважаю, що вони не оновлювались протягом певного часу, і я не пробував їх. Найкраще тут було б вирішити це в шарі програми з метою створення ключів сортування. Потім клавіші сортування будуть збережені в новий стовпчик сортування.

    Це може бути не найбільш практичним підходом. Однак все ще дуже круто, що така здатність існує. Я детальніше ознайомився з прикладом цього в наступній відповіді:

    Чи є порівняння для сортування наступних рядків у такому порядку 1,2,3,6,10,10A, 10B, 11?

    Але модель, якою займаються в цьому питанні, трохи простіша. Для прикладу, який показує, що тип шаблону, який розглядається в цьому запитанні, також працює, перейдіть на наступну сторінку:

    Демографічний показник співпраці ICU

    У розділі "Налаштування" встановіть для параметра "число" значення "увімкнено", а всі інші слід встановити на "за замовчуванням". Далі, праворуч від кнопки "сортувати", зніміть прапорець для "різної сили" та поставте прапорець "сортувати клавіші". Потім замініть перелік елементів у текстовій області "Введення" таким списком:

    P12B22
    P7B18
    P12B3
    as456456hgjg6786867
    P7Bb19
    P7BA19
    P7BB19
    P007B18
    P7Bb20
    P7Bb19z23
    

    Натисніть кнопку "сортувати". У текстовій області "Вихід" має відображатися наступне:

    as456456hgjg6786867
        29 4D 0F 7A EA C8 37 35 3B 35 0F 84 17 A7 0F 93 90 , 0D , , 0D .
    P7B18
        47 0F 09 2B 0F 14 , 08 , FD F1 , DC C5 DC 05 .
    P007B18
        47 0F 09 2B 0F 14 , 08 , FD F1 , DC C5 DC 05 .
    P7BA19
        47 0F 09 2B 29 0F 15 , 09 , FD FF 10 , DC C5 DC DC 05 .
    P7Bb19
        47 0F 09 2B 2B 0F 15 , 09 , FD F2 , DC C5 DC 06 .
    P7BB19
        47 0F 09 2B 2B 0F 15 , 09 , FD FF 10 , DC C5 DC DC 05 .
    P7Bb19z23
        47 0F 09 2B 2B 0F 15 5B 0F 19 , 0B , FD F4 , DC C5 DC 08 .
    P7Bb20
        47 0F 09 2B 2B 0F 16 , 09 , FD F2 , DC C5 DC 06 .
    P12B3
        47 0F 0E 2B 0F 05 , 08 , FD F1 , DC C5 DC 05 .
    P12B22
        47 0F 0E 2B 0F 18 , 08 , FD F1 , DC C5 DC 05 .
    

    Зауважте, що клавіші сортування - це структура в декількох полях, розділених комами. Кожне поле потрібно сортувати самостійно, щоб представити ще одну невелику проблему, яку потрібно вирішити, якщо потрібно реалізувати це в SQL Server.


** Якщо є якісь занепокоєння щодо продуктивності щодо використання визначених користувачем функцій, зауважте, що запропоновані підходи використовують їх мінімально. Насправді, основною причиною зберігання нормованого значення було уникнення виклику UDF для кожного рядка кожного запиту. У первинному підході UDF використовується для встановлення значення SortColumn, і це робиться тільки після INSERTі UPDATEчерез тригер. Вибір значень набагато частіше, ніж вставлення та оновлення, і деякі значення ніколи не оновлюються. Для кожного SELECTзапиту, який використовує SortColumnфільтр для діапазону в WHEREпункті, UDF потрібен лише один раз на кожен з значень range_start і range_end, щоб отримати нормалізовані значення; UDF не називається рядком.

Що стосується УДТ, то використання фактично таке ж, як і у скалярної АДС. Значення, вставка та оновлення вимагатиме методу нормалізації один раз у кожному рядку для встановлення значення. Тоді метод нормалізації буде викликатися один раз за запитом для кожного діапазону_старт та діапазону_значення у фільтрі діапазону, але не в рядку.

Точка на користь обробки нормалізації повністю в SQLCLR UDF є те , що дано це не робить ніякого доступу до даних і є детермінованим, якщо він позначений як IsDeterministic = true, то він може брати участь у паралельних планах (які могли б допомогти INSERTі UPDATEоперації) , в той час як T-SQL UDF не дозволить використовувати паралельний план.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.