从MSSQL中的表中获取Numbers范围

时间:2015-06-12 11:06:36

标签: sql-server sql-server-2008 sql-server-2008-r2 user-defined-functions

我在MSSQL 2008R2中有一个表:

    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

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

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)

由于

4 个答案:

答案 0 :(得分:7)

这是一个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

答案 1 :(得分:1)

试试此代码

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

答案 2 :(得分:0)

@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;

答案 3 :(得分:-1)

基本版,如果要获取所有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