SQL Server : CTE Months between dateranges - sql

I have a table with the following structure:
StartDate datetime NOT NULL
EndDate datetime NOT NULL
EnrollmentId nvarchar(255) NOT NULL
ProgrammeId nvarchar(255) not null
EnrollmentId is the primary key, and is a bit like a GUID string, and ProgrammeId refers to a 'subject code' a bit like a GUID too.
What I want to do is determine the total number of months a Programme has accrued during a specific date range for ALL enrollments e.g. from 01/01/2011 to 1st April 2014.
So let's say I have an enrollment which exists between 08/08/2010 to 01/01/2012 for a particular programmeId. I would want to count all the months that had accrued after 01/01/2011. So any months prior to this date should not count i.e. 08/08/2010 to 31/12/2010 should not be counted.
Ideally I'd like a count of how many months per ProgrammeId for a given date range.
Just a few notes:
I would need to count whole months so if an enrollment begins at 08/01/2011 and ends at 20/01/2012 only 12 months should be considered.
The period in which I want to count the months usually are at the beginning of the respective month i.e. 1st of jan 2011 to 1st of Apr 2014, 1st of May 2014 etc etc.
Some code to return all months for an enrollment:
;with MonthList as (
select
DATEADD(month, M, '12/1/1899') as 'FirstDay',
dateadd(day, -1, dateadd(month, M + 1, '12/1/1899')) as 'LastDay',
DATEADD(month, M + 1, '12/1/1899') as 'FirstDayNextMonth'
from (
select top 3000 ROW_NUMBER() over (order by s.name) as 'M'
from master..spt_values s) s
)
select
t.ProgrammeId, ml.FirstDay, ml.LastDay
from
Enrollment t
inner join MonthList ml
on t.startdate < ml.FirstDayNextMonth
and t.enddate >= ml.FirstDay
Thanks in advance

