【问题标题】:Order string alpha numerically A1-1-1, A1-2-1, A1-10-1, A1-2-2, A1-2-3 etc以字母数字顺序排列字符串 A1-1-1、A1-2-1、A1-10-1、A1-2-2、A1-2-3 等
【发布时间】:2019-06-05 06:31:20
【问题描述】:

我有一列包含不同长度的字符串,其中包含分隔字母数字字符串的破折号 (-)。 该字符串可能看起来像“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

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

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 个破折号或没有破折号的代码)

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

【问题讨论】:

  • 第 2 部分和第 3 部分是否始终为数值?最左边的字符是否总是一个字母,以及任何剩余的数字? IE。你能有像'AB1-1-2' or A17-A4-9'`这样的值吗?
  • 我建议对 CTE 或子查询进行拆分,然后在外面找出正确的顺序表达式。这样你就不必每次都重复所有的解析代码。
  • 然而,要回答为什么您的排序不符合您的预期,数字和字符串的排序方式不同。但是,对于数字 10 > 2,对于字符串 '2' > '10'。字符串按其字符从左到右的顺序排列。 '2' > '1' 因此'2' 的值比'10' 的值“更大”。
  • 你的数据没有被规范化,至少在每个数据点可能传达几条排序信息的意义上。如果您确实长期需要这种方式排序,那么我建议将每个数字存储在单独的列中。
  • @Larnu。是的,我也可以得到“AB1-2-E1”。

标签: sql sql-server tsql sorting


【解决方案1】:

这并不漂亮,但是......

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 获取(only)该字符串的数字部分(例如,对于'A10',我们得到'10')并将其转换为数值(int)。

然后我们将这些数据转出到各个部分。字母数字部分和数字部分。因此,对于 'A10-A1-12' 的值,我们以行结束:

'A', 10, 'A', 1, 12

然后,既然我们已经对数据进行了透视,我们将按每列单独对其进行排序。瞧。

如果你有像'A1A''1B1' 这样的值,这个 失败,老实说,我不会为了迎合它而改变它。这很混乱,而且确实不是 RDBMS 应该做的。

【讨论】:

  • 非常酷!谢谢
  • 不客气@SCDK,但我建议您采纳 Tim 的评论。规范化您的数据是这样一种排序顺序很重要。
【解决方案2】:

通过摆弄replace & parsename & patindex,最多可以覆盖 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

【讨论】:

  • 是否应该将'B2' 排在'A10' 之前(此解决方案会)?
  • @Larnu:A10 必须在 B2 之前。但是上面的代码非常接近
  • @Larnu:已修复 :-)
  • 看起来不错。 :) 我有兴趣针对我自己的速度进行测试,但这意味着制作一个大型数据集。我会过得很好。虽然 +1。
【解决方案3】:

另一种方法是使用您的技术将字符串拆分为 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

【讨论】:

  • 这与 dnoeth 的问题相同。 'B2' 'A10'。应该反过来。 DB<>fiddle.
  • 啊,好点 @Larnu,谢谢...有时像这样,我希望 SQL 中有适当的正则表达式支持(无需深入研究定制的 CLR 函数)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-09
  • 2020-07-09
  • 2018-09-12
  • 2021-12-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多