【Transact-SQL】统计某字段中的值第一次出现后的2小时内出现的次数

 

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



 

 

posted @ 2012-08-14 20:47  小木瓜瓜瓜  阅读(117)  评论(0编辑  收藏  举报