You may need a function like the one in Calculating number of full months between two dates in SQL to get the full months.
assuming you have something like that I would filter to make sure you are in range and chip the start and the end to fit the range in a CTE as you suggest.
3.The grouping and summing is easy.
Set up some sample data:
CREATE TABLE #tbl (
EnrollmentId INT NOT NULL --NOTE: using INT instead of your VARCHAR becuas eit easier and doesn't matter in sample
,ProgrammeId INT NOT NULL
,StartDate DATETIME NOT NULL
,EndDate DATETIME NOT NULL
)
INSERT INTO #tbl VALUES
(1,1,'2013-01-01','2014-01-01'),
(2,1,'2013-07-01','2014-01-01'),
(3,2,'2013-01-01','2014-01-01')
(4,3,'2013-01-15','2014-03-01')
Now declare the search range and make the query
DECLARE #RangeStart DATETIME = '2013-01-01'
DECLARE #RangeEnd DATETIME = '2013-12-01'
;WITH cte AS (
SELECT EnrollmentId
,ProgrammeId
,CASE WHEN #RangeStart >= StartDate THEN #RangeStart ELSE StartDate END EffectStartDate
,CASE WHEN #RangeEnd <= EndDate THEN #RangeEnd ELSE EndDate END EffectEndDate
FROM #tbl
WHERE #RangeStart BETWEEN StartDate AND EndDate --start date is in range
OR #RangeEnd BETWEEN StartDate AND EndDate --or end date is in range
OR (EndDate > #RangeEnd AND StartDate < #RangeStart) --or period contains the range
)
SELECT ProgrammeId
,SUM(dbo.FullMonthsSeparation(EffectStartDate,EffectEndDate)) Months
FROM cte
GROUP BY ProgrammeId
Results from sample:
ProgrammeId Months
----------- -----------
1 16
2 11
3 10

I thought I had an answer for you, but your update confused me as to what you actually wanted. I'm including the statement I wrote (SQL Fiddle) in the hope that it will be helpful.
SELECT ProgrammeId,
DATEDIFF(MONTH,
CASE WHEN '2011-01-01 00:00:00' > MIN(StartDate) THEN '2011-01-01 00:00:00'
ELSE MIN(StartDate)
END,
CASE WHEN '2014-04-01 00:00:00' < MAX(EndDate) THEN '2014-04-01 00:00:00'
ELSE MAX(EndDate)
END
) As Months
FROM ProgrammeEnrollment
GROUP BY ProgrammeID
;
SQL Server doesn't have GREATEST and LEAST, so I had to improvise with CASE statements. If you were using a RDBMS that did have GREATEST and LEAST, they'd be drop in replacements for the CASE statements.

Related

Find number of days that intersect a given date range in a table of date ranges

I want to find the total number of days in a date range that overlap a table of date ranges.
For example, I have 7 days between 2 dates in the table below. I want to find the days between between them that also fall this date range: 2019-08-01 to 2019-08-30.
It should return 1 day.
This is the data source query:
SELECT LeaveId, UserId, StartDate, EndDate, Days
FROM TblLeaveRequest
WHERE UserId = 218
LeaveID UserID StartDate EndDate Days
----------- ----------- ----------------------- ----------------------- -----------
22484 218 2019-07-26 00:00:00.000 2019-08-01 00:00:00.000 7
I believe this might help you:
--create the table
SELECT
22484 LeaveID,
218 UserID,
CONVERT(DATETIME,'7/26/2019') StartDate,
CONVERT(DATETIME,'8/1/2019') EndDate,
7 Days
INTO #TblLeaveRequest
--Range Paramters
DECLARE #StartRange AS DATETIME = '8/1/2019'
DECLARE #EndRange AS DATETIME = '8/30/2019'
--Find sum of days between StartDate and EndDate that intersect the range paramters
--for UserId=218
SELECT
SUM(
DATEDIFF(
DAY
,CASE WHEN #StartRange < StartDate THEN StartDate ELSE #StartRange END
,DATEADD(DAY, 1, CASE WHEN #EndRange > EndDate THEN EndDate ELSE #EndRange END)
)
) TotalDays
from #TblLeaveRequest
where UserId=218
This assumes that no start dates are greater than end dates. It also assumes that the range parameters always intersect some portion of the range in the table.
If the parameter ranges might not intersect then you'll have to eliminate those cases by excluding negative days:
SELECT SUM( CASE WHEN Days < 0 THEN 0 ELSE Days END ) TotalDays
FROM
(
SELECT
DATEDIFF(
DAY
,CASE WHEN #StartRange < StartDate THEN StartDate ELSE #StartRange END
,DATEADD(DAY, 1, CASE WHEN #EndRange > EndDate THEN EndDate ELSE #EndRange END)
) Days
from #TblLeaveRequest
where UserId=218
) TotalDays
if I understand your problem correctly, you have two ranges of dates and you are looking for the number of days in the intersection.
Considering a Gant chart:
Start_1 .................... End_1
Start_2 .......................End_2
If you can create a table structure like
LeaveID UserID StartDate_1 EndDate_1 StartDate_2 EndDate_2
----------- ----------- ---------- --------- ---------- ---------
22484 218 2019-07-26 2019-08-01 2019-08-01 2019-08-30
You can determine the number of days by
select
leaveID
, UserID
,case
when StartDate_2 <= EndDate_1 then datediff(day,StartDate_2,EndDate_1) + 1
else 0
end as delta_days_intersection
from table
I hope that helps
You can use a numbers (Tally) table to count the number of days:
SQL Fiddle
MS SQL Server 2017 Schema Setup:
CREATE TABLE LeaveRequest
(
LeaveId Int,
UserId Int,
StartDate Date,
EndDate Date,
Days Int
)
Insert Into LeaveRequest
VALUES
(22484, 218, '2019-07-26','2019-08-01', 7)
Query 1:
DECLARE #StartDate Date = '2019-08-01'
DECLARE #EndDate Date = '2019-08-30'
;WITH Tally
AS
(
SELECT ROW_NUMBER() OVER (ORdER By Numbers.Num) AS Num
FROM
(
Values(1),(2),(3),(4),(5),(6),(7),(8),(9)
)Numbers(Num)
Cross APPLY
(
Values(1),(2),(3),(4),(5),(6),(7),(8),(9)
)Numbers2(Num2)
)
SELECT COUNT(DATEADD(d, Num -1, StartDate)) As NumberOfDays
FROM LeaveRequest
CROSS APPLY Tally
WHERE DATEADD(d, Num -1, StartDate) <= EndDate AND
DATEADD(d, Num -1, StartDate) >= #StartDate AND
DATEADD(d, Num -1, StartDate) <= #EndDate
Results:
| NumberOfDays |
|--------------|
| 1 |
CREATE TABLE #LeaveRequest
(
LeaveId Int,
UserId Int,
StartDate Date,
EndDate Date,
Days Int
)
Insert Into #LeaveRequest
VALUES
(22484, 218, '2019-07-26','2019-08-01', 7)
Declare #FromDate datetime='2019-08-01'
declare #ToDate datetime='2019-08-30'
;With CTE as
(
select top (DATEDIFF(day,#FromDate,#ToDate)+1)
DATEADD(day, ROW_NUMBER()over(order by (select null))-1,#FromDate) DT
from sys.objects
)
select * from #LeaveRequest LR
cross apply(select count(*)IntersectingDays
from CTE c where dt between lr.StartDate and lr.EndDate)ca
--or
--select lr.*,c.*
from CTE c
--cross apply
(select lr.* from #LeaveRequest LR
where c.DT between lr.StartDate and lr.EndDate)lr
drop table #LeaveRequest
Better create one Calendar table which will always help you in other queries also.
create table CalendarDate(Dates DateTime primary key)
insert into CalendarDate WITH (TABLOCK) (Dates)
select top (1000000)
DATEADD(day, ROW_NUMBER()over(order by (select null))-1,'1970-01-01') DT
from sys.objects
Then inside CTE write this,
select top (DATEDIFF(day,#FromDate,#ToDate)+1) Dates from CalendarDate
where Dates between #FromDate and #ToDate
DECLARE #FromDate datetime = '2019-08-01'
DECLARE #ToDate datetime = '2019-08-30'
SELECT
IIF(#FromDate <= EndDate AND #ToDate >= StartDate,
DATEDIFF(day,
IIF(StartDate > #FromDate, StartDate, #FromDate),
IIF(EndDate < #ToDate, EndDate, #ToDate)
),
0) AS overlapping_days
FROM TblLeaveRequest;

How to split revenue by day between two months

We have group bookings held in our system as blocks. I can get total revenue held on the block per group by arrival and departure date.
The problem is that the revenue per group is the total revenue shown is that generated during the whole period the bedrooms are blocked for. This would be ok if each group arrived and left in the same month. However, there are groups that arrive in a month and leave on the following month.
I need to split the revenue for these groups by month. So, for example if a group arrives on 28/06 and leaves on 3/07, I need to know how much of that revenue is generated in June (3 nights) and how much in July (2 nights).
The query below is correct but will give me total revenue based on EndDate (check out date), so revenue will all go on the month the group departs.
I am also comparing same time last year
SELECT DAILYREV = ProjectedRevenueAccomNett/ DATEDIFF(Day,BeginDate, EndDate), DATEDIFF(DAY,BeginDate, EndDate) AS StayNights, Year(BeginDate) ArrivalYear, GroupRef, GAStatus, SourceSiteId, BeginDate, EndDate, CreatedTimestamp, DefMarketSegmentCode,ProjectedRevenueAccomNett, ProjectedRevenueFBNett
From SyncGroupRoomBlockHeaders
WHERE CreatedTimestamp <= '2019-03-28'
AND BeginDate BETWEEN '2019-03-28' AND '2019-12-31'
or
CreatedTimestamp <= '2018-03-28'
AND BeginDate BETWEEN '2018-03-28' AND '2018-12-31'
Order By YEAR (BeginDate)
Your question can be interpreted in different ways. I think the following query will point you in the right direction. Maybe you can use this approach and edit it to your own needs:
USE TEMPDB
IF OBJECT_ID ('TEMPDB..Bookings') IS NOT NULL DROP TABLE Bookings;
CREATE TABLE Bookings (ID INT,
StartDate DATE,
EndDate Date,
Total DECIMAL (8,2),
NumberDays AS DATEDIFF (DAY, StartDate, EndDate))
INSERT INTO Bookings VALUES (1, '20190130', '20190202', 210.15)
IF OBJECT_ID ('TEMPDB..BookingNew') IS NOT NULL DROP TABLE BookingNew;
SELECT *, Total / NumberDays AS PricePerDay
INTO BookingNew
FROM Bookings
DECLARE #StartDate DATE
DECLARE #EndDate DATE
SET #StartDate = '2000-01-01' -- << user input >>
SET #EndDate = '2040-12-31' -- << user input >>
IF OBJECT_ID ('TEMPDB..#Date') IS NOT NULL DROP TABLE #Date
IF OBJECT_ID ('TEMPDB..#Date') IS NULL CREATE TABLE #Date (Date DATE)
INSERT INTO #Date VALUES (#StartDate)
WHILE #StartDate < #EndDate
BEGIN
INSERT INTO #Date
SELECT DATEADD (DD, 1, #StartDate) AS Date
SET #StartDate = DATEADD (DD, 1, #StartDate)
END
SELECT *
FROM #Date AS D
INNER JOIN BookingNew AS B ON D.Date BETWEEN B.StartDate AND B.EndDate

SQL query to find employee aniversary

I need to find anniversary date and anniversary year of employees and send email in every 14 days.But I have a problem with last week of December when using the following query if start date and end date are in different years.
Select * from Resource
where (DATEPART(dayofyear,JoinDate)
BETWEEN DATEPART(dayofyear,GETDATE())
AND DATEPART(dayofyear,DateAdd(DAY,14,GETDATE())))
Instead of comparing to a dayofyear (which resets to zero at jan 1st and is the reason your query breaks within 14 days of the end of the year) you could update the employee's joindate to be the current year for the purpose of the query and just compare to actual dates
Select * from Resource
-- Add the number of years difference between joinDate and the current year
where DATEADD(year,DATEDIFF(Year,joinDate,GetDate()),JoinDate)
-- compare to range "today"
BETWEEN GetDate()
-- to 14 days from today
AND DATEADD(Day,14,GetDate())
-- duplicate for following year
OR DATEADD(year,DATEDIFF(Year,joinDate,GetDate())+1,JoinDate) -- 2016-1-1
BETWEEN GetDate()
AND DATEADD(Day,14,GetDate())
Test query:
declare #joindate DATETIME='2012-1-1'
declare #today DATETIME = '2015-12-26'
SELECT #joinDate
where DATEADD(year,DATEDIFF(Year,#joinDate,#today),#JoinDate) -- 2015-1-1
BETWEEN #today -- 2015-12-26
AND DATEADD(Day,14,#today) -- 2016-01-09
OR DATEADD(year,DATEDIFF(Year,#joinDate,#today)+1,#JoinDate) -- 2016-1-1
BETWEEN #today -- 2015-12-26
AND DATEADD(Day,14,#today) -- 2016-01-09
(H/T #Damien_The_Unbeliever for a simple fix)
The above correctly selects the joinDate which is in the first week of Jan (note I've had to fudge #today as Ive not managed to invent time travel).
The above solution should also solve the issue with leap years that was hiding in your original solution.
Update
You expressed in comments the requirement to select AnniversaryDate and Years of service, you need to apply some CASE logic to determine whether to add 1 (year or date) to your select
select *,
CASE
WHEN DATEADD(YEAR,DATEDIFF(Year,JoinDate,GETDATE()),JoinDate) < GetDate()
THEN DATEDIFF(Year,JoinDate,GETDATE())+1
ELSE DATEDIFF(Year,JoinDate,GETDATE())
END as [Years],
CASE WHEN DATEADD(YEAR,DATEDIFF(Year,JoinDate,GETDATE()),JoinDate) < GetDate()
THEN DATEADD(YEAR,DATEDIFF(Year,JoinDate,GETDATE())+1,JoinDate)
ELSE DATEADD(YEAR,DATEDIFF(Year,JoinDate,GETDATE()),JoinDate)
end as [AnniversaryDate]
.... // etc
You could do this:
Select * from Resource
where DATEPART(dayofyear,JoinDate)
BETWEEN DATEPART(dayofyear,GETDATE())
AND DATEPART(dayofyear,DateAdd(DAY,14,GETDATE()))
OR
DATEPART(dayofyear,JoinDate)
BETWEEN (DATEPART(dayofyear,GETDATE()) + 365)
AND (DATEPART(dayofyear,DateAdd(DAY,14,GETDATE())) + 365)
Try this:
DECLARE #Today DATE = GETDATE() --'12/25/2013'
DECLARE #Duration INT = 14
;WITH Recur AS
(
SELECT #Today AS RecurDate
UNION ALL
SELECT DATEADD(DAY, 1, RecurDate)
FROM Recur
WHERE DATEDIFF(DAY, #Today, RecurDate)+1 < #Duration
)
SELECT
r.*
FROM
Resource r
JOIN Recur
ON CONVERT(VARCHAR(5), JoinDate, 101) = CONVERT(VARCHAR(5), RecurDate, 101)
WHERE JoinDate < #Today
You can use the SQL DATEADD() function with week number parameter
Here is how you can use it:
DECLARE #date date = getdate()
Select * from Resource
where
JoinDate BETWEEN #date AND DATEADD(ww,2,#date)

How to find the total playing time per day for all the users in my sql server database

I have a table which contains following columns
userid,
game,
gameStarttime datetime,
gameEndtime datetime,
startdate datetime,
currentdate datetime
I can retrieve all the playing times but I want to count the total playing time per DAY and 0 or null if game not played on a specific day.
Take a look at DATEDIFF to do the time calculations. Your requirements are not very clear, but it should work for whatever you're looking to do.
Your end result would probably look something like this:
SELECT
userid,
game,
DATEDIFF(SS, gameStarttime, gameEndtime) AS [TotalSeconds]
FROM [source]
GROUP BY
userid,
game
In the example query above, the SS counts the seconds between the 2 dates (assuming both are not null). If you need just minutes, then MI will provide the total minutes. However, I imagine total seconds is best so that you can convert to whatever unit of measure you need accurate, such as hours that might be "1.23" or something like that.
Again, most of this is speculation based on assumptions and what you seem to be looking for. Hope that helps.
MSDN Docs for DATEDIFF: https://msdn.microsoft.com/en-us/library/ms189794.aspx
You may also look up DATEPART if you want the minutes and seconds separately.
UPDATED BASED ON FEEDBACK
The query below breaks out the hour breakdowns by day, splits time across multiple days, and shows "0" for days where no games are played. Also, for your output, I have to assume you have a separate table of users (so you can show users who have no time in your date range).
-- Define start date
DECLARE #BeginDate DATE = '4/21/2015'
-- Create sample data
DECLARE #Usage TABLE (
userid int,
game nvarchar(50),
gameStartTime datetime,
gameEndTime datetime
)
DECLARE #Users TABLE (
userid int
)
INSERT #Users VALUES (1)
INSERT #Usage VALUES
(1, 'sample', '4/25/2015 10pm', '4/26/2015 2:30am'),
(1, 'sample', '4/22/2015 4pm', '4/22/2015 4:30pm')
-- Generate list of days in range
DECLARE #DayCount INT = DATEDIFF(DD, #BeginDate, GETDATE()) + 1
;WITH CTE AS (
SELECT TOP (225) [object_id] FROM sys.all_objects
), [Days] AS (
SELECT TOP (#DayCount)
DATEADD(DD, ROW_NUMBER() OVER (ORDER BY x.[object_id]) - 1, #BeginDate) AS [Day]
FROM CTE x
CROSS JOIN CTE y
ORDER BY
[Day]
)
SELECT
[Days].[Day],
Users.userid,
SUM(COALESCE(CONVERT(MONEY, DATEDIFF(SS, CASE WHEN CONVERT(DATE, Usage.gameStartTime) < [Day] THEN [Day] ELSE Usage.gameStartTime END,
CASE WHEN CONVERT(DATE, Usage.gameEndTime) > [Day] THEN DATEADD(DD, 1, [Days].[Day]) ELSE Usage.gameEndTime END)) / 3600, 0)) AS [Hours]
FROM [Days]
CROSS JOIN #Users Users
LEFT OUTER JOIN #Usage Usage
ON Usage.userid = Users.userid
AND [Days].[Day] BETWEEN CONVERT(DATE, Usage.gameStartTime) AND CONVERT(DATE, Usage.gameEndTime)
GROUP BY
[Days].[Day],
Users.userid
The query above yields the output below for the sample data:
Day userid Hours
---------- ----------- ---------------------
2015-04-21 1 0.00
2015-04-22 1 0.50
2015-04-23 1 0.00
2015-04-24 1 0.00
2015-04-25 1 2.00
2015-04-26 1 2.50
2015-04-27 1 0.00
I've edited my sql on sql fiddle and I think this might get you what you asked for. to me it looks a little more simple then the answer you've accepted.
DECLARE #FromDate datetime, #ToDate datetime
SELECT #Fromdate = MIN(StartDate), #ToDate = MAX(currentDate)
FROM Games
-- This recursive CTE will get you all dates
-- between the first StartDate and the last CurrentDate on your table
;WITH AllDates AS(
SELECT #Fromdate As TheDate
UNION ALL
SELECT TheDate + 1
FROM AllDates
WHERE TheDate + 1 <= #ToDate
)
SELECT UserId,
TheDate,
COALESCE(
SUM(
-- When the game starts and ends in the same date
CASE WHEN DATEDIFF(DAY, GameStartTime, GameEndTime) = 0 THEN
DATEDIFF(HOUR, GameStartTime, GameEndTime)
ELSE
-- when the game starts in the current date
CASE WHEN DATEDIFF(DAY, GameStartTime, TheDate) = 0 THEN
DATEDIFF(HOUR, GameStartTime, DATEADD(Day, 1, TheDate))
ELSE -- meaning the game ends in the current date
DATEDIFF(HOUR, TheDate, GameEndTime)
END
END
),
0) As HoursPerDay
FROM (
SELECT DISTINCT UserId,
TheDate,
CASE
WHEN CAST(GameStartTime as Date) = TheDate
THEN GameStartTime
ELSE NULL
END As GameStartTime, -- return null if no game started that day
CASE
WHEN CAST(GameEndTime as Date) = TheDate
THEN GameEndTime
ELSE NULL
END As GameEndTime -- return null if no game ended that day
FROM Games CROSS APPLY AllDates -- This is where the magic happens :-)
) InnerSelect
GROUP BY UserId, TheDate
ORDER BY UserId, TheDate
OPTION (MAXRECURSION 0)
Play with it your self on sql fiddle.

Grouping by contiguous dates, ignoring weekends in SQL

I'm attempting to group contiguous date ranges to show the minimum and maximum date for each range. So far I've used a solution similar to this one: http://www.sqlservercentral.com/articles/T-SQL/71550/ however I'm on SQL 2000 so I had to make some changes. This is my procedure so far:
create table #tmp
(
date smalldatetime,
rownum int identity
)
insert into #tmp
select distinct date from testDates order by date
select
min(date) as dateRangeStart,
max(date) as dateRangeEnd,
count(*) as dates,
dateadd(dd,-1*rownum, date) as GroupID
from #tmp
group by dateadd(dd,-1*rownum, date)
drop table #tmp
It works exactly how I want except for one issue: weekends. My data sets have no records for weekend dates, which means any group found is at most 5 days. For instance, in the results below, I would like the last 3 groups to show up as a single record, with a dateRangeStart of 10/6 and a dateRangeEnd of 10/20:
Is there some way I can set this up to ignore a break in the date range if that break is just a weekend?
Thanks for the help.
EDITED
I didn't like my previous idea very much. Here's a better one, I think:
Based on the first and the last dates from the set of those to be grouped, prepare the list of all the intermediate weekend dates.
Insert the working dates together with weekend dates, ordered, so they would all be assigned rownum values according to their normal order.
Use your method of finding contiguous ranges with the following modifications:
1) when calculating dateRangeStart, if it's a weekend date, pick the nearest following weekday;
2) accordingly for dateRangeEnd, if it's a weekend date, pick the nearest preceding weekday;
3) when counting dates for the group, pick only weekdays.
Select from the resulting set only those rows where dates > 0, thus eliminating the groups formed only of the weekends.
And here's an implementation of the method, where it is assumed, that a week starts on Sunday (DATEPART returns 1) and weekend days are Sunday and Saturday:
DECLARE #tmp TABLE (date smalldatetime, rownum int IDENTITY);
DECLARE #weekends TABLE (date smalldatetime);
DECLARE #minDate smalldatetime, #maxDate smalldatetime, #date smalldatetime;
/* #1 */
SELECT #minDate = MIN(date), #maxDate = MAX(date)
FROM testDates;
SET #date = #minDate - DATEPART(dw, #minDate) + 7;
WHILE #date < #maxDate BEGIN
INSERT INTO #weekends
SELECT #date UNION ALL
SELECT #date + 1;
SET #date = #date + 7;
END;
/* #2 */
INSERT INTO #tmp
SELECT date FROM testDates
UNION
SELECT date FROM #weekends
ORDER BY date;
/* #3 & #4 */
SELECT *
FROM (
SELECT
MIN(date + CASE DATEPART(dw, date) WHEN 1 THEN 1 WHEN 7 THEN 2 ELSE 0 END)
AS dateRangeStart,
MAX(date - CASE DATEPART(dw, date) WHEN 1 THEN 2 WHEN 7 THEN 1 ELSE 0 END)
AS dateRangeEnd,
COUNT(CASE WHEN DATEPART(dw, date) NOT IN (1, 7) THEN date END) AS dates,
DATEADD(d, -rownum, date) AS GroupID
FROM #tmp
GROUP BY DATEADD(d, -rownum, date)
) s
WHERE dates > 0;