按字母顺序按字母顺序排列字符串A1-1-1,A1-2-1,A1-10-1,A1-2-2,A1-2-3等

时间:2019-01-10 11:33:43

标签: sql sql-server tsql sorting

我有一列带有不同长度字符串的列,其中带有破折号(-),用于分隔字母数字字符串。 该字符串可能看起来像“ A1-2-3”。 我需要先按“ A1”然后按“ 2”然后按“ 3”的顺序订购

我想实现该列的以下顺序:

A1
A1-1-1
A1-1-2
A1-1-3
A1-2-1
A1-2-2
A1-2-3
A1-7
A2-1-1
A2-1-2
A2-1-3
A2-2-1
A2-2-2
A2-2-3
A2-10-1
A2-10-2
A2-10-3
A10-1-1
A10-1-2
A10-1-3
A10-2-1
A10-2-2
A10-2-3

我可以使用以下代码分隔字符串:

declare @string varchar(max) = 'A1-2-3'
declare @first varchar(max) = SUBSTRING(@string,1,charindex('-',@string)-1)
declare @second varchar(max) = substring(@string, charindex('-',@string) + 1, charindex('-',reverse(@string))-1)
declare @third varchar(max) = right(@string,charindex('-',reverse(@string))-1)

select @first, @second, @third

根据上述逻辑,我认为我可以使用以下内容: 请注意,这仅适用于带有两个破折号的字符串

select barcode from tabelWithBarcodes
order by
case when len(barcode) - len(replace(barcode,'-','')) = 2 then
        len(SUBSTRING(barcode,1,charindex('-',barcode)-1))
    end
 , case when len(barcode) - len(replace(barcode,'-','')) = 2 then
     SUBSTRING(barcode,1,(charindex('-',barcode)-1))
 end


,  case when len(barcode) - len(replace(barcode,'-','')) = 2 then
        len(substring(barcode, charindex('-',barcode) + 1, charindex('-',reverse(barcode))-1))
    end
, case when len(barcode) - len(replace(barcode,'-','')) = 2 then
        substring(barcode, charindex('-',barcode) + 1, charindex('-',reverse(barcode))-1)
    end


, case when len(barcode) - len(replace(barcode,'-','')) = 2 then
    len(right(barcode,charindex('-',reverse(barcode))-1))
end
, case when len(barcode) - len(replace(barcode,'-','')) = 2 then
    right(barcode,charindex('-',reverse(barcode))-1)
end

但是该排序不适用于字符串的第二部分和第三部分。 (为简单起见,我没有添加用于检查字符串是否只有1或没有破折号的代码)

不确定我是否在正确的道路上。 有人能解决这个问题吗?

3 个答案:

答案 0 :(得分:3)

但是,这并不漂亮...

USE Sandbox;
GO

WITH VTE AS(
    SELECT V.SomeString
          --Randomised order
    FROM (VALUES ('A1-1-1'),
                 ('A10-1-3'),
                 ('A10-2-2'),
                 ('A1-1-3'),
                 ('A10-2-1'),
                 ('A2-2-2'),
                 ('A1-2-1'),
                 ('A1-2-2'),
                 ('A2-1-1'),
                 ('A10-1-2'),
                 ('B2-1-2'),
                 ('A1'),
                 ('A2-2-1'),
                 ('A2-10-3'),
                 ('A10-2-3'),
                 ('A2-1-2'),
                 ('B1-4'),
                 ('A2-10-2'),
                 ('A2-2-3'),
                 ('A10-1-1'),
                 ('A1-A1-3'),
                 ('A1-7'),
                 ('A2-10-1'),
                 ('A2-1-3'),
                 ('A1-1-2'),
                 ('A1-2-3')) V(SomeString)),
Splits AS(
    SELECT V.SomeString,
           DS.Item,
           DS.ItemNumber,
           CONVERT(int,STUFF((SELECT '' + NG.token
                              FROM dbo.NGrams8k(DS.item,1) NG
                              WHERE TRY_CONVERT(int, NG.Token) IS NOT NULL
                              ORDER BY NG.position
                              FOR XML PATH('')),1,0,'')) AS NumericPortion
    FROM VTE V
         CROSS APPLY dbo.DelimitedSplit8K(V.SomeString,'-') DS),
Pivoted AS(
    SELECT S.SomeString,
           MIN(CASE V.P1 WHEN S.Itemnumber THEN REPLACE(S.Item, S.NumericPortion,'') END) AS P1Alpha,
           MIN(CASE V.P1 WHEN S.Itemnumber THEN S.NumericPortion END) AS P1Numeric,
           MIN(CASE V.P2 WHEN S.Itemnumber THEN REPLACE(S.Item, S.NumericPortion,'') END) AS P2Alpha,
           MIN(CASE V.P2 WHEN S.Itemnumber THEN S.NumericPortion END) AS P2Numeric,
           MIN(CASE V.P3 WHEN S.Itemnumber THEN REPLACE(S.Item, S.NumericPortion,'') END) AS P3Alpha,
           MIN(CASE V.P3 WHEN S.Itemnumber THEN S.NumericPortion END) AS P3Numeric
    FROM Splits S
         CROSS APPLY (VALUES(1,2,3)) AS V(P1,P2,P3)
    GROUP BY S.SomeString)
SELECT P.SomeString
FROM Pivoted P
ORDER BY P.P1Alpha,
         P.P1Numeric,
         P.P2Alpha,
         P.P2Numeric,
         P.P3Alpha,
         P.P3Numeric;

这将输出:

A1
A1-1-1
A1-1-2
A1-1-3
A1-2-1
A1-2-2
A1-2-3
A1-7
A1-A1-3
A2-1-1
A2-1-2
A2-1-3
A2-2-1
A2-2-2
A2-2-3
A2-10-1
A2-10-2
A2-10-3
A10-1-1
A10-1-2
A10-1-3
A10-2-1
A10-2-2
A10-2-3
B1-4
B2-1-2

