Сортировать строку как число в sql server

у меня есть столбец, содержащий такие данные. тире указывают несколько копий одного и того же счета, и они должны быть отсортированы в порядке возрастания

790711
790109-1
790109-11
790109-2

Я должен сортировать его в порядке возрастания на это число, но так как это поле varchar, оно сортируется в алфавитном порядке, как это

790109-1
790109-11
790109-2
790711

чтобы исправить это, я попытался заменить- (тире) пустым, а затем бросить его как число, а затем отсортировать по этому

select cast(replace(invoiceid,'-','') as decimal) as invoiceSort...............order by invoiceSort asc

пока это лучше и вроде этого

            invoiceSort
790711      (790711)   <-----this is wrong now as it should come later than 790109
790109-1    (7901091)
790109-2    (7901092)
790109-11   (79010911)

кто - то предложил мне разделить идентификатор счета-фактуры на - (тире ) и заказать по 2 разделенным частям

как=====> order by split1 asc,split2 asc (790109,1)

который будет работать, я думаю, но как бы я разделил столбец.

различные функции разделения в интернете-это те, которые возвращают таблицу, в то время как в этом случае мне потребуется скалярная функция.

есть ли другие подходы, которые можно использовать? Данные показанный в представлении сетки и представлении сетки не поддерживает сортировку по 2 столбцам по умолчанию (я могу реализовать его, Хотя:)), поэтому, если есть какие-либо более простые подходы, мне было бы очень приятно.

редактировать : спасибо за все ответы. Хотя каждый ответ правильный, я выбрал ответ, который позволил мне включить эти столбцы в сортировку GridView с минимальным повторным факторингом sql-запросов.

9 ответов


рациональное использование REVERSE, CHARINDEX и SUBSTRING, может получить нам то, что мы хотим. Я использовал имена столбцов в моем коде ниже, чтобы проиллюстрировать, что происходит.

настройка выборочных данных:

DECLARE @Invoice TABLE (
    InvoiceNumber nvarchar(10)
);

INSERT @Invoice VALUES
('790711')
,('790709-1')
,('790709-11')
,('790709-21')
,('790709-212')
,('790709-2')

SELECT * FROM @Invoice

пример данных:

InvoiceNumber
-------------
790711
790709-1
790709-11
790709-21
790709-212
790709-2

и вот код. У меня есть ноющее чувство, что последние выражения могут быть упрощены.

SELECT 
    InvoiceNumber
    ,REVERSE(InvoiceNumber) 
        AS Reversed
    ,CHARINDEX('-',REVERSE(InvoiceNumber)) 
        AS HyphenIndexWithinReversed
    ,SUBSTRING(REVERSE(InvoiceNumber),1+CHARINDEX('-',REVERSE(InvoiceNumber)),LEN(InvoiceNumber)) 
        AS ReversedWithoutAffix
    ,SUBSTRING(InvoiceNumber,1+LEN(SUBSTRING(REVERSE(InvoiceNumber),1+CHARINDEX('-',REVERSE(InvoiceNumber)),LEN(InvoiceNumber))),LEN(InvoiceNumber)) 
        AS AffixIncludingHyphen
    ,SUBSTRING(InvoiceNumber,2+LEN(SUBSTRING(REVERSE(InvoiceNumber),1+CHARINDEX('-',REVERSE(InvoiceNumber)),LEN(InvoiceNumber))),LEN(InvoiceNumber)) 
        AS AffixExcludingHyphen
    ,CAST(
        SUBSTRING(InvoiceNumber,2+LEN(SUBSTRING(REVERSE(InvoiceNumber),1+CHARINDEX('-',REVERSE(InvoiceNumber)),LEN(InvoiceNumber))),LEN(InvoiceNumber))
        AS int)  
        AS AffixAsInt
    ,REVERSE(SUBSTRING(REVERSE(InvoiceNumber),1+CHARINDEX('-',REVERSE(InvoiceNumber)),LEN(InvoiceNumber))) 
        AS WithoutAffix
FROM @Invoice
ORDER BY
    -- WithoutAffix
    REVERSE(SUBSTRING(REVERSE(InvoiceNumber),1+CHARINDEX('-',REVERSE(InvoiceNumber)),LEN(InvoiceNumber))) 
    -- AffixAsInt
    ,CAST(
        SUBSTRING(InvoiceNumber,2+LEN(SUBSTRING(REVERSE(InvoiceNumber),1+CHARINDEX('-',REVERSE(InvoiceNumber)),LEN(InvoiceNumber))),LEN(InvoiceNumber))
        AS int)

выход:

InvoiceNumber Reversed   HyphenIndexWithinReversed ReversedWithoutAffix AffixIncludingHyphen AffixExcludingHyphen AffixAsInt  WithoutAffix
------------- ---------- ------------------------- -------------------- -------------------- -------------------- ----------- ------------
790709-1      1-907097   2                         907097               -1                   1                    1           790709
790709-2      2-907097   2                         907097               -2                   2                    2           790709
790709-11     11-907097  3                         907097               -11                  11                   11          790709
790709-21     12-907097  3                         907097               -21                  21                   21          790709
790709-212    212-907097 4                         907097               -212                 212                  212         790709
790711        117097     0                         117097                                                         0           790711

обратите внимание, что все, что вам действительно нужно, это ORDER BY предложение, остальное просто показать мою работу, которая идет следующим образом:

  • переверните строку, найдите дефис, получите подстроку после дефиса, переверните эту часть: это число без какого-либо аффикса
  • длина (число без какого-либо аффикса) говорит нам, сколько символов нужно отбросить с самого начала, чтобы получить аффикс, включая дефис. Отбросьте дополнительный символ, чтобы получить только числовую часть, и преобразуйте это в int. К счастью мы получаем перерыв от SQL Server в том, что это преобразование дает ноль для пустой строки.
  • наконец, получив эти две части, мы просто ORDER BY (число без какого-либо аффикса), а затем (числовое значение аффикса). Это последний приказ, который мы ищем.

код был бы более кратким, если бы SQL Server позволил нам сказать SUBSTRING(value, start) чтобы получить строку, начиная с этого момента, но это не так, поэтому мы должны сказать SUBSTRING(value, start, LEN(value)) много.


попробуйте это -

запрос:

DECLARE @Invoice TABLE (InvoiceNumber VARCHAR(10))
INSERT @Invoice 
VALUES
      ('790711')
    , ('790709-1')
    , ('790709-21')
    , ('790709-11')
    , ('790709-211')
    , ('790709-2')

;WITH cte AS 
(
    SELECT 
          InvoiceNumber
        , lenght = LEN(InvoiceNumber)
        , delimeter = CHARINDEX('-', InvoiceNumber)
    FROM @Invoice
)
SELECT InvoiceNumber
FROM cte
CROSS JOIN (
    SELECT repl = MAX(lenght - delimeter)
    FROM cte
    WHERE delimeter != 0
) mx
ORDER BY 
      SUBSTRING(InvoiceNumber, 1, ISNULL(NULLIF(delimeter - 1, -1), lenght))
    , RIGHT(REPLICATE('0', repl) + SUBSTRING(InvoiceNumber, delimeter + 1, lenght), repl)

выход:

InvoiceNumber
-------------
790709-1
790709-2
790709-11
790709-21
790709-211
790711

попробуй такое

SELECT invoiceid FROM Invoice
ORDER BY 
CASE WHEN PatIndex('%[-]%',invoiceid) > 0
      THEN LEFT(invoiceid,PatIndex('%[-]%',invoiceid)-1)
      ELSE invoiceid END * 1
,CASE WHEN PatIndex('%[-]%',REVERSE(invoiceid)) > 0
      THEN RIGHT(invoiceid,PatIndex('%[-]%',REVERSE(invoiceid))-1)
      ELSE NULL END * 1

Демо SQLFiddle

выше запрос использует два оператора case

  1. сортирует первую часть Invoiceid 790109-1 (например: 790709)
  2. сортирует вторую часть Invoiceid после разделения на ' - ' 790109-1 (например: 1)

для детального понимания проверьте ниже SQLfiddle

Подробная SQLFiddle Демо

или использовать 'Функция charindex'

SELECT invoiceid FROM Invoice
ORDER BY 
CASE WHEN CHARINDEX('-', invoiceid) > 0
      THEN LEFT(invoiceid, CHARINDEX('-', invoiceid)-1)
      ELSE invoiceid END * 1
,CASE WHEN CHARINDEX('-', REVERSE(invoiceid)) > 0
      THEN RIGHT(invoiceid, CHARINDEX('-', REVERSE(invoiceid))-1)
      ELSE NULL END * 1

заказ по каждой части отдельно-это самый простой и надежный путь, зачем искать другие подходы? Взгляните на этот простой вопрос.

select *
from Invoice
order by Convert(int, SUBSTRING(invoiceid, 0, CHARINDEX('-',invoiceid+'-'))) asc,
         Convert(int, SUBSTRING(invoiceid, CHARINDEX('-',invoiceid)+1, LEN(invoiceid)-CHARINDEX('-',invoiceid))) asc

