SQL:按字母和数字的子字符串排序

时间:2016-05-04 21:19:05

标签: sql sql-server-2008

如何在现有表格中选择以下数据,并按字母和数字组合排序。这是样本......

A-1
A-10
A-2
A-3
A-4
A-5
A-6
A-7
A-8
A-9
A-3a
A-3b
A-3c
B-1
B-10
B-11
B-12
B-12a
B-12b
B-13
B-2
B-3
B-4
B-5
B-6
B-7
B-8
B-9

3 个答案:

答案 0 :(得分:2)

我将此作为一个新答案,因为它不是一个真正的答案,而是对不同方法的比较:

结论:

  • 所有方法都是相当线性的,除了XML
  • XML行数较少,但行数较多时会变得更糟

创建测试场景

CREATE TABLE #tbl (ID INT IDENTITY,sortColumn VARCHAR(100));
INSERT INTO #tbl VALUES
 ('A-1')
,('A-10')
,('A-2')
,('A-3')
,('A-4')
,('A-5')
,('A-6')
,('A-7')
,('A-8')
,('A-9')
,('A-3a')
,('A-3b')
,('A-3c')
,('B-1')
,('B-10')
,('B-11')
,('B-12')
,('B-12a')
,('B-12b')
,('B-13')
,('B-2')
,('B-3')
,('B-4')
,('B-5')
,('B-6')
,('B-7')
,('B-8')
,('A-8a')
,('B-8')
,('B-9'); --30 rows
GO 1000  -- x 1.000 = 30.000 rows

马特的方法(清理到必要的)

  • 在3 mio行上46秒
  • 在300.000行上4.5秒
  • 在30.000行上1.3秒
  • 在3.000行
  • 上0.7秒

代码

SELECT ID,sortColumn
FROM
    #tbl
ORDER BY
LEFT(sortColumn,CHARINDEX('-',sortColumn) -1)
,CAST((CASE
    WHEN ISNUMERIC(SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,3)) = 1 THEN SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,3)
    WHEN ISNUMERIC(SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,2)) = 1 THEN SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,2)
    WHEN ISNUMERIC(SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,1)) = 1 THEN SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,1)
    ELSE NULL
END) AS INT)
,RIGHT(sortColumn,
    LEN(sortColumn) - 
    LEN(LEFT(sortColumn,CHARINDEX('-',sortColumn) -1)) 
    - LEN(CASE
            WHEN ISNUMERIC(SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,3)) = 1 THEN SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,3)
            WHEN ISNUMERIC(SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,2)) = 1 THEN SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,2)
            WHEN ISNUMERIC(SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,1)) = 1 THEN SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,1)
            ELSE NULL
    END)
    - 1 --the '-'
),ID;

CROSS APPLY中的逐步计算,对计算列进行排序

  • 在3 mio行上44秒
  • 在300.000行上4.4秒
  • 在30.000行
  • 上0.9秒
  • 在3.000行
  • 上0.3秒

代码

SELECT ID,sortColumn
FROM #tbl
CROSS APPLY(SELECT CHARINDEX('-',sortColumn) AS posMinus) AS pos
CROSS APPLY(SELECT SUBSTRING(sortColumn,1,posMinus-1) AS part1
                  ,SUBSTRING(sortColumn,posMinus+1,1000) AS part2
            ) AS parts
CROSS APPLY(SELECT ISNUMERIC(part2) AS p2isnum) AS checknum
CROSS APPLY(SELECT CASE WHEN p2isnum=1 THEN '' ELSE RIGHT(part2,1) END AS part3
                  ,CASE WHEN p2isnum=1 THEN part2 ELSE SUBSTRING(part2,1,LEN(part2)-1) END AS part2New
           ) AS partsNew
ORDER BY part1,part2new,part3,ID;

CROSS APPLY中的逐步计算,对连接的填充字符串进行排序

  • 在3 mio行上42秒
  • 在300.000行上4.2秒
  • 在30.000行
  • 上0.7秒
  • 在3.000行
  • 上0.4秒

代码

SELECT ID,sortColumn
FROM #tbl
CROSS APPLY(SELECT CHARINDEX('-',sortColumn) AS posMinus) AS pos
CROSS APPLY(SELECT SUBSTRING(sortColumn,1,posMinus-1) AS part1
                  ,SUBSTRING(sortColumn,posMinus+1,1000) AS part2
            ) AS parts
