table1 name createdate a 2011-03-01 10:00:00 a 2011-03-01 11:00:00 a 2011-03-01 14:00:00 b 2011-03-01 13:00:00 b 2011-03-01 13:20:00 b 2011-03-01 14:00:00 查询结果为 name createdate count a 2011-03-01 10:00:00 2 a 2011-03-01 14:00:00 1 b 2011-03-01 13:00:00 3 就相当于是统计name字段中的值在第一次出现后的2小时内,总共出现了几次?
这个是网上的解答:
declare @table1 table(name nvarchar,createdate smalldatetime) insert into @table1 select 'a','2011-03-01 10:00:00' union all select 'a','2011-03-01 11:00:00' union all select 'a','2011-03-01 14:00:00' union all select 'b','2011-03-01 13:00:00' union all select 'b','2011-03-01 13:20:00' union all select 'b','2011-03-01 14:00:00' select name, createdate, (select count(createdate) from @table1 b where a.name=b.name and a.createdate<=b.createdate and dateadd(hh,2,a.createdate) >= b.createdate ) as count from @table1 a where not exists (select 1 from @table1 b where a.name=b.name and a.createdate>b.createdate and a.createdate<dateadd(hh,2,b.createdate)) group by name,createdate
但是这个解答其实是有问题的,当把临时表中的第3条数据的createdate改为'2011-03-01 12:00:00',那么显示的结果是:
name createdate count
a 2011-03-01 10:00:00 3
b 2011-03-01 13:00:00 3
在其中没有包括createdate为'2011-03-01 12:00:00'的记录,因为这个时间到为'2011-03-01 10:00:00'是超过2个小时了,也就是说为'2011-03-01 10:00:00'是第一个出现时间,到为'2011-03-01 11:59:59'为止,接下来应该是从'2011-03-01 12:00:00'开始的下个区间了,而这里显然是有问题的。
以下是我写的解法,虽然效率不是太高,但是能解决这个问题:
declare @table1 table(name nvarchar,createdate smalldatetime) insert into @table1 select 'a','2011-03-01 10:00:00' union all select 'a','2011-03-01 11:00:00' union all select 'a','2011-03-01 12:00:00' union all select 'b','2011-03-01 13:10:00' union all select 'b','2011-03-01 13:20:00' union all select 'b','2011-03-01 14:30:00' union all select 'b','2011-03-01 15:15:00' union all select 'b','2011-03-01 16:00:00' union all select 'b','2011-03-01 17:00:00' ;with aa --按照name分区,同时按照createdate排序编号 as ( select name, createdate, ROW_NUMBER() over(partition by name order by createdate) as k1 from @table1 ), r as ( select v.name, starts=v.k1, --区间开始的编号 ends=isnull( min(case when v.k1<a.k1 and DATEADD(hour,2,v.createdate) <= a.createdate then a.k1 else null end)-1, max(case when v.k1<a.k1 then a.k1 else v.k1 end) ), --区间结尾的编号 isnull( min(case when v.k1<a.k1 and DATEADD(hour,2,v.createdate) <= a.createdate then a.k1 else null end)-1, max(case when v.k1<a.k1 then a.k1 else v.k1 end) ) - v.k1 as diff --区间结尾编号与区间开始编号之间的差值 from aa v inner join aa a on v.name = a.name --只关联name相等的 group by v.name, v.k1 having isnull( min(case when v.k1<a.k1 and DATEADD(hour,2,v.createdate) <= a.createdate then a.k1 else null end)-1, max(case when v.k1<a.k1 then a.k1 else v.k1 end) ) >=v.k1 and isnull( max(case when v.k1>a.k1 and DATEADD(hour,-2,v.createdate) >= a.createdate then v.k1 - 1 else null end) + 1, min(case when v.k1>a.k1 then a.k1 else v.k1 end) ) = v.k1 ) --select * from r select aa.name, aa.createdate, diff + 1 from r inner join aa on aa.name = r.name and aa.k1 =r.starts where not exists (select 1 from r rr where rr.name = r.name and rr.starts <> r.starts and rr.starts < r.starts and (rr.ends = r.ends or rr.ends = r.starts) )
不过发现我的这个解法也是有问题的,最大的问题在与不能准确的确定上限在那里,还得继续考虑问题的解法。
下面这个也是有问题的:
declare @table1 table(name nvarchar,createdate smalldatetime) insert into @table1 select 'a','2011-03-01 10:00:00' union all select 'a','2011-03-01 11:00:00' union all select 'a','2011-03-01 12:00:00' --union all select 'b','2011-03-01 13:10:00' --union all select 'b','2011-03-01 13:20:00' --union all select 'b','2011-03-01 14:30:00' --union all select 'b','2011-03-01 15:15:00' --union all select 'b','2011-03-01 16:00:00' --union all select 'b','2011-03-01 17:15:00' ;with a --按照name分区,同时按照createdate排序编号 as ( select name, createdate, ROW_NUMBER() over(partition by name order by createdate) as k1 from @table1 ), c as ( select a1.name, a1.createdate, a1.k1, MIN(a2.createdate) as nextCreatedate, MIN(a2.k1) as nextK1 from a a1 inner join a a2 on a1.name = a2.name and a2.createdate < DATEADD(hour,2,a1.createdate) and a2.createdate >= a1.createdate and a1.k1 <= a2.k1 group by a1.name, a1.createdate, a1.k1 ), w as ( select name, createdate, k1 --null, --null, --null from a where k1 = 1 union all select c.name, c.nextCreatedate, c.nextK1 from W inner join a on a.name = w.name and dateadd(hour,2,w.createdate) <= a.createdate and w.k1 <= a.k1 and w.createdate <> '2011-03-01 12:00:00' inner join c on w.name = c.name and w.createdate = c.createdate and w.k1 = c.k1 where w.k1 <=3 ) SELECT * FROM w
下面的解法是正确的,不过用的是T-SQL,不是纯sql了:
declare @table1 table(name nvarchar(100),createdate smalldatetime) declare @table2 table(name nvarchar(100),createdate smalldatetime,rnum bigint) declare @temp table(name nvarchar(100),createdate smalldatetime,rnum bigint) declare @i int = 1; insert into @table1 select 'a','2011-03-01 10:00:00' union all select 'a','2011-03-01 11:00:00' union all select 'a','2011-03-01 12:00:00' union all select 'b','2011-03-01 13:10:00' union all select 'b','2011-03-01 13:20:00' union all select 'b','2011-03-01 14:30:00' union all select 'b','2011-03-01 15:15:00' union all select 'b','2011-03-01 16:00:00' union all select 'b','2011-03-01 17:16:00' union all select 'b','2011-03-01 17:15:00' ;with a --按照name分区,同时按照createdate排序编号 as ( select name, createdate, ROW_NUMBER() over(partition by name order by createdate) as k1 from @table1 ) insert into @table2 select * from a insert into @temp select name, createdate, rnum from @table2 where rnum = 1 --select * from @temp while @i <= (select MAX(rnum) from @table2) begin insert into @temp select t2.name, min(t2.createdate), @i +1 from @temp t1 inner join @table2 t2 on t1.name = t2.name and t2.createdate >= dateadd(hour,2,t1.createdate) where t1.rnum = @i group by t2.name set @i = @i + 1 end ;with r as ( select name, createdate from @temp group by name, createdate ) select r.name, r.createdate, COUNT(1) from r inner join @table1 t on t.name = r.name and t.createdate >= r.createdate and t.createdate <DATEADD(HOUR,2,r.createdate) group by r.name, r.createdate
其实这个问题是个递归问题,由上一个找到下一个,但是得构造一下:
declare @table1 table(name nvarchar,createdate smalldatetime) insert into @table1 select 'a','2011-03-01 10:00:00' union all select 'a','2011-03-01 11:00:00' union all select 'a','2011-03-01 12:00:00' union all select 'a','2011-03-01 12:20:00' union all select 'b','2011-03-01 13:10:00' union all select 'b','2011-03-01 13:20:00' union all select 'b','2011-03-01 14:30:00' union all select 'b','2011-03-01 15:15:00' union all select 'b','2011-03-01 16:00:00' union all select 'b','2011-03-01 17:20:00' union all select 'b','2011-03-01 17:15:00' union all select 'b','2011-03-01 19:16:00' union all select 'b','2011-03-01 17:15:00' ;with a --按照name分区,同时按照createdate排序编号 as ( select name, createdate, ROW_NUMBER() over(partition by name order by createdate) as k1 from @table1 ), c --对于每个时间,找到大于这个时间2小时的时间中最小那个时间 as ( select a1.name, a1.createdate, a1.k1, MIN(a2.createdate) as nextCreatedate, MIN(a2.k1) as nextK1 from a a1 inner join a a2 on a1.name = a2.name and a2.createdate >= DATEADD(hour,2,a1.createdate) group by a1.name, a1.createdate, a1.k1 union all select a.name,null,null,a.createdate,1 --构造递归运行时需要的层级 from a where k1 = 1 ), w --递归查询 as ( select c.name, c.createdate, c.k1, c.nextCreatedate, c.nextK1, 1 as lev from c where createdate is null and k1 is null union all select c.name, c.createdate, c.k1, c.nextCreatedate, c.nextK1, lev + 1 from W inner join c on w.name = c.name and w.nextCreatedate = c.createdate ) SELECT distinct name, nextCreatedate, nextK1 FROM w