在 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进行分组,并使用MIN
和MAX
来获取每个范围的开始和结束:
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;
我在 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进行分组,并使用MIN
和MAX
来获取每个范围的开始和结束:
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;