【问题标题】:Dynamically create ranges from numeric sequences从数字序列动态创建范围
【发布时间】:2013-05-13 08:19:36
【问题描述】:

我有一张如下表:

+----+-----+-----+
| ID | GRP | NR  |
+----+-----+-----+
|  1 | 1   | 101 |
|  2 | 1   | 102 |
|  3 | 1   | 103 |
|  4 | 1   | 105 |
|  5 | 1-2 | 106 |
|  6 | 1-2 | 109 |
|  7 | 1-2 | 110 |
|  8 | 2   | 201 |
|  9 | 2   | 202 |
| 10 | 3   | 300 |
| 11 | 3   | 350 |
| 12 | 3   | 351 |
| 13 | 3   | 352 |
+----+-----+-----+

我想创建一个视图,该视图按GRP 对该列表进行分组,并将NR 中的值连接起来。 是否可以动态检测序列并将其缩短为范围? 比如1, 2, 3, 5 会变成1-3, 5

所以结果应该是这样的:

+-----+--------------------+
| GRP |        NRS         |
+-----+--------------------+
| 1   | 101 - 103, 105     |
| 1-2 | 106, 109 - 110     |
| 2   | 201 - 202          |
| 3   | 300, 350 - 352     |
+-----+--------------------+

我现在得到的只是连接值,所以上面的表格会变成这样:

+-----+--------------------+
| GRP |        NRS         |
+-----+--------------------+
| 1   | 101, 102, 103, 105 |
| 1-2 | 106, 109, 110      |
| 2   | 201, 202           |
| 3   | 300, 350, 351, 352 |
+-----+--------------------+

这是实际的声明:

DECLARE @T TABLE
(
    ID INT IDENTITY(1, 1)
  , GRP VARCHAR(10)
  , NR INT
)
INSERT INTO @T
VALUES ('1',101),('1',102),('1',103),('1',105)
      ,('1-2',106),('1-2',109), ('1-2',110)
      ,('2',201),('2',202)
      ,('3',300),('3',350),('3',351),('3',352)

SELECT * FROM @T

;WITH GROUPNUMS (RN, GRP, NR, NRS) AS 
(
    SELECT 1, GRP, MIN(NR), CAST(MIN(NR) AS VARCHAR(MAX)) 
    FROM @T
    GROUP BY GRP

    UNION ALL

    SELECT CT.RN + 1, T.GRP, T.NR, CT.NRS + ', ' + CAST(T.NR AS VARCHAR(MAX))
    FROM @T T
    INNER JOIN GROUPNUMS CT ON CT.GRP = T.GRP 
    WHERE T.NR > CT.NR
)
SELECT NRS.GRP, NRS.NRS
FROM GROUPNUMS NRS
INNER JOIN (
    SELECT GRP, MAX(RN) AS MRN 
    FROM GROUPNUMS 
    GROUP BY GRP
) R
ON NRS.RN = R.MRN AND NRS.GRP = R.GRP
ORDER BY NRS.GRP

谁能告诉我这样做是否容易? 如果有人有想法并愿意分享,那就太好了。

【问题讨论】:

    标签: sql tsql sql-server-2008-r2 numeric-ranges


    【解决方案1】:

    SQLFiddle demo

    with TRes 
    as 
    (
    select T.GRP,T.NR NR,
    CASE WHEN T1.NR IS NULL and T2.NR is null
          THEN CAST(T.NR as VARCHAR(MAX))
         WHEN T1.NR IS NULL and T2.NR IS NOT NULL
          THEN '-'+CAST(T.NR as VARCHAR(MAX))
         WHEN T1.NR IS NOT NULL and T2.NR IS NULL
          THEN CAST(T.NR as VARCHAR(MAX))+'-'
    END AS NR_GRP
    
    
    from T
    left join T T1 on T.Grp=T1.Grp and t.Nr+1=t1.Nr
    left join T T2 on T.Grp=T2.Grp and t.Nr-1=t2.Nr
    
    WHERE T1.NR IS NULL or T2.NR IS NULL
    
    )
    SELECT
       GRP,
       REPLACE(
       substring((SELECT ( ',' + NR_GRP)
                               FROM TRes t2
                               WHERE t1.GRP = t2.GRP
                               ORDER BY 
                                  GRP,
                                  NR
                               FOR XML PATH( '' )
                              ), 2, 10000 )
       ,'-,-','-')  
    FROM TRes t1
    GROUP BY GRP
    

    【讨论】:

      【解决方案2】:

      请检查我的尝试:

      DECLARE @T TABLE
      (
          ID INT IDENTITY(1, 1)
        , GRP VARCHAR(10)
        , NR INT
      )
      INSERT INTO @T
      VALUES ('1',101),('1',102),('1',103),('1',105)
            ,('1-2',106),('1-2',109), ('1-2',110)
            ,('2',201),('2',202)
            ,('3',300),('3',350),('3',351),('3',352)
      
      SELECT * FROM @T
      
      ;WITH T1 as
      (
          SELECT GRP, NR, ROW_NUMBER() over(order by GRP, NR) ID FROM @T
      )
      ,T as (
          SELECT *, 1 CNT FROM T1 where ID=1
          union all
          SELECT b.*, (case when T.NR+1=b.NR and T.GRP=b.GRP then t.CNT 
                              else T.CNT+1 end)
          from T1 b INNER JOIN T on b.ID=T.ID+1
      )
      , TN as(
          select *, 
              MIN(NR) over(partition by GRP, CNT) MinVal, 
              MAX(NR) over(partition by GRP, CNT) MaxVal
          From T
      )
      SELECT GRP, STUFF(
          (SELECT distinct ','+(CASE WHEN MinVal=MaxVal THEN CAST(MinVal as nvarchar(10)) ELSE CAST(MinVal as nvarchar(10))+'-'+cast(MaxVal as nvarchar(10)) END)
             FROM TN b where b.GRP=a.GRP
              FOR XML PATH(''),type).value('.','nvarchar(max)'),1,1,'') AS [ACCOUNT NAMES]
      FROM TN a GROUP BY GRP
      

      【讨论】:

      • 您的查询未能给出关于此测试数据的预期结果 ('3',300),('3',350),('3',351),('3',353 ),('3',352)
      • 是的,你是对的,我只是想展示一种不同的方法来解决给定的问题。这可以通过ROW_NUMBER() 和一个额外的块来实现。
      • +1 提供了一个解决方案——恕我直言——更容易阅读。我只是喜欢这种方法。如果您编辑帖子以包含修复,我会将其标记为答案。
      • @techdo 非常感谢!拯救了我的一天! ;-)
      • 我更喜欢另一种解决方案,它避免了递归,使其执行得更快,并且在 100 次递归时不会失败(这部分很容易修复)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-10
      • 1970-01-01
      相关资源
      最近更新 更多