Правильная сортировка точек, хранящихся как символы в SQL Server - PullRequest
4 голосов
/ 23 июня 2011

У меня есть таблица SQL, в которой хранится пользовательский номер элемента. У каждого из них может быть оторван ребенок с разделителем .. У каждого из них тоже может быть ребенок.

Пример того, что это может быть (опять же, динамический, не знаю, что это будет):

Item Number
1
1.1
1.1.1
1.1.1.1
1.1.1.1.a
1.1.1.1.b
10
11
2.1
2.10
2.2
2.20
20
3
30

То, что делает это жестким, состоит в том, что эти числа создаются на лету и не обязательно в порядке. Вы можете создать 5 чисел (1, 2, 3, 4, 5), а затем создать дочернего элемента 1, чтобы он не сохранялся в порядке в БД.

Как выбрать из таблицы и упорядочить по Item Number, чтобы она правильно отображалась, как указано выше, когда данные не сохраняются в этом порядке?

Большинство решений, которые я пробовал, дают мне 1, 2, 3, 4, 5...1.1, 1.2 OR 1, 1.1, 1.1.1, 10, 11...2, 2.1, 20....3, 30, etc.

Ответы [ 7 ]

7 голосов
/ 23 июня 2011

Если у вас SQL 2008, вы можете использовать новый тип данных иерархии:

WITH Items (ItemNumber) AS (
    SELECT '1' UNION ALL SELECT '1.1' UNION ALL SELECT '1.1.1'
    UNION ALL SELECT '10' UNION ALL SELECT '11' UNION ALL SELECT '2'
    UNION ALL SELECT '2.1' UNION ALL SELECT '20' UNION ALL SELECT '3'
    UNION ALL SELECT '30'
)
SELECT *
FROM Items 
ORDER BY Convert(hierarchyid, '/' + ItemNumber + '/');
5 голосов
/ 01 июля 2011

Я обсуждал это на другом форуме, где мы придумали XML-решение, которое было очень динамичным. Адам Хейнс помог оптимизировать его, что значительно улучшило производительность. Эта версия содержит исправление для правильной сортировки букв алфавита.

Учитывая следующие значения:

declare @temp table (id varchar(255))

insert into @temp (id) values
  ('1.1.a.1'),('1.1.aa.2'),
  ('1.1.b.3'),('1.1.a.4'),
  ('1.1.a.5'),('1.1.a.6'),
  ('1.1.a.7'),('1.1.a.8'),
  ('1.1.a.9'),('1.1.a.10'),
  ('1.1.a.11'),('1.1.b.1'),
  ('1.1.b.2'),('1.2.a.1'),
  ('1.10.a.1'),('1.11.a.1'),
  ('1.20.a.1'),('101.20.a.2'),
  ('1.20.a.150'),('1.1'),
  ('1.2'),('1')

Этот запрос:

declare @xml xml,
        @max_len int

set @xml =
(
select id as id, cast('<i>' + replace(id,'.','</i><i>') + '</i>' as xml)
from @temp
for xml path('id_root'),type
)

select @max_len = max(len(x.i.value('.','varchar(10)')))
from @xml.nodes('/id_root/i') x(i)

select [id]--, srt.srtvalue
from @temp
cross apply(
    select case when ISNUMERIC(x.i.value('.','varchar(10)')) = 1 then right(replicate('0',@max_len) + x.i.value('.','varchar(10)'),@max_len) else x.i.value('.','varchar(10)') end + '.'
    from @xml.nodes('/id_root/i') x(i)
    where x.i.value('../id[1]','varchar(50)') = [@temp].id
    for xml path('')
) as srt(srtvalue)
order by srt.srtvalue

Возвращает эти значения:

id
1
1.1
1.1.a.1
1.1.a.4
1.1.a.5
1.1.a.6
1.1.a.7
1.1.a.8
1.1.a.9
1.1.a.10
1.1.a.11
1.1.aa.2
1.1.b.1
1.1.b.2
1.1.b.3
1.2
1.2.a.1
1.10.a.1
1.11.a.1
1.20.a.1
1.20.a.150
101.20.a.2

Если у вас более 10 символов в одной цифре, вам придется соответствующим образом изменить varchar (10).

- Джеймс

1 голос
/ 23 июня 2011

Это скорее шутка, чем реальный ответ.Если

  • в ваших категориях есть максимум 4 уровня
  • , вы действительно не заботитесь о преформансе

, попробуйте следующее:

WITH Items (ItemNumber) AS (
              SELECT '1' UNION ALL SELECT '1.1' UNION ALL SELECT '1.1.1'
    UNION ALL SELECT '-1' UNION ALL SELECT '1.-1' UNION ALL SELECT '1.-1.1'
    UNION ALL SELECT '10' UNION ALL SELECT '11' UNION ALL SELECT '2'
    UNION ALL SELECT '1.2000' UNION ALL SELECT '1.-2000' UNION ALL SELECT '2.1'
    UNION ALL SELECT '2.2' UNION ALL SELECT '20' UNION ALL SELECT '3'
    UNION ALL SELECT '30' UNION ALL SELECT '30.1' UNION ALL SELECT '10.10'
    UNION ALL SELECT '1.-10' UNION ALL SELECT '1.1.1.1'
)

SELECT ItemNumber
FROM 
  ( SELECT
          ItemNumber
        , CASE WHEN ItemNumber LIKE '%.%.%.%' THEN ItemNumber
               WHEN ItemNumber LIKE '%.%.%' THEN ItemNumber + '.0'
               WHEN ItemNumber LIKE '%.%' THEN ItemNumber + '.0.0'
               ELSE ItemNumber + '.0.0.0'
          END AS ItemNumberToParse
    FROM Items
  ) AS tmp
ORDER BY CAST(PARSENAME(ItemNumberToParse, 4) AS INT),
         CAST(PARSENAME(ItemNumberToParse, 3) AS INT),
         CAST(PARSENAME(ItemNumberToParse, 2) AS INT),
         CAST(PARSENAME(ItemNumberToParse, 1) AS INT) ;

Результат:

  ItemNumber
    -1
    1.-2000
    1.-10
    1.-1
    1.-1.1
    1
    1.1
    1.1.1
    1.1.1.1
    1.2000
    2
    2.1
    2.2
    3
    10
    10.10
    11
    20
    30
    30.1
1 голос
/ 23 июня 2011

Если вы хотите отсортировать объекты по числовому значению, не храните их как nvarchar.

Решение ACTUAL состоит в том, чтобы сделать эти числа своими собственными int полями, например, Version, Versiona, Versionb ...

Тогда ORDER BY Version, Versiona, Versionb

Если вы храните числа в виде символов, не ожидайте, что они будут работать как числа.

0 голосов
/ 30 апреля 2013

попробуйте этот запрос по порядку по месту, где он работает в моем случае.У меня такое же значение формата версии в столбце varchar (max)

order by CAST (Substring( ItemNumberToParse, 1, CharIndex( '.', ItemNumberToParse ) - 1)as int)
0 голосов
/ 23 июня 2011

Некоторые вопросы:

  • Сколько может быть подкатегорий?
  • Это всегда будут просто цифры или они могут быть буквами?
  • Что такоенаибольшее число, которое когда-либо могло бы быть одним значением между точками?

Если вы используете SQL 2008, тогда я рекомендую ответ @ Vito, поскольку это, безусловно, лучший способ.

Есливы используете более раннюю версию, тогда вам придется поработать.

Вот версия SQL 2005.Я предположил, что ответы на поставленные выше вопросы - 100, всегда только цифры, и 9999999999 (10 цифр).

WITH Items (ItemNumber) AS (
    SELECT '1' UNION ALL SELECT '1.1' UNION ALL SELECT '1.1.1'
    UNION ALL SELECT '10' UNION ALL SELECT '11' UNION ALL SELECT '2'
    UNION ALL SELECT '2.1' UNION ALL SELECT '20' UNION ALL SELECT '3'
    UNION ALL SELECT '30' UNION ALL SELECT '9999999999.9999999999'
), Padded AS (
   SELECT
      ItemNumber,
      Convert(nvarchar(max), '') SortValue,
      ItemNumber Remainder,
      0 Selector
   FROM Items
   UNION ALL
   SELECT
      ItemNumber,
      SortValue + Right('000000000' + Left(Remainder, CharIndex('.', Remainder + '.') - 1), 10),
      Substring(Remainder, CharIndex('.', Remainder + '.') + 1, 2147483647),
      CASE WHEN Remainder LIKE '%.%' THEN 0 ELSE 1 END
   FROM Padded
   WHERE
      Remainder <> ''
)
SELECT ItemNumber
FROM Padded
WHERE Selector = 1
ORDER BY SortValue;

Для SQL 2000 будет немного сложнее ...

0 голосов
/ 23 июня 2011

Это прекрасно работает для меня:

select cast(ItemNumber as nvarchar(25))
from (
    select '3.1' as ItemNumber
    union all
    select '1'
    union all
    select '3.2'
    union all
    select '3.2.1'
    union all
    select '3.2.1.1'
    union all
    select '3.2.1.2'
    union all
    select '2'
    union all
    select '3'
    union all
    select '3.1.1'
) a
order by ItemNumber

Вывод:

-------------------------
1
2
3
3.1
3.1.1
3.2
3.2.1
3.2.1.1
3.2.1.2

(9 row(s) affected)
...