在 MSSQL 中从 Table 获取数字范围

Get Numbers Range from Table in MSSQL

我在 MSSQL 2008R2 中有一个 table:

    ID  |  PinAddress
-------------------------------------
   1  |   1
   1  |   2
   1  |   3
   1  |   4
   1  |   5
   1  |   6
   1  |   16
   1  |   31
   2  |   55
   2  |   56
   2  |   57
   2  |   81
   2  |   82
   2  |   83
   2  |   84
   3  |   101
   3  |   102
   3  |   103
   3  |   107
   3  |   108
   3  |   109

我想要的是当我搜索 ID = 1 时,我想要这样的结果

1-6,16,31

当我搜索 ID = 2 时,我想要这样的结果

55-57,81-84

当我搜索 ID = 3 时,我想要这样的结果

101-103,107-109

您可以使用以下脚本创建 table 和数据:

CREATE TABLE PinAddress(ID INT,PinAddress INT)
INSERT INTO PinAddress values(1,1)
INSERT INTO PinAddress values(1,2)
INSERT INTO PinAddress values(1,3)
INSERT INTO PinAddress values(1,4)
INSERT INTO PinAddress values(1,5)
INSERT INTO PinAddress values(1,6)
INSERT INTO PinAddress values(1,16)
INSERT INTO PinAddress values(1,31)
INSERT INTO PinAddress values(2,55)
INSERT INTO PinAddress values(2,56)
INSERT INTO PinAddress values(2,57)
INSERT INTO PinAddress values(2,81)
INSERT INTO PinAddress values(2,82)
INSERT INTO PinAddress values(2,83)
INSERT INTO PinAddress values(2,84)
INSERT INTO PinAddress values(3,101)
INSERT INTO PinAddress values(3,102)
INSERT INTO PinAddress values(3,103)
INSERT INTO PinAddress values(3,107)
INSERT INTO PinAddress values(3,108)
INSERT INTO PinAddress values(3,109)

谢谢

基本版,如果要获取所有 ID 的结果集,请创建一个标量函数。

declare @id int = 1
declare @formed varchar(max)
Select @Formed = ISNULL(@formed+',','')+Formed
from
(
Select  
 Formed = convert(varchar,MIN([PinAddress]))
            +case when MIN([PinAddress]) != MAX([PinAddress]) then '-'
            +convert(varchar,MAX([PinAddress] )) else '' end
from PinAddress  where ID = @id            
group by [PinAddress]/case when [#PinAddress]/10= 0 then 10 else 5 end)t

select @formed

试试这个代码

DECLARE @values VARCHAR(8000) 
DECLARE @prevseq int
SET @values = ''



SELECT @values = @values + 
    (CASE WHEN @values = '' OR @values like '%,' THEN cast(PinAddress as varchar) --first value or new after sequence
          WHEN PinAddress - 1 = @prevseq THEN '' 
          ELSE '-' + cast (@prevseq as varchar) + ',' + cast(PinAddress as varchar) 
     END),
        @prevseq = coalesce(PinAddress, -1)
FROM PinAddress 
WHERE ID = 1
ORDER BY PinAddress ASC

SELECT @values = @values + 
        (CASE WHEN @values not like '%' + cast(@prevseq as varchar) THEN '-' + cast(@prevseq as varchar) ELSE '' END)

PRINT @values

这是一个 gaps and islands problem,关键是识别您的连续范围,这是使用 ROW_NUMBER() 完成的。所以对于 ID 3,你有:

ID  PinAddress  RowNumber
---------------------------
3   101         1
3   102         2
3   103         3
3   107         4
3   108         5
3   109         6

从引脚地址中减去行号将为每个连续范围提供一个常数值:

ID  PinAddress  RowNumber   (PinAddress - RowNumber)
---------------------------------------------------
3   101         1           100
3   102         2           100
3   103         3           100
---------------------------------------------------
3   107         4           103
3   108         5           103
3   109         6           103

到目前为止的查询很简单:

SELECT  ID,
        PinAddress,
        GroupingSet = PinAddress - ROW_NUMBER() OVER(PARTITION BY ID ORDER BY PinAddress)
FROM    dbo.PinAddress;

然后你可以根据你的常量值和ID进行分组,并使用MINMAX来获取每个范围的开始和结束:

WITH RankedData AS
(   SELECT  ID,
            PinAddress,
            GroupingSet = PinAddress - ROW_NUMBER() OVER(PARTITION BY ID ORDER BY PinAddress)
    FROM    PinAddress
)
SELECT  ID,
        RangeStart = MIN(PinAddress),
        RangeEnd = MAX(PinAddress),
        RangeText = CONVERT(VARCHAR(10), MIN(PinAddress)) + 
                                    CASE WHEN MIN(PinAddress) = MAX(PinAddress) THEN '' 
                                        ELSE ' - ' + CONVERT(VARCHAR(10), MAX(PinAddress))
                                    END
FROM    RankedData
GROUP BY ID, GroupingSet;

其中,对于 ID 3 给出:

ID  RangeStart  RangeEnd    RangeText
-----------------------------------------
3   101         103         101 - 103
3   107         109         107 - 109

最后,您需要将 RangeText 个值连接成一行,这可以使用 SQL Server's XML Extensions.

来完成
WITH RankedData AS
(   SELECT  ID,
            PinAddress,
            GroupingSet = PinAddress - ROW_NUMBER() OVER(PARTITION BY ID ORDER BY PinAddress)
    FROM    PinAddress
)
SELECT  p.ID,   
        Ranges = STUFF((SELECT  ', ' + CONVERT(VARCHAR(10), MIN(PinAddress)) + 
                                    CASE WHEN MIN(PinAddress) = MAX(PinAddress) THEN '' 
                                        ELSE ' - ' + CONVERT(VARCHAR(10), MAX(PinAddress))
                                    END
                        FROM    RankedData AS rd
                        WHERE   rd.ID = p.ID
                        GROUP BY ID, GroupingSet
                        FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'), 1, 2, '')
FROM    (SELECT DISTINCT ID FROM PinAddress) AS p;

给出:

ID      Ranges
------------------------------
1       1 - 6, 16 - 16, 31 - 31
2       55 - 57, 81 - 84
3       101 - 103, 107 - 109

@GarethD 你与 ROW_NUMBER 的逻辑非常好,而且很有魅力。 我利用了您的查询并对其进行了一些更改以获得我想要的输出:

WITH RankedData AS
(
    SELECT ID,
           PinAddress,
           GroupingSet = PinAddress - ROW_NUMBER() OVER(PARTITION BY ID ORDER BY PinAddress)           
    FROM   dbo.PinAddress
    WHERE  ID = 1
)
SELECT p.ID,
       Ranges = STUFF(
           (            
               SELECT CASE WHEN MIN(pinaddress) = MAX(PINADDRESS) THEN
                ', ' + CONVERT(VARCHAR(10), MIN(PinAddress))
                ELSE 
                 ', ' + CONVERT(VARCHAR(10), MIN(PinAddress)) + '-' + 
                      CONVERT(VARCHAR(10), MAX(PinAddress))
                    END  
               FROM   RankedData AS rd
               WHERE  rd.ID = p.ID
               GROUP BY
                      ID,
                      GroupingSet
                      FOR XML PATH(''),
                      TYPE
           ).value('.', 'VARCHAR(MAX)'),
           1,
           2,
           ''
       )
FROM   (
           SELECT DISTINCT ID
           FROM   RankedData
       ) AS p;