AlexGuevara
AlexGuevara

Reputation: 942

SQL Server range count

I have SQL Server table in which one of the column represents time, while another represents an ID, like this:

    Datum                     SomeID 
2017-01-01 07:44:57.840         1   
2017-01-02 07:45:10.093         2   
2017-01-02 07:45:21.557         3    
2017-01-03 09:07:21.253         2  
2017-01-05 09:07:42.680         1 
2017-01-06 09:07:49.007         5 

I am looking for an SQL Query or Stored Procedure which takes the time unit as an input (e.g. 'YY', 'MM', 'DD', 'HH') and returns a table with the unique ID count for each time interval. If no records are found for that interval, the count should be zero.

In this case, assuming 'DD' as time unit, the result should be:

Time interval      Count
2017-01-01           1
2017-01-02           2
2017-01-03           1
2017-01-04           0
2017-01-05           1
2017-01-06           1

Upvotes: 1

Views: 231

Answers (2)

John Cappelletti
John Cappelletti

Reputation: 81990

I'll often use a Table-Value-Function to create dynamic Date/Time Ranges. A tally/calendar table would do the trick as well. However, the function offers a little more flexibility. Parameters are Date Range, DatePart, and Increment.

I should also note that your particular question, I use Format(). There are some great features in this function, but it is NOT know for its performance.

Example:

Declare @YourTable table (Datum datetime,SomeID int)
Insert Into @YourTable values
('2017-01-01 07:44:57.840',1),
('2017-01-02 07:45:10.093',2),   
('2017-01-02 07:45:21.557',3),    
('2017-01-03 09:07:21.253',2),  
('2017-01-05 09:07:42.680',1), 
('2017-01-06 09:07:49.007',5)

Declare @DispFormat varchar(10)='yyyy-MM-dd'   --<< Standard Format() options
Declare @DatePart   varchar(10)='DD'           --<< YY,MM,DD,HH,MI,SS
Declare @Increment  int        = 1             --<< Set Increment

Declare @DateR1 datetime
Declare @DateR2 datetime
Select @DateR1=min(Datum),@DateR2=max(Datum) from @YourTable  --<< Can be set manually if desired

Select A.TimeInterval 
      ,Count        = count(Distinct SomeID)
 From  (Select *,TimeInterval = format(RetVal,@DispFormat) From [dbo].[udf-Range-Date](@DateR1,@DateR2,@DatePart,@Increment)) A
 Left  Join @YourTable B on format(A.RetVal,@DispFormat) = format(B.Datum,@DispFormat)
 Group By  A.TimeInterval 

Returns

TimeInterval    Count
2017-01-01      1
2017-01-02      2
2017-01-03      1
2017-01-04      0
2017-01-05      1
2017-01-06      1

The UDF if interested

CREATE FUNCTION [dbo].[udf-Range-Date] (@R1 datetime,@R2 datetime,@Part varchar(10),@Incr int)
Returns Table
Return (
    with cte0(M)   As (Select 1+Case @Part When 'YY' then DateDiff(YY,@R1,@R2)/@Incr When 'QQ' then DateDiff(QQ,@R1,@R2)/@Incr When 'MM' then DateDiff(MM,@R1,@R2)/@Incr When 'WK' then DateDiff(WK,@R1,@R2)/@Incr When 'DD' then DateDiff(DD,@R1,@R2)/@Incr When 'HH' then DateDiff(HH,@R1,@R2)/@Incr When 'MI' then DateDiff(MI,@R1,@R2)/@Incr When 'SS' then DateDiff(SS,@R1,@R2)/@Incr End),
         cte1(N)   As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
         cte2(N)   As (Select Top (Select M from cte0) Row_Number() over (Order By (Select NULL)) From cte1 a, cte1 b, cte1 c, cte1 d, cte1 e, cte1 f, cte1 g, cte1 h ),
         cte3(N,D) As (Select 0,@R1 Union All Select N,Case @Part When 'YY' then DateAdd(YY, N*@Incr, @R1) When 'QQ' then DateAdd(QQ, N*@Incr, @R1) When 'MM' then DateAdd(MM, N*@Incr, @R1) When 'WK' then DateAdd(WK, N*@Incr, @R1) When 'DD' then DateAdd(DD, N*@Incr, @R1) When 'HH' then DateAdd(HH, N*@Incr, @R1) When 'MI' then DateAdd(MI, N*@Incr, @R1) When 'SS' then DateAdd(SS, N*@Incr, @R1) End From cte2 )

    Select RetSeq = N+1
          ,RetVal = D 
     From  cte3,cte0 
     Where D<=@R2
)
/*
Max 100 million observations -- Date Parts YY QQ MM WK DD HH MI SS
Syntax:
Select * from [dbo].[udf-Range-Date]('2016-10-01','2020-10-01','YY',1) 
Select * from [dbo].[udf-Range-Date]('2016-01-01','2017-01-01','MM',1) 
*/

Just for Fun ... Try:

Declare @DispFormat varchar(10)='HH:00'
Declare @DatePart   varchar(10)='HH'   
Declare @Increment  int        = 1     

Upvotes: 0

Gordon Linoff
Gordon Linoff

Reputation: 1270401

Probably the simplest method is dynamic SQL:

declare @timeunit nvarchar(255);
declare @sql nvarchar(max);

set timeunit = N'hh';

set @sql = '
select dateadd(@timeunit, datediff(@timeunit, 0, datum), 0) as dt, count(*)
from t
group by dateadd(@timeunit, datediff(@timeunit, 0, datum), 0) 
';

set @sql = replace(@sql, '@timeunit', @timeunit);

exec sp_executesql @sql;

I am not thrilled with putting user input into a query like this. You can readily check it for valid values, though:

(case when @timeunit in ('year', 'yy', 'month', 'mm',  . . .) then . . .
 end)

Upvotes: 2

Related Questions