如何在运行编号之间添加破折号,并在非运行编号之间添加逗号

时间:2019-06-10 13:52:09

标签: tsql

我想在适当的地方用逗号和连字符替换一组正在运行的和未运行的数字。

使用STUFFXML PATH,我可以通过获得1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 13, 15, 19, 20, 21, 22, 24之类的东西来完成我想要的一些事情。

WITH CTE AS (  
SELECT DISTINCT t1.ORDERNo, t1.Part, t2.LineNum  
FROM [DBName].[DBA].Table1 t1    
JOIN Table2 t2 ON t2.Part = t1.Part    
WHERE t1.ORDERNo = 'AB12345') 

SELECT c1.ORDERNo, c1.Part, STUFF((SELECT ', ' + CAST(LineNum AS VARCHAR(5))  
FROM CTE c2  
WHERE c2.ORDERNo= c1.ORDERNo
FOR XML PATH('')), 1, 2, '') AS [LineNums]  
FROM CTE c1  
GROUP BY c1.ORDERNo, c1.Part

以下是一些示例输出:

ORDERNo Part        LineNums
ON5650  PT01-0181   5, 6, 7, 8, 12
ON5652  PT01-0181   1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 13, 15, 19, 20, 21, 22, 24
ON5654  PT01-0181   1, 4
ON5656  PT01-0181   1, 2, 4
ON5730  PT01-0181   1, 2
ON5253  PT16-3934   1, 2, 3, 4, 5
ON1723  PT02-0585   1, 2, 3, 6, 8, 9, 10

想拥有:

OrderNo Part        LineNums
ON5650  PT01-0181   5-8, 12
ON5652  PT01-0181   1-10, 13, 15, 19-22, 24
ON5654  PT01-0181   1, 4
ON5656  PT01-0181   1-2, 4
ON5730  PT01-0181   1-2
ON5253  PT16-3934   1-5
ON1723  PT02-0585   1-3, 6, 8-10

4 个答案:

答案 0 :(得分:5)