这利用了2个用户定义的功能。首先或DelimitedSplit8k_Lead(我使用了DelimitedSplit8k,因为目前我的沙盒中没有另一个)。然后您还有NGrams8k

我真的应该解释一下它是如何工作的,但是好吧...(编辑来)。

好的...(/叹气)它的作用。首先,我们使用delimitedsplit8k(_lead)将数据分为相关部分。然后,在SELECT内,我们使用FOR XML PATH来()获得该字符串的数字部分(例如,对于'A10',我们得到{{1} }),然后将其转换为数值('10')。

然后,我们将数据透视到各个部分。字母数字部分和数字部分。因此,对于值int,我们最后一行:

'A10-A1-12'

然后,既然我们已经对数据进行了透视,我们就按每一列对它进行排序。还有,瞧。

如果您具有'A', 10, 'A', 1, 12 'A1A'之类的值,则此 会失效,老实说,我并没有将其更改为为了那个原因。这太混乱了,实际上不是RDBMS应该做的。

答案 1 :(得分:1)

摆弄replaceparsenamepatindex最多可以覆盖3个破折号:

declare @TabelWithBarcodes table (id int primary key identity(1,1), barcode varchar(20) not null, unique (barcode));

insert into @TabelWithBarcodes (barcode) values
('2-2-3'),('A2-2-2'),('A2-2-1'),('A2-10-3'),('A2-10-2'),('A2-10-1'),('A2-1-3'),('A2-1-2'),('A2-1-1'),
('A10-2-3'),('A10-2-2'),('A10-2-10'),('A10-1-3'),('AA10-A111-2'),('A10-1-1'),
('A1-7'),('A1-2-3'),('A1-2-12'),('A1-2-1'),('A1-1-3'),('B1-1-2'),('A1-1-1'),('A1'),('A10-10-1'),('A12-10-1'), ('AB1-2-E1') ;

with cte as 
 (
   select barcode,
      replace(BarCode, '-', '.')
      + replicate('.0', 3 - (len(BarCode)-len(replace(BarCode, '-', '')))) as x
from @TabelWithBarcodes
 )
select * 
,  substring(parsename(x,4), 1, patindex('%[0-9]%',parsename(x,4))-1)
  ,cast(substring(parsename(x,4), patindex('%[0-9]%',parsename(x,4)), 10) as int)
  ,substring(parsename(x,3), 1, patindex('%[0-9]%',parsename(x,3))-1)
  ,cast(substring(parsename(x,3), patindex('%[0-9]%',parsename(x,3)), 10) as int)
  ,substring(parsename(x,2), 1, patindex('%[0-9]%',parsename(x,2))-1)
  ,cast(substring(parsename(x,2), patindex('%[0-9]%',parsename(x,2)), 10) as int)
  ,substring(parsename(x,1), 1, patindex('%[0-9]%',parsename(x,1))-1)
  ,cast(substring(parsename(x,1), patindex('%[0-9]%',parsename(x,1)), 10) as int)
from cte
order by
  substring(parsename(x,4), 1, patindex('%[0-9]%',parsename(x,4))-1)
  ,cast(substring(parsename(x,4), patindex('%[0-9]%',parsename(x,4)), 10) as int)
  ,substring(parsename(x,3), 1, patindex('%[0-9]%',parsename(x,3))-1)
  ,cast(substring(parsename(x,3), patindex('%[0-9]%',parsename(x,3)), 10) as int)
  ,substring(parsename(x,2), 1, patindex('%[0-9]%',parsename(x,2))-1)
  ,cast(substring(parsename(x,2), patindex('%[0-9]%',parsename(x,2)), 10) as int)
  ,substring(parsename(x,1), 1, patindex('%[0-9]%',parsename(x,1))-1)
  ,cast(substring(parsename(x,1), patindex('%[0-9]%',parsename(x,1)), 10) as int)
  1. 通过添加尾随.0(如果缺少)将每个条形码扩展为4组
  2. 将每个条形码分为4组
  3. 将每个组的前导字符和结尾数字分开
  4. 先按主角排序
  5. 然后通过将数字转换为数字

请参见db<>fiddle

答案 2 :(得分:0)

一种替代方法是使用您的技术将字符串分成3个组成部分,然后用前导零(或您选择的字符)对这些字符串进行左填充。这样可以避免字符串可能包含字母数字而不是数字的任何问题。但是,这确实意味着包含不同长度字母字符的字符串可能未如您期望的那样进行排序...这是要使用的代码(使用@dnoeth's excellent answer中的定义):

;with cte as 
(
   select barcode
   , case 
       when barcode like '%-%' then 
           substring(barcode,1,charindex('-',barcode)-1) 
       else 
           barcode 
       end part1
   , case 
       when barcode like '%-%' then 
           substring(barcode, charindex('-',barcode) + 1, case 
               when barcode like '%-%-%' then 
                   (charindex('-',barcode,charindex('-',barcode) + 1)) - 1 
               else 
                   len(barcode) 
               end 
           - charindex('-',barcode))
       else 
           '' 
       end part2
   , case 
       when barcode like '%-%-%' then 
           right(barcode,charindex('-',reverse(barcode))-1) --note: assumes you don't have %-%-%-%
       else 
           '' 
       end part3 
   from @TabelWithBarcodes
)
select barcode
, part1, part2, part3
, right('0000000000' + coalesce(part1,''), 10) lpad1
, right('0000000000' + coalesce(part2,''), 10) lpad2
, right('0000000000' + coalesce(part3,''), 10) lpad3
from cte
order by lpad1, lpad2, lpad3

DBFiddle Example