ORDER BY RIGHT('.....' + part1,5) + RIGHT('.....' + part2,5 - ISNUMERIC(RIGHT(part2,1)))
        ,ID;

使用XML拆分,对连接的填充字符串进行排序

  • 在3 mio行上67秒
  • 300.000行的6.2秒
  • 在30.000行
  • 上0.7秒
  • 在3.000行
  • 上0.3秒

代码

SELECT ID,sortColumn
FROM
(
    SELECT CAST('<r>' + REPLACE(sortColumn,'-','</r><r>') + '</r>' AS XML) AS SortColumnSplitted
          ,*
    FROM #tbl
) AS tbl
ORDER BY RIGHT('.....' + SortColumnSplitted.value('r[1]','varchar(max)'),5) + RIGHT('.....' + SortColumnSplitted.value('r[2]','varchar(max)'),5 - ISNUMERIC(RIGHT(SortColumnSplitted.value('r[2]','varchar(max)'),1)))
        ,ID;

答案 1 :(得分:1)

最强大的解决方案是创建SQL CLR function。但是,这有点难度。

另一种方法是编写一个插入/更新触发器,它使用TSQL将混合列中的值拆分,并将三个部分(字符,数字,字符)存储在特定的帮助列(可用于排序)中。 根据您的示例,您可以尝试按照此代码的顺序进行拆分:

declare @value nvarchar(10) = 'B-12b';

-- first part
select substring(@value, 1, 1)

-- second part
select case when isnumeric(right(@value, 1)) = 1
    then substring(@value, 3, len(@value) - 2)
    else substring(@value, 3, len(@value) - 3)
    end

-- third part
select case when isnumeric(right(@value, 1)) = 1
    then '_'
    else right(@value, 1)
    end

答案 2 :(得分:1)

我同意托马斯,但我也有很多通过CLR公开的.Net Regex和String函数。我们稍微使用的其他技术是用户定义的函数,它们递归地逐个字符地去除非期望的字符(例如,在查找数字时没有alpha,在查找alpha时没有数字)。但在特定情况下,如果您知道格式非常标准,您可以使用ISNUMERIC,SUBSTRINGS等组合来轻松实现目标。例如。如果你知道它总是: Alpha +&#34; - &#34; +数字(1-3位)+ alpha 您可以执行以下操作,它会将alpha排序为alpha,将numeric排序为数字,将alpha排序为alpha。

DECLARE @Values AS TABLE (Value VARCHAR(5))

INSERT INTO @Values (Value)
    VALUES ('A-1')
    ,('A-10')
    ,('A-2')
    ,('A-3')
    ,('A-4')
    ,('A-5')
    ,('A-6')
    ,('A-7')
    ,('A-8')
    ,('A-9')
    ,('A-3a')
    ,('A-3b')
    ,('A-3c')
    ,('B-1')
    ,('B-10')
    ,('B-11')
    ,('B-12')
    ,('B-12a')
    ,('B-12b')
    ,('B-13')
    ,('B-2')
    ,('B-3')
    ,('B-4')
    ,('B-5')
    ,('B-6')
    ,('B-7')
    ,('B-8')
    ,('B-9')

SELECT
    *
    ,FirstAlphaSection = LEFT(Value,CHARINDEX('-',Value) -1)
    ,SecondNumericSection = CASE
       WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,3)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,3)
       WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,2)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,2)
       WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,1)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,1)
       ELSE NULL
    END
    ,ThirdAlphaSection =
       RIGHT(Value,
          LEN(Value) - 
          LEN(LEFT(Value,CHARINDEX('-',Value) -1)) 
          - LEN(CASE
                WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,3)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,3)
                WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,2)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,2)
                WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,1)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,1)
                ELSE NULL
          END)
          - 1 --the '-'
       )
FROM
    @Values
ORDER BY
    LEFT(Value,CHARINDEX('-',Value) -1)
    ,CAST((CASE
       WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,3)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,3)
       WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,2)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,2)
       WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,1)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,1)
       ELSE NULL
    END) AS INT)
    ,RIGHT(Value,
       LEN(Value) - 
       LEN(LEFT(Value,CHARINDEX('-',Value) -1)) 
       - LEN(CASE
             WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,3)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,3)
             WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,2)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,2)
             WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,1)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,1)
             ELSE NULL
       END)
       - 1 --the '-'
    )