Select unexist date yyyy/mm - sql

I have a SQL query as follows:
SELECT
R.BOOK_YM, COUNT(*)
FROM
#TB_TRANSED_GUI_AMOUNT AS R
GROUP
BY R.BOOK_YM
The selected results like as follows:
BOOK_YM | count(*)
---------+---------
201411 | 51
201412 | 142
201501 | 1
201506 | 1
How do I modify the result like the follows:
BOOK_YM | count(*)
--------+-----------
201411 | 51
201412 | 142
201501 | 1
201502 | 0
201503 | 0
201504 | 0
201505 | 0
201506 | 1

You can use the following to get a table of all YYYYMM between 2 dates and use the resulting #YMList variable in a join:
declare #StartDate date = cast(dateadd(month, -12, GetDate()) as date)
declare #EndDate date = cast(GetDate() as date)
declare #YMList TABLE (YYYYMM INT)
;with dates as (
SELECT cast(#StartDate as Date) [date]
UNION ALL
SELECT DATEADD(month,1,t.date)
FROM dates t
WHERE t.[date] < #EndDate
)
insert #YMList (YYYYMM)
select
YEAR([Date]) * 100 + MONTH([Date]) AS YYYYMM
from dates
WHERE [Date] < #EndDate
OPTION (MAXRECURSION 10000)

Try this: (I think BOOK_YM is a varchar field if not CAST it)
SELECT
Y.YYYY + M.MM AS BOOK_YM,
COUNT(R.BOOK_YM) AS CNT
FROM
(SELECT SUBSTRING(BOOK_YM, 1, 4) As YYYY
FROM t
GROUP BY SUBSTRING(BOOK_YM, 1, 4)) AS Y
CROSS JOIN
(SELECT '01' As MM
UNION ALL SELECT '02'
UNION ALL SELECT '03'
UNION ALL SELECT '04'
UNION ALL SELECT '05'
UNION ALL SELECT '06'
UNION ALL SELECT '07'
UNION ALL SELECT '08'
UNION ALL SELECT '09'
UNION ALL SELECT '10'
UNION ALL SELECT '11'
UNION ALL SELECT '12') AS M
LEFT JOIN
t AS R
ON Y.YYYY + M.MM = R.BOOK_YM
WHERE
Y.YYYY + M.MM BETWEEN (SELECT MIN(BOOK_YM) FROM t) AND (SELECT MAX(BOOK_YM) FROM t)
GROUP BY
Y.YYYY + M.MM

Related

SQL Convert Week Number to Date (dd/MM)

I am trying to convert the week number (for example: 21) in SQL-Server to the date (from the Monday of that week) in dd/MM format.
I have searched online but cannot seem to find anything that I could use.
Is this something I can do?
Any help or advice is appreciated.
Thank you in advance.
Try this,
declare #wk int set #wk = 21
declare #yr int set #yr = 2016
select dateadd (week, #wk-1, dateadd (year, #yr-1900, 0)) - 4 -
datepart(dw, dateadd (week, #wk-1, dateadd (year, #yr-1900, 0)) - 4) + 1
or try this way
declare #wk int = 21
select dateadd(week,#wk-1, DATEADD(wk, DATEDIFF(wk,-1,DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)), 0))
You can do it something like:
declare #Week_Number int, #Year int, #Year_Start_Day date, #Week_Day date
select
#Week_Number = 1,
#Year = 2016
select #Year_Start_Day = cast(#Year as nvarchar(4)) + '0101'
select #Week_Day = dateadd(wk, #Week_Number, #Year_Start_Day)
select dateadd(dd, 1 - datepart(weekday, #Week_Day), #Week_Day)
This will do:
DECLARE #y int = 2016,
#w int = 21
SELECT CONVERT(nvarchar(5),DATEADD(day,#w*7-(DATEPART(WEEKDAY,CAST(#y as nvarchar(4))+'-01-01')-2),CAST(#y as nvarchar(4))+'-01-01'),3)
Output:
23/05
How about this?
DECLARE #YearNum SMALLINT = 2016;
DECLARE #WeekNum TINYINT=25;
select
SUBSTRING(CONVERT(VARCHAR(10),selected_date,105),0,6) AS WeeKDate
from
(select DATEADD(dd,t4.i*10000 + t3.i*1000 + t2.i*100 + t1.i*10 + t0.i,'1970-01-01') selected_date from
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4) v
where YEAR(selected_date)=#YearNum
AND DATEPART(WK,selected_date)=#WeekNum
AND DATEPART(WEEKDAY,selected_date)=2 -- Monday

SQL - How to get a complete month calendar table

I need to generate a report that will show 7 Columns Monday to Sunday and 5 Rows, just like a normal month calendar:
I need the dates in a list table, so the desire table result should look like this:
That way my report designer will automatically create a view like above.
The above table result should be generated on the fly with no need of any database table because after this I will have to left join the dates to one of my db tables so I can populate other kind of information that I will use to display in my report for each day.
Any clue?
SQL Fiddle for arbitrary sequence of dates, no tables required.
http://sqlfiddle.com/#!3/9eecb7/275
DECLARE #dtStartDate datetime
DECLARE #dtEndDate datetime
SET #dtStartDate = '2015-05-01'
SET #dtEndDate = '2015-05-31'
SELECT
CASE DATEPART(weekday, T.DateVal)
WHEN 1 THEN 'Sunday'
WHEN 2 THEN 'Monday'
WHEN 3 THEN 'Tuesday'
WHEN 4 THEN 'Wednesday'
WHEN 5 THEN 'Thursday'
WHEN 6 THEN 'Friday'
WHEN 7 THEN 'Saturday'
END AS WeekDay,
DATEPART(day, T.DateVal) AS [Date],
DATEPART(month, T.DateVal) AS [Month],
DATEPART(year, T.DateVal) AS [Year]
FROM
(
SELECT
DATEADD(day, SEQ.SeqValue, #dtStartDate) DateVal
FROM
(
SELECT
(HUNDREDS.SeqValue + TENS.SeqValue + ONES.SeqValue) SeqValue
FROM
(
SELECT 0 SeqValue
UNION ALL
SELECT 1 SeqValue
UNION ALL
SELECT 2 SeqValue
UNION ALL
SELECT 3 SeqValue
UNION ALL
SELECT 4 SeqValue
UNION ALL
SELECT 5 SeqValue
UNION ALL
SELECT 6 SeqValue
UNION ALL
SELECT 7 SeqValue
UNION ALL
SELECT 8 SeqValue
UNION ALL
SELECT 9 SeqValue
) ONES
CROSS JOIN
(
SELECT 0 SeqValue
UNION ALL
SELECT 10 SeqValue
UNION ALL
SELECT 20 SeqValue
UNION ALL
SELECT 30 SeqValue
UNION ALL
SELECT 40 SeqValue
UNION ALL
SELECT 50 SeqValue
UNION ALL
SELECT 60 SeqValue
UNION ALL
SELECT 70 SeqValue
UNION ALL
SELECT 80 SeqValue
UNION ALL
SELECT 90 SeqValue
) TENS
CROSS JOIN
(
SELECT 0 SeqValue
UNION ALL
SELECT 100 SeqValue
UNION ALL
SELECT 200 SeqValue
UNION ALL
SELECT 300 SeqValue
UNION ALL
SELECT 400 SeqValue
UNION ALL
SELECT 500 SeqValue
UNION ALL
SELECT 600 SeqValue
UNION ALL
SELECT 700 SeqValue
UNION ALL
SELECT 800 SeqValue
UNION ALL
SELECT 900 SeqValue
) HUNDREDS
) SEQ
) T
WHERE
T.DateVal <= #dtEndDate
ORDER BY
T.DateVal ASC
Edited to add beginning blank days. Same logic can be used to append blank days if necessary.
declare #begindate date = '5-1-2015'
declare #enddate date = '6-1-2015'
create table MyCal (MyWeekday varchar(10), MyDate varchar(2), MyMonth varchar(10), MyYear int)
declare #DaysFromMonday int =
case datepart(weekday, #begindate)
when 1 then 6
else datepart(weekday, #begindate) - 2
end
declare #datecounter date = dateadd(dd, -1* #daysFromMonday, #begindate)
while #datecounter < #enddate
begin
insert into MyCal values (
datename(weekday, #datecounter)
, case when #datecounter < #begindate then '' else cast(datepart(DD, #datecounter) as varchar) end
, datename(month, #datecounter)
, datepart(YYYY, #datecounter)
)
set #datecounter = dateadd(day, 1, #datecounter)
end
select * from MyCal
You can use this piece of code.
Replace the #tmp table with a proper table name that you prefer.
declare #start_date datetime, #end_date datetime, #cur_date datetime
set #start_date = '2015-05-01'
set #end_date = '2016-04-30'
set #cur_date = #start_date
create table #tmp
(weekday varchar(10),
date int,
month varchar(10),
year int)
while #cur_date <= #end_date
begin
insert into #tmp
select datename(dw, #cur_date), datepart(day, #cur_date), datename(month, #cur_date), datepart(year, #cur_date)
set #cur_date = dateadd(dd, 1, #cur_date)
end
select * from #tmp
drop table #tmp
Try this version - You will have to run one month at a time. I've also added the blanks if the last day of the month is not a Sunday e.g. Try he month of August 2015.
declare #start_date datetime, #end_date datetime, #cur_date datetime
set #start_date = '2015-05-01'
set #end_date = '2015-05-31'
set #cur_date = #start_date
create table #tmp
(weekday varchar(10),
date varchar(2),
month varchar(10),
year int)
while datepart(dw, #cur_date) > 2
begin
insert into #tmp
select datename(dw, dateadd(dd, -(datepart(dw, #cur_date) - 2), #start_date)), '', datename(month, #start_date), datepart(year, #start_date)
set #cur_date = dateadd(dd, -1, #cur_date)
end
set #cur_date = #start_date
while #cur_date <= #end_date
begin
insert into #tmp
select datename(dw, #cur_date), datepart(day, #cur_date), datename(month, #cur_date), datepart(year, #cur_date)
set #cur_date = dateadd(dd, 1, #cur_date)
end
set #cur_date = #end_date
while datepart(dw, #cur_date) > 1
begin
insert into #tmp
select datename(dw, dateadd(dd, 1, #cur_date)), '', datename(month, #end_date), datepart(year, #end_date)
set #cur_date = dateadd(dd, 1, #cur_date)
end
select * from #tmp
drop table #tmp
Hope this helps.
Here is an alternative method using multiple CTE queries and SQL EMONTH end of month function with DATENAME SQL function
declare #date as date = dateadd(mm,0,getdate())
;with monthdates as (
SELECT dateadd(dd,1,EOMONTH(dateadd(mm,-1,#date))) firstofmonth, EOMONTH (#date) lastofmonth
), calc as (
select
firstofmonth, lastofmonth,
dateadd(dd,
-1 * case when datepart(dw,firstofmonth)-2 >= 0 then datepart(dw,firstofmonth)-2 else datepart(dw,firstofmonth)+7-2 end,
firstofmonth
) firstofcalendar,
datediff(WEEK,
dateadd(dd,
-1 * case when datepart(dw,firstofmonth)-2 >= 0 then datepart(dw,firstofmonth)-2 else datepart(dw,firstofmonth)+7-2 end,
firstofmonth
),
lastofmonth) weekcount
from monthdates
), calendar1 as (
select
firstofcalendar,
firstofmonth,
lastofmonth,
case when dateadd(dd, weekcount * 7 - 1, firstofcalendar) = lastofmonth then lastofmonth
else
dateadd(dd, (weekcount+1) * 7 - 1, firstofcalendar)
end as lastofcalendar
from calc
), calendar as (
select
datename(dw,date) dayname,
case when month(date) = month(#date) then cast(cast(date as date) as nvarchar) else '' end as date
from calendar1
cross apply [dbo].[DateTable](firstofcalendar, lastofcalendar)
)
select *, datename(mm,#date) month, datepart(yyyy,#date) year from calendar
Something like this should do you. It should work for any start-of-week you choose: you just need to set datefirst N on your connection/query to suit. If you want the week to start on Monday, use set datefirst 1.
Here's the query:
set datefirst = 1 -- start the week on Monday
declare #month date = '1 May 2015'
;with
month_days as
(
select dd = -5 union all
select dd = -4 union all
select dd = -3 union all
select dd = -2 union all
select dd = -1 union all
select dd = 0 union all
select dd = 1 union all
select dd = 2 union all
select dd = 3 union all
select dd = 4 union all
select dd = 5 union all
select dd = 6 union all
select dd = 7 union all
select dd = 8 union all
select dd = 9 union all
select dd = 10 union all
select dd = 11 union all
select dd = 12 union all
select dd = 13 union all
select dd = 14 union all
select dd = 15 union all
select dd = 16 union all
select dd = 17 union all
select dd = 18 union all
select dd = 19 union all
select dd = 20 union all
select dd = 21 union all
select dd = 22 union all
select dd = 23 union all
select dd = 24 union all
select dd = 25 union all
select dd = 26 union all
select dd = 27 union all
select dd = 28 union all
select dd = 29 union all
select dd = 30 union all
select dd = 31 union all
select dd = 32 union all
select dd = 33 union all
select dd = 34 union all
select dd = 35 union all
select dd = 36 union all
select dd = 37
),
calendar as
(
select * ,
yyyymmdd = dateadd(day,t.dd-1,#month)
from month_days t
)
select Day_Of_Week = datename(weekday,c.yyyymmdd ) ,
DD = datepart(day , c.yyyymmdd ) ,
MM = datepart(month , c.yyyymmdd ) ,
YYYY = datepart(year , c.yyyymmdd )
from calendar c
where datediff(month,#month,c.yyyymmdd) = 0 -- current month
OR ( datediff(month,#month,c.yyyymmdd) < 0 -- previous month
and datepart(weekday,c.yyyymmdd) < datepart(weekday,#month) ) -- ...to pad out the first week
OR ( datediff(month,#month,c.yyyymmdd) > 0 -- next month
and datepart(weekday,c.yyyymmdd) >= datepart(weekday,dateadd(month,1,#month)) -- ... to pad out the last week
)
order by c.yyyymmdd
Running the above query produces the expected
Day_Of_Week DD MM YYYY
=========== == == ====
Monday 27 4 2015
Tuesday 28 4 2015
Wednesday 29 4 2015
Thursday 30 4 2015
Friday 1 5 2015
Saturday 2 5 2015
Sunday 3 5 2015
Monday 4 5 2015
Tuesday 5 5 2015
Wednesday 6 5 2015
Thursday 7 5 2015
Friday 8 5 2015
Saturday 9 5 2015
Sunday 10 5 2015
Monday 11 5 2015
Tuesday 12 5 2015
Wednesday 13 5 2015
Thursday 14 5 2015
Friday 15 5 2015
Saturday 16 5 2015
Sunday 17 5 2015
Monday 18 5 2015
Tuesday 19 5 2015
Wednesday 20 5 2015
Thursday 21 5 2015
Friday 22 5 2015
Saturday 23 5 2015
Sunday 24 5 2015
Monday 25 5 2015
Tuesday 26 5 2015
Wednesday 27 5 2015
Thursday 28 5 2015
Friday 29 5 2015
Saturday 30 5 2015
Sunday 31 5 2015
Monday 1 6 2015
Tuesday 2 6 2015
Wednesday 3 6 2015
Thursday 4 6 2015
Friday 5 6 2015
Saturday 6 6 2015

Dynamic column name change based on date [T-SQL]

Is there a way to make this work? change static, "13MonthsAgo"
into January?
Original
> COUNT(CASE WHEN dateadd(MONTH, - 13, getdate()) >
> HireDate AND dateadd(MONTH, - 13, getdate()) <
> TerminationDate OR
>TerminationDate IS NULL THEN 1 ELSE NULL END) AS 13Monthsago
Preferred
> COUNT(CASE WHEN dateadd(MONTH, - 13, getdate()) >
> HireDate AND dateadd(MONTH, - 13, getdate()) <
> TerminationDate OR
>TerminationDate IS NULL THEN 1 ELSE NULL END) AS
>DATENAME(month, dateadd(MONTH,-13,getdate()))
A lengthy approach but will work..
declare #MonthName varchar(20)
select 1 num, 'January' name into #Months
union
select 2 num, 'February' name
union
select 3 num, 'March' name
union
select 4 num, 'April' name
union
select 5 num, 'May' name
union
select 6 num, 'June' name
union
select 7 num, 'July' name
union
select 8 num, 'August' name
union
select 9 num, 'September' name
union
select 10 num, 'October' name
union
select 11 num, 'November' name
union
select 12 num, 'December' name
select #MonthName = name from #Months where datepart(mm,getdate()) = num
--Add the other columns to the dataset here
--This is just an example
select HireDate, TerminationDate,
COUNT(CASE WHEN dateadd(MONTH, - 13, getdate()) >
HireDate AND dateadd(MONTH, - 13, getdate()) <
TerminationDate OR
TerminationDate IS NULL THEN 1 ELSE NULL END) AS 13Monthsago
into #Dataset
FROM SomeTable GROUP BY HireDate, TerminationDate
use tempdb
EXEC sp_RENAME '#Dataset.13Monthsago' , #MonthName, 'COLUMN'
SELECT * FROM #Dataset
This could be one way to go about it.
;with cte_Dates AS
(
SELECT CAST('20150101' as DATEtime) as DateStr UNION ALL
SELECT '20150201' UNION ALL
SELECT '20150202' UNION ALL
SELECT '20150203' UNION ALL
SELECT '20150204' UNION ALL
SELECT '20150301' UNION ALL
SELECT '20150401' UNION ALL
SELECT '20150501' UNION ALL
SELECT '20150601' UNION ALL
SELECT '20150701' UNION ALL
SELECT '20150801' UNION ALL
SELECT '20150901' UNION ALL
SELECT '20151001' UNION ALL
SELECT '20151101' UNION ALL
SELECT '20151201'
)
SELECT *
FROM
(SELECT DateStr,DATENAME(MONTH,DateStr) As MONTHS
FROM
cte_Dates
)P
PIVOT
(
count(DateStr)
FOR MONTHS IN ([January], [February],[March],[April],[May],[June],[July],[August],[September],[October],[November],[December])
)AS PVT

SQL to get an daily average from month total

I have a table that lists month totals (targets)
person total month
----------- --------------------- -----------
1001 114.00 201005
1001 120.00 201006
1001 120.00 201007
1001 120.00 201008
.
1002 114.00 201005
1002 222.00 201006
1002 333.00 201007
1002 111.00 201008
.
.
but month is an integer(!)
I also have another table that has a list of working days (calendar)
tran_date day_type
----------------------- ---------------------------------
1999-05-01 00:00:00.000 WEEKEND
1999-05-02 00:00:00.000 WEEKEND
1999-05-03 00:00:00.000 WORKING_DAY
1999-05-04 00:00:00.000 WORKING_DAY
1999-06-01 00:00:00.000 .....
.
.
.
What I want to do is get a list of dates with the average for that day based on the number of days in the month where day_type is 'WORKING_DAY' / the month's total.
so if I had say 20 working days in 201005 then I'd get an average of 114/20 on each working day, while the other days would be 0.
somthing like
person tran_date day_avg
------- ----------------------- ---------------------------------
1001 2010-05-01 00:00:00.000 0
1001 2010-05-02 00:00:00.000 0
1001 2010-05-03 00:00:00.000 114/2 (as there are two working days)
1001 2010-05-04 00:00:00.000 114/2 (as there are two working days)
.
.
.
It has to be done as a CTE as this is a limitation of the target system (I can only do one statement)
I can start off with (Dates to
WITH
Dates AS
(
SELECT CAST('19990501' as datetime) TRAN_DATE
UNION ALL
SELECT TRAN_DATE + 1
FROM Dates
WHERE TRAN_DATE + 1 <= CAST('20120430' as datetime)
),
Targets as
(
select CAST(cast(month as nvarchar) + '01' as dateTime) mon_start,
DATEADD(MONTH, 1, CAST(cast(month as nvarchar) + '01' as dateTime)) mon_end,
total
from targets
)
select ????
Sample data (may vary):
select * into #totals from (
select '1001' as person, 114.00 as total, 199905 as month union
select '1001', 120.00, 199906 union
select '1001', 120.00, 199907 union
select '1001', 120.00, 199908
) t
select * into #calendar from (
select cast('19990501' as datetime) as tran_date, 'WEEKEND' as day_type union
select '19990502', 'WEEKEND' union
select '19990503', 'WORKING_DAY' union
select '19990504', 'WORKING_DAY' union
select '19990505', 'WORKING_DAY' union
select '19990601', 'WEEKEND' union
select '19990602', 'WORKING_DAY' union
select '19990603', 'WORKING_DAY' union
select '19990604', 'WORKING_DAY' union
select '19990605', 'WORKING_DAY' union
select '19990606', 'WORKING_DAY' union
select '19990701', 'WORKING_DAY' union
select '19990702', 'WEEKEND' union
select '19990703', 'WEEKEND' union
select '19990704', 'WORKING_DAY' union
select '19990801', 'WORKING_DAY' union
select '19990802', 'WORKING_DAY' union
select '19990803', 'WEEKEND' union
select '19990804', 'WEEKEND' union
select '19990805', 'WORKING_DAY' union
select '19990901', 'WORKING_DAY'
) t
Select statement, it returns 0 if the day is 'weekend' or not exists in calendar table. Please keep in mind that MAXRECURSION is a value between 0 and 32,767.
;with dates as (
select cast('19990501' as datetime) as tran_date
union all
select dateadd(dd, 1, tran_date)
from dates where dateadd(dd, 1, tran_date) <= cast('20010101' as datetime)
)
select t.person , d.tran_date, (case when wd.tran_date is not null then t.total / w_days else 0 end) as day_avg
from dates d
left join #totals t on
datepart(yy, d.tran_date) * 100 + datepart(mm, d.tran_date) = t.month
left join (
select datepart(yy, tran_date) * 100 + datepart(mm, tran_date) as month, count(*) as w_days
from #calendar
where day_type = 'WORKING_DAY'
group by datepart(yy, tran_date) * 100 + datepart(mm, tran_date)
) c on t.month = c.month
left join #calendar wd on d.tran_date = wd.tran_date and wd.day_type = 'WORKING_DAY'
where t.person is not null
option(maxrecursion 20000)
You could calculate the number of working days per month in a subquery. Only the subquery would have to use group by. For example:
select t.person
, wd.tran_date
, t.total / m.WorkingDays as day_avg
from #Targets t
join #WorkingDays wd
on t.month = convert(varchar(6), wd.tran_date, 112)
left join
(
select convert(varchar(6), tran_date, 112) as Month
, sum(case when day_type = 'WORKING_DAY' then 1 end) as WorkingDays
from #WorkingDays
group by
convert(varchar(6), tran_date, 112)
) as m
on m.Month = t.month
Working example at SE Data.
For the "magic number" 112 in convert, see the MSDN page.
If I understood your question correctly, the following query should do it:
SELECT
*,
ISNULL(
(
SELECT total
FROM targets
WHERE
MONTH(tran_date) = month - ROUND(month, -2)
AND c1.day_type = 'WORKING_DAY'
) /
(
SELECT COUNT(*)
FROM calendar c2
WHERE
MONTH(c1.tran_date) = MONTH(c2.tran_date)
AND c2.day_type = 'WORKING_DAY'
),
0
) day_avg
FROM
calendar c1
In plain English:
For each row in calendar,
get the total of the corresponding month if this row is a working day (otherwise get NULL),
get the number of working days in the same month
and divide them.
Finally, convert the NULL (of non-working days) into 0.

Get records only if consecutive days per user is 30 or greater

I have the following data being returned from a query. Essentially I am putting this in a temp table so it is now in a temp table that I can query off of(Obviously a lot more data in real life, I am just showing an example):
EmpId Date
1 2011-01-01
1 2011-01-02
1 2011-01-03
2 2011-02-03
3 2011-03-01
4 2011-03-02
5 2011-01-02
I need to return only EmpId's that have 30 or more consecutive days in the date column. I also need to return the day count for these employees that have 30 or more consecutive days. There could potentially be 2 or more sets of different consecutive days that are 30 or more days. iIn this instance I would like to return multiple rows. So if an employee has a date from 2011-01-01 to 2011-02-20 then return this and the count in one row. Then if this same employee has dates of 2011-05-01 to 2011-07-01 then return this in another row. Essentially all breaks in consecutive days are treated as a seperate record.
Using DENSE_RANK should do the trick:
;WITH sampledata
AS (SELECT 1 AS id, DATEADD(day, -0, GETDATE())AS somedate
UNION ALL SELECT 1, DATEADD(day, -1, GETDATE())
UNION ALL SELECT 1, DATEADD(day, -2, GETDATE())
UNION ALL SELECT 1, DATEADD(day, -3, GETDATE())
UNION ALL SELECT 1, DATEADD(day, -4, GETDATE())
UNION ALL SELECT 1, DATEADD(day, -5, GETDATE())
UNION ALL SELECT 1, DATEADD(day, -10, GETDATE())
UNION ALL SELECT 1, '2011-01-01 00:00:00'
UNION ALL SELECT 1, '2010-12-31 00:00:00'
UNION ALL SELECT 1, '2011-02-01 00:00:00'
UNION ALL SELECT 1, DATEADD(day, -10, GETDATE())
UNION ALL SELECT 2, DATEADD(day, 0, GETDATE())
UNION ALL SELECT 2, DATEADD(day, -1, GETDATE())
UNION ALL SELECT 2, DATEADD(day, -2, GETDATE())
UNION ALL SELECT 2, DATEADD(day, -6, GETDATE())
UNION ALL SELECT 3, DATEADD(day, 0, GETDATE())
UNION ALL SELECT 4, DATEADD(day, 0, GETDATE())
UNION ALL SELECT 5, DATEADD(day, 0, GETDATE()))
, ranking
AS (SELECT *, DENSE_RANK()OVER(PARTITION BY id ORDER BY DATEDIFF(day, 0, somedate)) - DATEDIFF(day, 0, somedate)AS dategroup
FROM sampledata)
SELECT id
, MIN(somedate)AS range_start
, MAX(somedate)AS range_end
, DATEDIFF(day, MIN(somedate), MAX(somedate)) + 1 AS consecutive_days
FROM ranking
GROUP BY id, dategroup
--HAVING DATEDIFF(day, MIN(somedate), MAX(somedate)) + 1 >= 30 --change as needed
ORDER BY id, range_start
Something like this should do the trick, haven't tested it though.
SELECT
a.empid
, count(*) as consecutive_count
, min(a.mydate) as startdate
FROM (SELECT * FROM logins ORDER BY mydate) a
INNER JOIN (SELECT * FROM logins ORDER BY mydate) b
ON (a.empid = b.empid AND datediff(day,a.mydate,b.mydate) = 1
GROUP BY a.empid, startdate
HAVING consecutive_count > 30
This is a good case for a recursive CTE. I stole the data table from #Davin:
with data AS --sample data
( SELECT 1 as id ,DATEADD(DD,-0,GETDATE()) as date UNION ALL
SELECT 1 as id ,DATEADD(DD,-1,GETDATE()) as date UNION ALL
SELECT 1 as id ,DATEADD(DD,-2,GETDATE()) as date UNION ALL
SELECT 1 as id ,DATEADD(DD,-3,GETDATE()) as date UNION ALL
SELECT 1 as id ,DATEADD(DD,-4,GETDATE()) as date UNION ALL
SELECT 1 as id ,DATEADD(DD,-5,GETDATE()) as date UNION ALL
SELECT 1 as id ,DATEADD(DD,-10,GETDATE()) as date UNION ALL
SELECT 1 as id ,'2011-01-01 00:00:00.000' as date UNION ALL
SELECT 1 as id ,'2010-12-31 00:00:00.000' as date UNION ALL
SELECT 1 as id ,'2011-02-01 00:00:00.000' as date UNION ALL
SELECT 1 as id ,DATEADD(DD,-10,GETDATE()) as date UNION ALL
SELECT 2 as id ,DATEADD(DD,0,GETDATE()) as date UNION ALL
SELECT 2 as id ,DATEADD(DD,-1,GETDATE()) as date UNION ALL
SELECT 2 as id ,DATEADD(DD,-2,GETDATE()) as date UNION ALL
SELECT 2 as id ,DATEADD(DD,-6,GETDATE()) as date UNION ALL
SELECT 3 as id ,DATEADD(DD,0,GETDATE()) as date UNION ALL
SELECT 4 as id ,DATEADD(DD,0,GETDATE()) as date UNION ALL
SELECT 5 as id ,DATEADD(DD,0,GETDATE()) as date )
,CTE AS
(
SELECT id, CAST(date as date) Date, Consec = 1
FROM data
UNION ALL
SELECT t.id, CAST(t.date as DATE) Date, Consec = (c.Consec + 1)
FROM data T
INNER JOIN CTE c
ON T.id = c.id
AND CAST(t.date as date) = CAST(DATEADD(day, 1, c.date) as date)
)
SELECT id, MAX(consec)
FROM CTE
GROUP BY id
ORDER BY id
Basically this generates a lot of rows per person, and measures how many days in a row each date represents.
Assuming there are no duplicate dates for the same employee:
;WITH ranged AS (
SELECT
EmpId,
Date,
RangeId = DATEDIFF(DAY, 0, Date)
- ROW_NUMBER() OVER (PARTITION BY EmpId ORDER BY Date)
FROM atable
)
SELECT
EmpId,
StartDate = MIN(Date),
EndDate = MAX(Date),
DayCount = DATEDIFF(DAY, MIN(Date), MAX(Date)) + 1
FROM ranged
GROUP BY EmpId, RangeId
HAVING DATEDIFF(DAY, MIN(Date), MAX(Date)) + 1 >= 30
ORDER BY EmpId, MIN(Date)
DATEDIFF turns the dates into integers (the difference of days between the 0 date (1900-01-01) and Date). If the dates are consecutive, the integers are consecutive too. Using the data sample in the question as an example, the DATEDIFF results will be:
EmpId Date DATEDIFF
----- ---------- --------
1 2011-01-01 40542
1 2011-01-02 40543
1 2011-01-03 40544
2 2011-02-03 40575
3 2011-03-01 40601
4 2011-03-02 40602
5 2011-01-02 40543
Now, if you take each employee's rows, assign row numbers to them in the order of dates, and get the difference between the numeric representations and row numbers, you will find that the difference stays the same for consecutive numbers (and, therefore, consecutive dates). Using a slightly different sample for better illustration, it will look like this:
Date DATEDIFF RowNum RangeId
---------- -------- ------ -------
2011-01-01 40542 1 40541
2011-01-02 40543 2 40541
2011-01-03 40544 3 40541
2011-01-05 40546 4 40542
2011-01-07 40548 5 40543
2011-01-08 40549 6 40543
2011-01-09 40550 7 40543
The specific value of RangeId is not important, only the fact that it remains the same for consecutive dates matters. Based on that fact, you can use it as a grouping criterion to count the dates in the group and get the range bounds.
The above query uses DATEDIFF(DAY, MIN(Date), MAX(Date)) + 1 to count the days, but you could also simply use COUNT(*) instead.