много хороших ответов здесь, но я думаю, что это может быть самый компактный порядок по предложению, которое эффективно:

SELECT *
FROM Invoice
ORDER BY LEFT(InvoiceId,CHARINDEX('-',InvoiceId+'-'))
         ,CAST(RIGHT(InvoiceId,CHARINDEX('-',REVERSE(InvoiceId)+'-'))AS INT)DESC

демо: - SQL Fiddle

Примечание. я добавил версию "790709" в свой тест, так как некоторые из перечисленных здесь методов не обрабатывают версию без суффикса как меньшую, чем версии с суффиксом.

Если ваш invoiceID изменяется по длине, перед "-" , То есть, вам нужно:

SELECT *
FROM Invoice
ORDER BY CAST(LEFT(list,CHARINDEX('-',list+'-')-1)AS INT)
         ,CAST(RIGHT(InvoiceId,CHARINDEX('-',REVERSE(InvoiceId)+'-'))AS INT)DESC

демо с различные длины перед тире:SQL Fiddle


мой вариант:

declare @Len int
select @Len = (select max (len (invoiceid) -  charindex ( '-', invoiceid))-1 from MyTable)

select 
invoiceid ,
cast (SUBSTRING (invoiceid ,1,charindex ( '-', invoiceid )-1) as int) * POWER (10,@Len) + 
cast (right(invoiceid, len (invoiceid) -  charindex ( '-', invoiceid)  ) as int )
from MyTable

вы можете реализовать это как новый столбец в своей таблице:

ALTER TABLE MyTable ADD COLUMN invoice_numeric_id int null
GO

declare @Len int
select @Len = (select max (len (invoiceid) -  charindex ( '-', invoiceid))-1 from MyTable)


UPDATE TABLE MyTable
SET  invoice_numeric_id = cast (SUBSTRING (invoiceid ,1,charindex ( '-', invoiceid )-1) as int) * POWER (10,@Len) + 
    cast (right(invoiceid, len (invoiceid) -  charindex ( '-', invoiceid)  ) as int )

один из способов-разделить InvoiceId в его части, а затем отсортировать на части. Здесь я использую производную таблицу, но это можно сделать с помощью CTE или временной таблицы.

select InvoiceId, InvoiceId1, InvoiceId2
from
(
    select
    InvoiceId,
    substring(InvoiceId, 0, charindex('-', InvoiceId, 0)) as InvoiceId1,
    substring(InvoiceId, charindex('-', InvoiceId, 0)+1, len(InvoiceId)) as InvoiceId2
    FROM Invoice
) tmp
order by
cast((case when len(InvoiceId1) > 0 then InvoiceId1 else InvoiceId2 end) as int),
cast((case when len(InvoiceId1) > 0 then InvoiceId2 else '0' end) as int)

выше InvoiceId1 и InvoiceId2 - являются составными частями InvoiceId. The внешний select включает в себя детали, но только для демонстрационных целей - вам не нужно делать это в вашем выборе.

производная таблица (внутренняя select) схватил InvoiceId также как составные части. Как это работает:

  • когда есть тире в InvoiceId, InvoiceId1 будет содержать первую часть числа и InvoiceId2 будет содержать второго.
  • когда нет тире,InvoiceId1 будет пустым и InvoiceId2 будет содержать все число.

втором случае выше (без тире) не является оптимальным, потому что в идеале InvoiceId1 будет содержать число и InvoiceId2 будет пустой. Чтобы сделать внутреннюю работу select оптимально уменьшит читаемость select. Я выбрал неоптимальный, более читаемый подход, поскольку он достаточно хорош для сортировки.

вот почему ORDER BY тесты предложения для длины-он должен обрабатывать два случая выше.

демо на SQL Fiddle


разбейте сортировку на две части:

SQL Fiddle

Настройка схемы MS SQL Server 2008:

CREATE TABLE TestData
(
  data varchar(20)
)

INSERT TestData
SELECT '790711' as data
UNION
    SELECT '790109-1'
UNION
    SELECT '790109-11'
UNION 
    SELECT '790109-2'

запрос 1:

SELECT *
FROM TestData
ORDER BY 
    FLOOR(CAST(REPLACE(data, '-', '.') AS FLOAT)),
    CASE WHEN CHARINDEX('-', data) > 0 
        THEN CAST(RIGHT(data, len(data) - CHARINDEX('-', data)) AS INT)
        ELSE 0 
    END

результаты:

|      DATA |
-------------
|  790109-1 |
|  790109-2 |
| 790109-11 |
|    790711 |

попробуй:

select invoiceid  ... order by Convert(decimal(18, 2), REPLACE(invoiceid, '-', '.'))