SQL 查询连续区间

 

这篇文章演示如何查询连续区间。

首先创建一个实验表,并插入测试数据。

create table tbl(num int not null primary key)
go

insert into tbl
values(1),(2),(3),(4),(5),(6),(100),(101),(102),(103),(104),(105),(200),(201),(202) -- 多值插入,SQL Server 2008新引入的语法。
go

 

期望的结果是找出每一个连续区间的起始值和结束值,如下图:

image

 

如果能找到一个办法将每一个连续区间分成单独的组。那么,每个组中的最小值就是该区间的起始值,最大值就是该区间的结束值。当然,分组后还可以进行其它聚合计算。

从连续区间的定义可以得出一个结论:表中不存在比每个连续区间起始值小1的数字,也不存在比连续区间结束值大1的数字。反之,如果表中存在数字x并且不存在数字x-1,那么x就是起始值;如果表中存在数字y并且不存y+1,那么y就是结束值。

基于这个逻辑,可以得出一个计算每个数字所在区间起始值的方法:返回小于或等于当前数字的所有起始值中的最大者。同理,计算所在区间结束值的方法是:返回大于或等于当前数字的所有结束值中的最小者。

查询方案1:

select  min(num) as start_num, max(num) as end_num
from
(select num, 
(select MAX(num) from tbl as i where num <= o.num and not exists(select * from tbl where num = i.num - 1) ) as grp  -- 计算所在组的起始值,作为分组号
from tbl as o) as m
group by grp

该查询的成本是:表扫描31次,逻辑读取281次,物理读取0次。

该查询为表中的每一行都计算了一次起始值,它的成本与表中的记录数成正比。以下查询为每个连续的区间只计算一次结束值,性能有很大提升。

查询方案2:

select num as start_num, 
(select MIN(num) from tbl as a where num >= o.num and not exists (select * from tbl where num = a.num + 1)) as end_num
from tbl as o
where not exists(select * from tbl where num = o.num - 1)

该查询的成本是:表扫描8次,逻辑读取96次,物理读取0次。

表扫描从31次降到8次,逻辑读取也从281次降到了96次,性能有了明显的改善。该查询的成本与连续区间的数量成正比。

考虑到每次计算结束值都使用了min聚合函数,而且在num列上有索引。因此,可以使用top语句代替。以下是用top语句改进后的查询。

查询方案3:

select num as start_num, 
(select top 1 num from tbl as a where num >= o.num and not exists (select * from tbl where num = a.num + 1) order by num) as end_num
from tbl as o
where not exists(select * from tbl where num = o.num - 1)

该查询的成本是:表扫描6次,逻辑读取70次,物理读取0次。

 

SQL Server 2005开始提供了row_number窗口函数,它用途广泛。

接下来介绍使用row_number函数解决连续区间问题的方案。

select min(num) as start_num, max(num) as end_num 
from
(select num, num - ROW_NUMBER() over(order by num) as grp
 from tbl) m
group by grp

该查询的成本是:表扫描1次,逻辑读取2次,物理读取0次。

该查询简洁、优雅、性能好,只需要1次表扫描和2次逻辑读取。由于使用了row_number函数,它不能运行在SQL Server 2000及以前的版本上。

 

查询方案1和查询方案4参考了 Itzik Ben-Gan, Lubor Kollar 和 Dejan Sarka 合著的《SQL Server 2005技术内幕 T-SQL 查询》。

posted @ 2012-03-15 22:12  Ken Yang  阅读(6568)  评论(0编辑  收藏  举报