这是一个经典的问题。
(有关此主题的好书是SQL Server MVP Deep Dives的Itzik Ben-Gan的Gaps and islands

这个想法是,您首先需要识别连续数字的组。完成此操作后,其余的操作就很容易了。

首先,创建并填充示例表(在您将来的问题中为我们保存此步骤):

DECLARE @T AS TABLE
(
    N int
);

INSERT INTO @T VALUES
(1), (2), (3), (4), 
(6), 
(8), 
(10), (11), 
(13), (14), (15), 
(17), 
(19), (20), (21), 
(25);

然后,使用通用表表达式来识别组。

With Grouped AS
(
    SELECT N,
           N - ROW_NUMBER() OVER(ORDER BY N) As Grp
    FROM @T
)

此CTE的结果如下:

N   Grp
1   0
2   0
3   0
4   0
6   1
8   2
10  3
11  3
13  4
14  4
15  4
17  5
19  6
20  6
21  6
25  9

如您所见,当数字连续时,grp的值保持不变。
当一行中的数字与上一个数字不连续时,grp值将更改。

然后,您可以从该cte中进行选择,使用case表达式选择一个数字(如果它是该组中的唯一数字)或该组的开始和结束,并用破折号分隔:

SELECT STUFF(
(
    SELECT ', ' +
           CASE WHEN MIN(N) = MAX(N) THEN CAST(MIN(N) as varchar(11))
           ELSE CAST(MIN(N) as varchar(11)) +'-' + CAST(MAX(N) as varchar(11)) 
           END
    FROM Grouped   
    GROUP BY grp
    FOR XML PATH('')
), 1, 2, '')  As GapsAndIslands

结果:

GapsAndIslands
1-4, 6, 8, 10-11, 13-15, 17, 19-21, 25

答案 1 :(得分:1)

为了好玩,我使用窗口聚合(例如SUM()OVER ...)组合了另一种方式。我还使用了一些更新的T-SQL功能,例如CONCAT(2012+)和STRING_AGG(2017+)。这使用Zohar的样本数据。

DECLARE @T AS TABLE(N INT PRIMARY KEY CLUSTERED);    
INSERT INTO @T VALUES (1),(2),(3),(4),(6),(8),(10),(11),(13),(14),(15),(17),(19),(20),(21),(25);

WITH 
a AS (
  SELECT t.N,isNewGroup = SIGN(t.N-LAG(t.N,1,t.N-1) OVER (ORDER BY t.N)-1)
  FROM @t AS t),
b AS (
  SELECT a.N, GroupNbr = SUM(a.isNewGroup) OVER (ORDER BY a.N)
  FROM a),
c AS (
  SELECT b.GroupNbr, 
         txt = CONCAT(MIN(b.N), REPLICATE(CONCAT('-',MAX(b.N)), SIGN(MAX(b.N)-MIN(b.N))))
  FROM b
  GROUP BY b.GroupNbr)
SELECT STRING_AGG(c.txt,', ')  WITHIN GROUP (ORDER BY c.GroupNbr) AS Islands
FROM c;

返回:

Islands
1-4, 6 , 8, 10-11, 13-15, 17, 19-21, 25

答案 2 :(得分:1)

这里是使用递归CTE 的方法。

DECLARE @T AS TABLE(N INT PRIMARY KEY CLUSTERED);    
INSERT INTO @T VALUES (1),(2),(3),(4),(6),(8),(10),(11),(13),(14),(15),(17),(19),(20),(21),(25);


WITH Numbered AS
(
    SELECT N, ROW_NUMBER() OVER(ORDER BY N) AS RowIndex FROM @T 
)
,recCTE AS
(
    SELECT N
          ,RowIndex
          ,CAST(N AS VARCHAR(MAX)) AS OutputString
          ,(SELECT MAX(n2.RowIndex) FROM Numbered n2) AS MaxRowIndex
    FROM Numbered WHERE RowIndex=1
    UNION ALL
    SELECT n.N
          ,n.RowIndex
          ,CASE WHEN A.TheEnd  =1                  THEN CONCAT(r.OutputString,CASE WHEN IsIsland=1 THEN '-' ELSE ',' END, n.N)
                WHEN A.IsIsland=1 AND A.IsWithin=0 THEN CONCAT(r.OutputString,'-')
                WHEN A.IsIsland=1 AND A.IsWithin=1 THEN r.OutputString
                WHEN A.IsIsland=0 AND A.IsWithin=1 THEN CONCAT(r.OutputString,r.N,',',n.N)
                ELSE                                    CONCAT(r.OutputString,',',n.N)
           END
          ,r.MaxRowIndex
    FROM Numbered n
    INNER JOIN recCTE r ON n.RowIndex=r.RowIndex+1
    CROSS APPLY(SELECT CASE WHEN n.N-r.N=1 THEN 1 ELSE 0 END AS IsIsland
                      ,CASE WHEN RIGHT(r.OutputString,1)='-' THEN 1 ELSE 0 END AS IsWithin
                      ,CASE WHEN n.RowIndex=r.MaxRowIndex THEN 1 ELSE 0 END AS TheEnd) A

)
SELECT TOP 1 OutputString FROM recCTE ORDER BY RowIndex DESC;

简而言之:

  • 首先,我们创建一个编号集。
  • 递归CTE将使用该行的索引来选择下一行,从而遍历集合 row-by-row
  • APPLY确定三个BIT值:
    • 是到上一个值1的距离,那么我们在岛上,否则不是
    • 输出字符串中的最后一个字符是连字符,那么我们正在等待一个岛的结尾,否则就不会。
    • ...如果我们已经结束了
  • CASE处理以下四场矩阵
    • 首先,我们处理结尾以避免结尾处出现连字符
    • 到达一个小岛,我们添加一个连字符
    • 我们继续在岛上停留
    • 到达一个岛的末端,我们添加最后一个数字,一个逗号并开始一个新的岛
    • 任何其他情况下只会添加一个逗号并开始一个新的岛。

提示:您可以将读为 group section ,而逗号则标记了空白。

答案 3 :(得分:0)

结合已经拥有的内容并使用Zohar Peled的代码,我终于找到了解决方案:

WITH cteLineNums AS (
SELECT TOP 100 PERCENT t1.OrderNo, t1.Part, t2.LineNum
, (t2.line_number - ROW_NUMBER() OVER(PARTITION BY t1.OrderNo, t1.Part ORDER BY t1.OrderNo, t1.Part, t2.LineNum)) AS RowSeq
FROM [DBName].[DBA].Table1 t1    
JOIN Table2 t2 ON t2.Part = t1.Part    
WHERE t1.OrderNo = 'AB12345')
GROUP BY t1.OrderNo, t1.Part, t2.LineNum
ORDER BY t1.OrderNo, t1.Part, t2.LineNum)

SELECT OrderNo, Part
,  STUFF((SELECT ', ' +
       CASE WHEN MIN(line_number) = MAX(line_number) THEN CAST(MIN(line_number) AS VARCHAR(3))
             WHEN MIN(line_number) = (MAX(line_number)-1) THEN CAST(MIN(line_number) AS VARCHAR(3)) + ', ' + CAST(MAX(line_number) AS VARCHAR(3)) 
       ELSE CAST(MIN(line_number) AS VARCHAR(3)) + '-' + CAST(MAX(line_number) AS VARCHAR(3)) 
       END
    FROM cteLineNums c1
        WHERE c1.OrderNo = c2.OrderNo
        AND c1.Part = c2.Part
    GROUP BY OrderNo, Part
        ORDER BY OrderNo, Part
    FOR XML PATH('')), 1, 2, '') AS [LineNums]
FROM cteLineNums c2
GROUP BY OrderNo, Part

我使用了ROW_NUMBER()OVER PARTITION BY,因为我返回了具有不同订单号和零件号的多个记录。所有这些导致我仍然必须在第二部分中进行自连接,以便为每条记录显示正确的LineNum。 CASE语句中的第二个WHEN是由于代码默认情况下在应为2、5、8、9、14时会显示类似2、5、8-9、14的内容。