I'm trying to make a report that generates a calendar for each month where the user can specify a range of months and years. My matrix has a column grouping on Weekday and the row grouping is on WeekNumber (see query results below).
The issue I'm stuck on is that the day is starting in the wrong place as shown below:
This is because my query only takes in the days from that particular month and not the few days from the month before and after that would normally be grayed out on a calendar:
Although I suspect this problem may also be solvable in the matrix itself I'm not sure if solving it in the query or solving it in the matrix would be better than the other.
My current query:
SELECT t1.[TimeSheetLineID] ,
t1.[TimeSheetID] ,
t1.[JobID] ,
t1.[TimeSheetDate] ,
t1.[TimeSheetCreatedOn] ,
t1.[TimeSheetLastModifiedOn] ,
t1.[StartTime] ,
t1.[EndTime] ,
t1.[TotalTime] ,
t1.[EmployeeName] ,
t2.DateFull ,
t2.[FullYear] ,
t2.[WeekNumber] ,
t2.[WeekDay] ,
t2.[WeekDayName] ,
t2.[MonthDay] ,
t2.[MonthName] ,
t2.[MonthNumber]
FROM dbo.DateLookup t2
LEFT JOIN [dbo].[FactTimeSheets] t1
ON t1.TimeSheetDate = t2.DateFull
AND (t1.JobID = #jobNumber)
WHERE (t2.FullYear = #year)
AND (t2.MonthNumber BETWEEN #startMonth AND #endMonth)
ORDER BY FullYear, MonthNumber, MonthDay, WeekDay
Current query result (notice WeekDay starts with 2 for the month of Aug):
Based off this article.
--UPDATE--
I'm using the CTE in my query below to get the extra dates I need to fill out this calendar. I tested it on the Date table and will update my answer tomorrow with the final query:
DECLARE #year INT ,
#startMonth DATE ,
#endMonth DATE;
SET #year = '2016';
SET #startMonth = '2016-08-01';
SET #endMonth = '2016-08-31';
DECLARE #StartDate DATE ,
#EndDate DATE;
SET #StartDate = DATEADD(s, 0, DATEADD(mm, DATEDIFF(m, 0, #startMonth), 0));
SET #StartDate = DATEADD(DAY, -DATEPART(WEEKDAY, #StartDate) + 1, #StartDate);
--SELECT #StartDate
SET #EndDate = DATEADD(s, -1, DATEADD(mm, DATEDIFF(m, 0, #startMonth) + 1, 0));
SET #EndDate = DATEADD(DAY, 7 - DATEPART(WEEKDAY, #EndDate), #EndDate)
--SELECT #EndDate;
;
WITH Dates ( [Date] )
AS (
--Select First day in range
SELECT CONVERT(DATETIME, #StartDate) AS [Date]
UNION ALL
--Add a record for every day in the range
SELECT DATEADD(DAY, 1, [Date])
FROM Dates
WHERE Date < CONVERT(DATETIME, #EndDate)
),
Events
AS ( SELECT [FullYear] ,
[DateFull] ,
[WeekNumber] ,
[WeekDay] ,
[WeekDayName] ,
[MonthDay] ,
[MonthName] ,
[MonthNumber]
FROM [dbo].[DateLookup]
)
SELECT e.[FullYear] ,
e.[DateFull] ,
d.[Date] ,
e.[WeekNumber] ,
e.[WeekDay] ,
e.[WeekDayName] ,
e.[MonthDay] ,
e.[MonthName] ,
e.[MonthNumber]
FROM Dates d
LEFT JOIN Events e ON d.[Date] = CAST(e.DateFull AS DATE)
GROUP BY FullYear ,
MonthNumber ,
DateFull ,
d.[Date] ,
WeekNumber ,
MonthName ,
WeekDayName ,
MonthDay ,
WeekDay
ORDER BY Date;
--FINAL UPDATED QUERY--
I hope this helps someone in the future.
SET #startDate = DATEADD(mm, DATEDIFF(m, 0, CAST(CAST(#year AS varchar) + '-' + CAST(#startMonth AS varchar) + '-01' AS DATETIME)), 0);
SET #startDate = DATEADD(DAY, -DATEPART(WEEKDAY, #startDate) + 1, #startDate);
SET #endDate = DATEADD(mm, DATEDIFF(m, 0, CAST(CAST(#year AS varchar) + '-' + CAST(#endMonth AS varchar) + '-01' AS DATETIME)) + 1, 0);
SET #endDate = DATEADD(DAY, 7 - DATEPART(WEEKDAY, #endDate), #endDate);
WITH Dates ( [Date] )
AS ( SELECT CONVERT(DATETIME, #startDate) AS [Date]
UNION ALL
SELECT DATEADD(DAY, 1, [Date])
FROM Dates
WHERE Date < CONVERT(DATETIME, #endDate)
),
Events
AS ( SELECT t1.[TimeSheetLineID] ,
t1.[TimeSheetID] ,
t1.[JobID] ,
t1.[TimeSheetDate] ,
t1.[TimeSheetCreatedOn] ,
t1.[TimeSheetLastModifiedOn] ,
t1.[StartTime] ,
t1.[EndTime] ,
t1.[TotalTime] ,
t1.[EmployeeName] ,
t1.[CostCategory] ,
t2.[DateFull] ,
t2.[FullYear] ,
t2.[WeekNumber] ,
t2.[WeekDay] ,
t2.[WeekDayName] ,
t2.[MonthDay] ,
t2.[MonthName] ,
t2.[MonthNumber]
FROM dbo.DateLookup t2
LEFT JOIN [dbo].[FactTimeSheets] t1 ON t1.TimeSheetDate = t2.DateFull
AND ( t1.JobID = #jobNumber )
WHERE ( t2.FullYear = #year )
)
SELECT e.[TimeSheetLineID] ,
e.[TimeSheetID] ,
e.[JobID] ,
e.[TimeSheetDate] ,
e.[TimeSheetCreatedOn] ,
e.[TimeSheetLastModifiedOn] ,
e.[StartTime] ,
e.[EndTime] ,
e.[TotalTime] ,
e.[EmployeeName] ,
e.[CostCategory] ,
e.[DateFull] ,
d.[Date] ,
e.[FullYear] ,
e.[WeekNumber] ,
e.[WeekDay] ,
e.[WeekDayName] ,
e.[MonthDay] ,
e.[MonthName] ,
e.[MonthNumber]
FROM Dates d
LEFT JOIN Events e ON d.[Date] = CAST(e.DateFull AS DATE)
ORDER BY Date;
You can group the dates in a matrix to get the layout right.
Insert a matrix. Group and sort the columns by weekday (number). Group and sort the rows by week number. It should look like this:
Now when you run it it will come out like this:
Final answer:
SET #startDate = DATEADD(mm, DATEDIFF(m, 0, CAST(CAST(#year AS varchar) + '-' + CAST(#startMonth AS varchar) + '-01' AS DATETIME)), 0);
SET #startDate = DATEADD(DAY, -DATEPART(WEEKDAY, #startDate) + 1, #startDate);
SET #endDate = DATEADD(mm, DATEDIFF(m, 0, CAST(CAST(#year AS varchar) + '-' + CAST(#endMonth AS varchar) + '-01' AS DATETIME)) + 1, 0);
SET #endDate = DATEADD(DAY, 7 - DATEPART(WEEKDAY, #endDate), #endDate);
WITH Dates ( [Date] )
AS ( SELECT CONVERT(DATETIME, #startDate) AS [Date]
UNION ALL
SELECT DATEADD(DAY, 1, [Date])
FROM Dates
WHERE Date < CONVERT(DATETIME, #endDate)
),
Events
AS ( SELECT t1.[TimeSheetLineID] ,
t1.[TimeSheetID] ,
t1.[JobID] ,
t1.[TimeSheetDate] ,
t1.[TimeSheetCreatedOn] ,
t1.[TimeSheetLastModifiedOn] ,
t1.[StartTime] ,
t1.[EndTime] ,
t1.[TotalTime] ,
t1.[EmployeeName] ,
t1.[CostCategory] ,
t2.[DateFull] ,
t2.[FullYear] ,
t2.[WeekNumber] ,
t2.[WeekDay] ,
t2.[WeekDayName] ,
t2.[MonthDay] ,
t2.[MonthName] ,
t2.[MonthNumber]
FROM dbo.DateLookup t2
LEFT JOIN [dbo].[FactTimeSheets] t1 ON t1.TimeSheetDate = t2.DateFull
AND ( t1.JobID = #jobNumber )
WHERE ( t2.FullYear = #year )
)
SELECT e.[TimeSheetLineID] ,
e.[TimeSheetID] ,
e.[JobID] ,
e.[TimeSheetDate] ,
e.[TimeSheetCreatedOn] ,
e.[TimeSheetLastModifiedOn] ,
e.[StartTime] ,
e.[EndTime] ,
e.[TotalTime] ,
e.[EmployeeName] ,
e.[CostCategory] ,
e.[DateFull] ,
d.[Date] ,
e.[FullYear] ,
e.[WeekNumber] ,
e.[WeekDay] ,
e.[WeekDayName] ,
e.[MonthDay] ,
e.[MonthName] ,
e.[MonthNumber]
FROM Dates d
LEFT JOIN Events e ON d.[Date] = CAST(e.DateFull AS DATE)
ORDER BY Date;
August 1st is Monday so the weekday should be 1. But it's 2 in your table
Related
I have this result, [year.month week] with my query below
[2021.12 51],[2021.12 53],[2022.01 1],[2022.01 2],[2022.01 3],[2022.01 5],[2022.01 6],[2022.02 10]
My goal is to fill in the gaps of the week, for example:
[2021.12 51],**[2021.12 52]**,[2021.12 53],[2022.01 1],[2022.01 2],[2022.01 3],**[2022.01 4]**,[2022.01 5],[2022.01 6],**[2022.01 7]**,**[2022.01 8]**,**[2022.01 9]**,[2022.02 10]
How could I do this with my query below:
DECLARE
#startdate datetime,
#enddate datetime,
#paramdef nvarchar(max)
set #startdate = '2021-01-01 00:00:00.000';
set #enddate = GETDATE();
set #paramdef = '#startdate datetime, #enddate datetime';
SELECT STUFF
(
(
SELECT
',' + QUOTENAME( S_FORMATTED_WEEK_MONTH )
FROM
MONITORING
WHERE
S_DATE_TO >= #startdate AND
S_DATE_TO <= #enddate
GROUP BY
S_FORMATTED_WEEK_MONTH
ORDER BY
CAST( REPLACE( SUBSTRING(S_FORMATTED_WEEK_MONTH, 1, 7), '.', '' ) AS INT ),
CAST( REPLACE( SUBSTRING(S_FORMATTED_WEEK_MONTH, 8, 9), '.', '' ) AS INT )
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)') ,1,1,''
)
you can use the below calendar table approach to get the list of weeknumbers as per your format.
DECLARE #StartDate DATE = '2021-01-01'
DECLARE #EndDate DATE = getdate()
;WITH Cal(n) AS
(
SELECT 0 UNION ALL SELECT n + 1 FROM Cal
WHERE n < DATEDIFF(DAY, #StartDate, #EndDate)
),
FnlDt(d,y,m,w) AS
(
SELECT DATEADD(DAY, n, #StartDate)
, year(DATEADD(DAY, n, #StartDate))
, month(DATEADD(DAY, n, #StartDate))
, datepart(week,DATEADD(DAY, n, #StartDate)) FROM Cal
)
select
string_agg(weekarr,',') as concat_weekarr
from
(SELECT concat('[',y,'.',m,' ',w,']') as weekarr
FROM fnlDt
group by y,m,w
) as dt
option(maxrecursion 32767)
[2021.1 1],[2021.1 2],[2021.1 3],[2021.1 4],...
I am trying to group a total count of days within a month that a patient had a catheter line inserted. The data is broken down into stints so is not contiguous throughout the month. I also do not to count overlapping days between the stints. See screenshot and query below.
DECLARE #start_date DATETIME
DECLARE #end_date DATETIME
SET #start_date = '2/1/2022'
SET #end_date = '2/28/2022';
CREATE TABLE mytable(
Patient_ID INTEGER NOT NULL PRIMARY KEY
,startdate DATE NOT NULL
,enddate DATE NOT NULL
,Type_of_Line VARCHAR(4) NOT NULL
,Insertion_Date DATE NOT NULL
,Removal_Date DATE
,_of_Cath_Days INTEGER NOT NULL
);
INSERT INTO mytable(Patient_ID,startdate,enddate,Type_of_Line,Insertion_Date,Removal_Date,_of_Cath_Days) VALUES (10247,'2022-01-16','2022-02-11','Port','2021-08-03 00:00:00.000',NULL,11);
INSERT INTO mytable(Patient_ID,startdate,enddate,Type_of_Line,Insertion_Date,Removal_Date,_of_Cath_Days) VALUES (10247,'2022-02-11','2022-02-15','Port','2021-08-03 00:00:00.000',NULL,5);
INSERT INTO mytable(Patient_ID,startdate,enddate,Type_of_Line,Insertion_Date,Removal_Date,_of_Cath_Days) VALUES (10247,'2022-02-15','2022-02-24','Port','2021-08-03 00:00:00.000',NULL,10);
INSERT INTO mytable(Patient_ID,startdate,enddate,Type_of_Line,Insertion_Date,Removal_Date,_of_Cath_Days) VALUES (10247,'2022-02-24','2022-03-23','Port','2021-08-03 00:00:00.000',NULL,5);
WITH stat
AS (SELECT pt.ptkey,
ptid,
ptptinfusionstatus.startdate,
ptptinfusionstatus.enddate
FROM pt
LEFT JOIN ptptinfusionstatus
ON ptptinfusionstatus.ptkey = pt.ptkey
LEFT JOIN ptinfusionstatus
ON ptinfusionstatus.ptinfusionstatuskey =
ptptinfusionstatus.ptinfusionstatuskey
WHERE ptptinfusionstatus.ptinfusionstatuskey IN ( 1, 5 )),
access1
AS (SELECT d.NAME,
d.pharmacyeventandoutcometypedetailkey,
T.pharmacyeventandoutcometypekey
FROM pharmacyeventandoutcometypedetail d WITH (nolock)
LEFT JOIN pharmacyeventandoutcometype t WITH (nolock)
ON t.pharmacyeventandoutcometypekey =
d.pharmacyeventandoutcometypekey
LEFT JOIN pharmacyeventandoutcomelist l WITH (nolock)
ON l.pharmacyeventandoutcomelistkey =
t.pharmacyeventandoutcomelistkey
WHERE l.pharmacyeventandoutcomelistkey = 2),
access2
AS (SELECT stat.ptkey,
stat.ptid,
stat.startdate,
stat.enddate,
Isnull(devicetype.NAME, '') [Access Device_Type],
ppad.insertiondate [Access Device_Insertion Date],
ppad.removaldate [Access Device_Removal Date]
FROM stat WITH (nolock)
JOIN pharmacyptaccessdevice ppad WITH(nolock)
ON ppad.ptkey = stat.ptkey
LEFT JOIN access1 devicetype WITH (nolock)
ON devicetype.pharmacyeventandoutcometypedetailkey =
ppad.accessdevicetypekey
AND devicetype.pharmacyeventandoutcometypekey = 4)
--***MAIN QUERY***
SELECT access2.[ptid] AS 'Patient ID',
access2.startdate,
access2.enddate,
access2.[access device_type] AS 'Type of Line',
access2.[access device_insertion date] AS 'Insertion Date',
access2.[access device_removal date] AS 'Removal Date',
Datediff(d, CASE WHEN [access device_insertion date] >= #start_date AND
[access device_insertion date] >=access2.startdate THEN
access2.[access device_insertion date] WHEN access2.startdate >=
access2.[access device_insertion date] AND
access2.startdate >= #start_date THEN access2.startdate ELSE #start_date
END,
CASE WHEN #end_date <= Isnull(access2.enddate, #end_date) AND #end_date
<= Isnull(access2.[access device_removal date], #end_date) THEN #end_date
WHEN access2.enddate IS NOT NULL AND access2.enddate < #end_date AND
access2.enddate <= Isnull(access2.[access device_removal date],
access2.enddate) THEN access2.enddate ELSE
access2.[access device_removal date] END) + 1 AS '# of Cath Days'
FROM access2
WHERE access2.startdate <= #end_date
AND ( access2.enddate >= #start_date
OR access2.enddate IS NULL )
AND access2.[access device_insertion date] <= #end_date
AND ( access2.[access device_removal date] >= #start_date
OR access2.[access device_removal date] IS NULL )
AND access2.ptid = '10247'
I tried grouping by patient and adding a sum of days at the patient group level in SQL Reporting Services, but could not get around the overlapping days so the counts are all wrong.
Based on the data provided, below is an example code that will group based on patientId and if start date equals end date then it will subtract 1 from number of days.
DECLARE #start_date DATETIME
DECLARE #end_date DATETIME
SET #start_date = '2/1/2022'
SET #end_date = '2/28/2022';
declare #mytable table(
Patient_ID INTEGER NOT NULL -- PRIMARY KEY
,startdate DATE NOT NULL
,enddate DATE NOT NULL
,Type_of_Line VARCHAR(4) NOT NULL
,Insertion_Date DATE NOT NULL
,Removal_Date DATE
,_of_Cath_Days INTEGER NOT NULL
);
INSERT INTO #mytable(Patient_ID,startdate,enddate,Type_of_Line,Insertion_Date,Removal_Date,_of_Cath_Days) VALUES (10247,'2022-01-16','2022-02-11','Port','2021-08-03 00:00:00.000',NULL,11);
INSERT INTO #mytable(Patient_ID,startdate,enddate,Type_of_Line,Insertion_Date,Removal_Date,_of_Cath_Days) VALUES (10247,'2022-02-11','2022-02-15','Port','2021-08-03 00:00:00.000',NULL,5);
INSERT INTO #mytable(Patient_ID,startdate,enddate,Type_of_Line,Insertion_Date,Removal_Date,_of_Cath_Days) VALUES (10247,'2022-02-15','2022-02-24','Port','2021-08-03 00:00:00.000',NULL,10);
INSERT INTO #mytable(Patient_ID,startdate,enddate,Type_of_Line,Insertion_Date,Removal_Date,_of_Cath_Days) VALUES (10247,'2022-02-24','2022-03-23','Port','2021-08-03 00:00:00.000',NULL,5);
select
Patient_ID
, startdate
, enddate
, Type_of_Line
, Insertion_Date
, Removal_Date
, LAG([enddate]) OVER (ORDER BY [startdate]) as compareenddate
from #mytable
select Patient_ID
, min(startdate) as startdate
, max(enddate) as enddate
, max(Type_of_Line) as Type_of_Line
, min(Insertion_Date) as Insertion_Date
, max(Removal_Date) as Removal_Date
, sum(iif(startdate = isnull(compareenddate, '1900-01-01'), _of_Cath_Days - 1, _of_Cath_Days) )
from
(
select
Patient_ID
, startdate
, enddate
, Type_of_Line
, Insertion_Date
, Removal_Date
, _of_Cath_Days
, LAG([enddate]) OVER (ORDER BY [startdate]) as compareenddate
from #mytable
) x
group by Patient_ID
Query result
I'm in the process of writing a stored procedure (for SQL Server 2012) that is supposed to calculate the number of hours for our employee from 16-15th of every month.
I have the following database structure
I have written a stored procedure to calculate the hours but I think I can only get the week start date to filter my condition. The stored procedure is returning me the wrong result because the weekly start date is not always the 16th.
CREATE PROCEDURE [dbo].[spGetTotalHoursBetween16to15EveryMonth]
AS
BEGIN
SET NOCOUNT ON
BEGIN TRY
DECLARE #SixteenthDate datetime2(7) = DATEADD(DAY, 15, DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0))
DECLARE #currentDate datetime2(7) = getDate()
DECLARE #LastSixteenthDate datetime2(7) = DATEADD(DAY, 15, DATEADD(MONTH, DATEDIFF(MONTH, 0, DATEADD(mm, DATEDIFF(mm, 0, GETDATE()) - 1, 0)), 0))
IF(#currentDate >= #SixteenthDate)
BEGIN
SELECT
(Sum(Day1Hours) + sum(Day2Hours) + Sum(Day3Hours) +
sum(Day4Hours) + Sum(Day5Hours) + sum(Day6Hours) + Sum(Day7Hours)) AS Total
FROM
dbo.TimeSheets
WHERE
WeekStartDate BETWEEN DATEADD(wk, DATEDIFF(wk, 0, #SixteenthDate), -1) AND #currentDate
END
ELSE
BEGIN
SELECT
(Sum(Day1Hours) + sum(Day2Hours) + Sum(Day3Hours) +
sum(Day4Hours) + Sum(Day5Hours) + sum(Day6Hours) + Sum(Day7Hours)) AS Total
FROM
dbo.TimeSheets
WHERE
WeekStartDate BETWEEN DATEADD(wk, DATEDIFF(wk, 0, #LastSixteenthDate), -1) AND #currentDate
END
END TRY
BEGIN CATCH
THROW
END CATCH
END
I'd probably just do it the simple way:
declare #today date = convert(date,current_timestamp)
declare #prev_month_end date = dateadd( day , -day(#today) , #today )
declare #period_start date = dateadd( day , 16 , #prev_month_end ) -- 16th of THIS month
declare #period_end date = dateadd( month , 1 , #period_start ) -- 16th of NEXT month
select #period_start = dateadd(month, -1 , #period_start ) ,
#period_end = dateadd(month, -1 , #period_end )
where day(#today) < 16
select total_hours = coalesce(sum(t.hours),0)
from ( select id = t.id , report_date = dateadd(day,0,t.WeekStartDate) , hours = t.Day1Hours from dbo.TimeSheets t
union all select id = t.id , report_date = dateadd(day,1,t.WeekStartDate) , hours = t.Day2Hours from dbo.TimeSheets t
union all select id = t.id , report_date = dateadd(day,2,t.WeekStartDate) , hours = t.Day3Hours from dbo.TimeSheets t
union all select id = t.id , report_date = dateadd(day,3,t.WeekStartDate) , hours = t.Day4Hours from dbo.TimeSheets t
union all select id = t.id , report_date = dateadd(day,4,t.WeekStartDate) , hours = t.Day5Hours from dbo.TimeSheets t
union all select id = t.id , report_date = dateadd(day,5,t.WeekStartDate) , hours = t.Day6Hours from dbo.TimeSheets t
union all select id = t.id , report_date = dateadd(day,6,t.WeekStartDate) , hours = t.Day7Hours from dbo.TimeSheets t
) t
where t.report_date >= #period_start
and t.report_date < #period_end
Always start with start or an end of a month.
E.g. Here's some logic
Start Date = Start date of previous month + 16
End Date = Start date of current month + 15
Following might help you figure out the dates
-- First Day of the month
select DATEADD(mm, DATEDIFF(mm,0,getdate()), 0)
-- Last Day of previous month
select dateadd(ms,-3,DATEADD(mm, DATEDIFF(mm,0,getdate() ), 0))
More examples are here
Life is easier with a little data normalization. Don't work from the spreadsheet-style table
CREATE VIEW Timesheets_Normalized AS
SELECT
[id]
,[Date] = DATEADD(day,[Date_Offset],[WeekStartDate])
,[Hours]
FROM MyTable a
UNPIVOT([Hours] FOR [Date_Column] IN (Day1Hours,Day2Hours,Day3Hours,Day4Hours,Day5Hours,Day6Hours,Day7Hours)) b
INNER JOIN (VALUES (0,'Day1Hours'),(1,'Day2Hours'),(2,'Day3Hours'),(3,'Day4Hours'),(4,'Day5Hours'),(5,'Day6Hours'),(6,'Day7Hours')) c([Date_Offset],[Date_Column])
ON (b.[Date_Column] = c.[Date_Column])
Then you can get your answers very simply:
SELECT
MIN([Date]) AS [PayrollMonthStart]
,MAX([Date]) AS [PayrollMonthEnd]
,SUM([Hours]) AS [TotalHours]
FROM Timesheets_Normalized
GROUP BY YEAR(DATEADD(day,-15,[Date])),MONTH(DATEADD(day,-15,[Date]))
HAVING CAST(GETDATE() AS date) BETWEEN MIN([Date]) AND MAX([Date])
I would like to show the week column in 7 days increment. Below is my desired output example. Is there a way to write in SQL Server?
Week Football soccer baseball
10/01 - 10/07 1 2 3
10/08 - 10/14 4 5 6
10/15 - 10/21 5 8 0
Here is my attempted script:
CREATE TABLE #TEMP
(
DT CHAR(10),
TO_DT CHAR(10),
BALLs VARCHAR(100),
PRODUCT INT,
Week INT
)
DECLARE
#DT DATETIME,
#TO_DT DATETIME,
#i INT
BEGIN
SET #_DT = '10/01/2012'
SET #TO_DT = DATEADD(DAY,7,#_DT)
set #i=1
WHILE (#DT <= '12/31/2013')
BEGIN
INSERT INTO #TEMP (DT, TO_DT, BALLs, PRODUCT, week)
SELECT
CONVERT(CHAR(10),#DT,101) AS FROM_DT,
CONVERT(CHAR(10),DATEADD(DAY,-1,#TO_DT),101) AS TO_DT,
BALLS,
COUNT(PRODUCT) AS PRODUCT,
#I AS WEEK
FROM #TABLE
GROUP BY BALLS
SET #DT = DATEADD(DAY,7,#DT)
SET #TO_DT = DATEADD(DAY, 7,#TO_DT)
set #i = #i + 1
END
SELECT * FROM #TEMP
DROP TABLE #TEMP
END
DATEDIFF(WK, 0, ) should do the trick.
SELECT
DATEDIFF(WK, 0, MyDate)
, COUNT(*)
FROM (VALUES
('2013-01-01')
, ('2013-01-02')
, ('2013-01-03')
, ('2013-01-04')
, ('2013-01-05')
, ('2013-01-06')
, ('2013-01-07')
) AS X(MyDate)
GROUP BY DATEDIFF(WK, 0, MyDate)
EDIT:
With date formatting it goes like this:
SELECT
CONVERT(VARCHAR(5), DATEADD(WK, WK, 0), 103) + ' - ' + CONVERT(VARCHAR(5), DATEADD(DD, 6, DATEADD(WK, WK, 0)), 103)
, Cnt
FROM (
SELECT
DATEDIFF(WK, 0, MyDate) AS WK
, COUNT(*) AS Cnt
FROM (VALUES
('2013-01-01')
, ('2013-01-02')
, ('2013-01-03')
, ('2013-01-04')
, ('2013-01-05')
, ('2013-01-06')
, ('2013-01-07')
) AS X(MyDate)
GROUP BY DATEDIFF(WK, 0, MyDate)
) AS X
We have a production database that manages personnel booking at 100s of branches for years in advance with minute level accuracy.
Part of this system are reports that highlight gaps, i.e. compare branch opening hours and staff bookings to see if any branches are open with nobody booked.
It also checks for overlaps, double bookings etc all at the same time, basically minute level accuracy is required.
The way we're doing this is to expand the start and end times of openings hours and bookings into minutes with an integer tally table:
--===== Create and populate the Tally table on the fly
SELECT TOP 16777216
IDENTITY(INT,1,1) AS N
INTO dbo.Tally
FROM Master.dbo.SysColumns sc1,
Master.dbo.SysColumns sc2,
Master.dbo.SysColumns sc3
--===== Add a Primary Key to maximize performance
ALTER TABLE dbo.Tally
ADD CONSTRAINT PK_Tally_N
PRIMARY KEY CLUSTERED (N) WITH FILLFACTOR = 100
We utilise this static indexed tally table to expand opening hours and bookings as follows:
SELECT [BranchID] ,
[DayOfWeek] ,
DATEADD(MINUTE, N - 1, StartTime)
FROM OpeningHours
LEFT OUTER JOIN tally ON tally.N BETWEEN 0
AND DATEDIFF(MINUTE, OpeningHours.StartTime, OpeningHours.EndTime) + 1
The problem is, once we have the 13,000,000 "open minutes" and the "booked minutes" we then need to join the results to see what's covered:
SELECT OpenDatesAndMinutes.[Date] ,
OpenDatesAndMinutes.[Time] ,
OpenDatesAndMinutes.[BranchID] ,
ISNULL(BookedMinutes.BookingCount, 0) AS BookingCount
FROM OpenDatesAndMinutes
LEFT OUTER JOIN BookedMinutes ON OpenDatesAndMinutes.BranchID = BookedMinutes.BranchID
AND OpenDatesAndMinutes.[Date] = BookedMinutes.[Date]
AND OpenDatesAndMinutes.[Time] = BookedMinutes.[Time]
As you can imagine, joining on the branch, date & time with 13,000,000 rows all stored in CTE tables takes AGES - running it for a week isnt too bad, about 10 seconds but if we run it for 6 months (13,000,000 minutes) bloats to 25 minutes+
Once we have joined the open minutes to the booked minutes we then group the data on islands and present to the user:
CrossTabPrep ( [Date], [Time], [BranchID], [BookingCount], [Grp] )
AS ( SELECT [Date] ,
[Time] ,
[BranchID] ,
[BookingCount] ,
DATEPART(HOUR, Time) * 60 + DATEPART(MINUTE, Time) - ROW_NUMBER() OVER ( PARTITION BY [BranchID], Date, [BookingCount] ORDER BY Time ) AS [Grp]
FROM PreRender
),
FinalRender ( [BranchID], [Date], [Start Time], [End Time], [Duration], [EntryCount], [EntryColour] )
AS ( SELECT [BranchID] ,
[Date] ,
MIN([Time]) AS [Start Time] ,
MAX([Time]) AS [End Time] ,
ISNULL(DATEDIFF(MINUTE, MIN([Time]), MAX([Time])), 0) AS Duration ,
[BookingCount] AS EntryCount ,
CASE WHEN [BookingCount] = 0 THEN 'Red'
WHEN [BookingCount] = 1 THEN 'Green'
ELSE 'Yellow'
END AS EntryColour
FROM CrossTabPrep
GROUP BY [BranchID] ,
[Date] ,
[BookingCount] ,
[Grp]
)
Quite simply, is my method efficient? is there any way i can improve on this method whilst retaining minute level accuracy? When dealing with massive CTE tables such as this, would there be any benefit in dumping this data to indexed temp tables & joining them instead?
Another thing I was considering is replacing the DATE & TIME(0) data types that the big join uses, would is be more efficient if I cast these to integers?
Here is the Full CTE in case that helps:
WITH OpeningHours ( [BranchID], [DayOfWeek], [StartTime], [EndTime] )
AS ( SELECT BranchID ,
DayOfWeek ,
CONVERT(TIME(0), AM_open) ,
CONVERT(TIME(0), AM_close)
FROM db_BranchDetails.dbo.tbl_ShopOpeningTimes (NOLOCK)
INNER JOIN #tbl_Days Filter_Days ON db_BranchDetails.dbo.tbl_ShopOpeningTimes.DayOfWeek = Filter_Days.DayNumber
WHERE CONVERT(TIME(0), AM_open) <> CONVERT(TIME(0), '00:00:00')
UNION ALL
SELECT BranchID ,
DayOfWeek ,
CONVERT(TIME(0), PM_open) ,
CONVERT(TIME(0), PM_close)
FROM db_BranchDetails.dbo.tbl_ShopOpeningTimes (NOLOCK)
INNER JOIN #tbl_Days Filter_Days ON db_BranchDetails.dbo.tbl_ShopOpeningTimes.DayOfWeek = Filter_Days.DayNumber
WHERE CONVERT(TIME(0), PM_open) <> CONVERT(TIME(0), '00:00:00')
UNION ALL
SELECT BranchID ,
DayOfWeek ,
CONVERT(TIME(0), EVE_open) ,
CONVERT(TIME(0), EVE_close)
FROM db_BranchDetails.dbo.tbl_ShopOpeningTimes (NOLOCK)
INNER JOIN #tbl_Days Filter_Days ON db_BranchDetails.dbo.tbl_ShopOpeningTimes.DayOfWeek = Filter_Days.DayNumber
WHERE CONVERT(TIME(0), EVE_open) <> CONVERT(TIME(0), '00:00:00')
),
DateRange ( [Date], [DayOfWeek] )
AS ( SELECT CONVERT(DATE, DATEADD(DAY, N - 1, #StartDate)) ,
DATEPART(WEEKDAY, DATEADD(DAY, N - 1, #StartDate))
FROM tally (NOLOCK)
WHERE N <= DATEDIFF(DAY, #StartDate, #EndDate) + 1
),
OpenMinutes ( [BranchID], [DayOfWeek], [Time] )
AS ( SELECT [BranchID] ,
[DayOfWeek] ,
DATEADD(MINUTE, N - 1, StartTime)
FROM OpeningHours
LEFT OUTER JOIN tally ON tally.N BETWEEN 0
AND DATEDIFF(MINUTE, OpeningHours.StartTime, OpeningHours.EndTime) + 1
),
OpenDatesAndMinutes ( [Date], [Time], [BranchID] )
AS ( SELECT DateRange.[Date] ,
OpenMinutes.[Time] ,
OpenMinutes.BranchID
FROM DateRange
LEFT OUTER JOIN OpenMinutes ON DateRange.DayOfWeek = OpenMinutes.DayOfWeek
WHERE OpenMinutes.BranchID IS NOT NULL
),
WhiteListEmployees ( [DET_NUMBERA] )
AS ( SELECT DET_NUMBERA
FROM [dbo].[tbl_ChrisCache_WhiteList]
WHERE [TimeSheetV2_SecurityContext] = #TimeSheetV2_SecurityContext
),
BookedMinutesByRole ( [Date], [Time], [BranchID], BookingCount )
AS ( SELECT [BookingDate] ,
DATEADD(MINUTE, N - 1, StartTime) ,
BranchID ,
COUNT(BookingID) AS Bookings
FROM tbl_Booking (NOLOCK)
INNER JOIN tbl_BookingReason (NOLOCK) ON dbo.tbl_BookingReason.ReasonID = dbo.tbl_Booking.ReasonID
INNER JOIN tbl_ChrisCache (NOLOCK) ON dbo.tbl_Booking.DET_NUMBERA = dbo.tbl_ChrisCache.DET_NUMBERA
INNER JOIN #ValidPosCodes AS Filter_PostCodes ON dbo.tbl_ChrisCache.POS_NUMBERA = Filter_PostCodes.POSCODE
LEFT OUTER JOIN tally (NOLOCK) ON tally.N BETWEEN 0
AND DATEDIFF(MINUTE, tbl_Booking.StartTime, tbl_Booking.EndTime) + 1
WHERE ( Void = 0 )
AND tbl_BookingReason.CoverRequired = 0 --#### Only use bookings that dont require cover
AND tbl_booking.BranchID <> '023' --#### Branch 23 will always have messy data
AND ( dbo.tbl_Booking.BookingDate BETWEEN #StartDate
AND #EndDate )
GROUP BY [BookingDate] ,
BranchID ,
DATEADD(MINUTE, N - 1, StartTime)
),
BookedMinutesByWhiteList ( [Date], [Time], [BranchID], BookingCount )
AS ( SELECT [BookingDate] ,
DATEADD(MINUTE, N - 1, StartTime) ,
BranchID ,
COUNT(BookingID) AS Bookings
FROM tbl_Booking(NOLOCK)
INNER JOIN tbl_BookingReason (NOLOCK) ON dbo.tbl_BookingReason.ReasonID = dbo.tbl_Booking.ReasonID
INNER JOIN tbl_ChrisCache (NOLOCK) ON dbo.tbl_Booking.DET_NUMBERA = dbo.tbl_ChrisCache.DET_NUMBERA
INNER JOIN WhiteListEmployees Filter_WhiteList ON dbo.tbl_Booking.DET_NUMBERA = Filter_WhiteList.DET_NUMBERA
LEFT OUTER JOIN tally (NOLOCK) ON tally.N BETWEEN 0
AND DATEDIFF(MINUTE, tbl_Booking.StartTime, tbl_Booking.EndTime) + 1
WHERE ( Void = 0 )
AND tbl_BookingReason.CoverRequired = 0 --#### Only use bookings that dont require cover
AND tbl_booking.BranchID <> '023' --#### Branch 23 will always have messy data
AND ( dbo.tbl_Booking.BookingDate BETWEEN #StartDate
AND #EndDate )
GROUP BY [BookingDate] ,
BranchID ,
DATEADD(MINUTE, N - 1, StartTime)
),
BookedMinutes ( [Date], [Time], [BranchID], BookingCount )
AS ( SELECT [Date] ,
[Time] ,
[BranchID] ,
BookingCount
FROM BookedMinutesByRole
UNION
SELECT [Date] ,
[Time] ,
[BranchID] ,
BookingCount
FROM BookedMinutesByWhiteList
),
PreRender ( [Date], [Time], [BranchID], [BookingCount] )
AS ( SELECT OpenDatesAndMinutes.[Date] ,
OpenDatesAndMinutes.[Time] ,
OpenDatesAndMinutes.[BranchID] ,
ISNULL(BookedMinutes.BookingCount, 0) AS BookingCount
FROM OpenDatesAndMinutes
LEFT OUTER JOIN BookedMinutes ON OpenDatesAndMinutes.BranchID = BookedMinutes.BranchID
AND OpenDatesAndMinutes.[Date] = BookedMinutes.[Date]
AND OpenDatesAndMinutes.[Time] = BookedMinutes.[Time]
),
CrossTabPrep ( [Date], [Time], [BranchID], [BookingCount], [Grp] )
AS ( SELECT [Date] ,
[Time] ,
[BranchID] ,
[BookingCount] ,
DATEPART(HOUR, Time) * 60 + DATEPART(MINUTE, Time) - ROW_NUMBER() OVER ( PARTITION BY [BranchID], Date, [BookingCount] ORDER BY Time ) AS [Grp]
FROM PreRender
),
DeletedBranches ( [BranchID] )
AS ( SELECT [ShopNo]
FROM [dbo].[vw_BranchList]
WHERE [Branch_Deleted] = 1
),
FinalRender ( [BranchID], [Date], [Start Time], [End Time], [Duration], [EntryCount], [EntryColour] )
AS ( SELECT [BranchID] ,
[Date] ,
MIN([Time]) AS [Start Time] ,
MAX([Time]) AS [End Time] ,
ISNULL(DATEDIFF(MINUTE, MIN([Time]), MAX([Time])), 0) AS Duration ,
--dbo.format_timeV2(ISNULL(DATEDIFF(SECOND, MIN([Time]), MAX([Time])), 0)) AS DurationF ,
[BookingCount] AS EntryCount ,
CASE WHEN [BookingCount] = 0 THEN 'Red'
WHEN [BookingCount] = 1 THEN 'Green'
ELSE 'Yellow'
END AS EntryColour
FROM CrossTabPrep
GROUP BY [BranchID] ,
[Date] ,
[BookingCount] ,
[Grp]
)
SELECT [BranchID] ,
CONVERT(VARCHAR(10), DATEADD(DAY, 7, CONVERT(DATETIME, CONVERT(VARCHAR(10), DATEADD(day, -1 - ( DATEPART(dw, [Date]) + ##DATEFIRST - 2 ) % 7, [Date]), 103) + ' 23:59:59', 103)), 103) AS WeekEnding ,
[Date] ,
[Start Time] ,
[End Time] ,
[Duration] ,
CONVERT(VARCHAR, ( [Duration] * 60 ) / 3600) + 'h ' + CONVERT(VARCHAR, ROUND(( ( CONVERT(FLOAT, ( ( [Duration] * 60 ) % 3600 )) ) / 3600 ) * 60, 0)) + 'm' AS [DurationF] ,
[EntryCount] ,
[EntryColour] ,
CASE WHEN [EntryCount] = 0 THEN 'Red'
WHEN [EntryCount] >= 1 THEN 'Green'
END AS DurationColour ,
CASE WHEN [EntryCount] = 0 THEN 'This period of open-time isnt covered'
WHEN [EntryCount] >= 1 THEN 'This period of open-time is covered by ' + CONVERT(VARCHAR, [EntryCount]) + ' booking(s)'
END AS [DurationComment]
FROM FinalRender
WHERE FinalRender.BranchID NOT IN ( SELECT [BranchID]
FROM DeletedBranches )
It's funny, because you have answered your own question with your questions at the end. You should just try them all but to summarize:
Materialize CTEs for better performance. You never know when SQL Server will evaluate a CTE more than once
You can build indexex against temporary tables.
I'm not sure how you jumped from [DayOfWeek],DATEADD(MINUTE, N - 1, StartTime) to the join on [Date],[Time] on the other, but having two columns here doesn't make sense. Use either a single datetime or a bigint representing the seconds from an epoch. UnixTimestamp works well here.
My proposal is not based on your data, but on generated test data, so it can be not fully applicable.
Proposal: In order to move from quadratic degradation of performance to at least linear, batch processing can be used, if data is distributed equally among batch periods.
In example below 2 years of bookings is being processed with 3 day batch interval and it takes it 2 minutes and 30 seconds to get back free periods per day per branch.
Test run results:
2 years - 2 minutes and 30 seconds
4 years - 4 minutes and 55 seconds.
6 years - 6 minutes and 41 seconds
It incorporates the same logic that is being used in question by using numbers to find non-matching minutes.
Schema and test data creation:
IF OBJECT_ID('vwRandomNumber') IS NOT NULL
DROP VIEW vwRandomNumber
GO
IF OBJECT_ID('dbo.fnRandNumber') IS NOT NULL
DROP FUNCTION dbo.fnRandNumber
GO
IF OBJECT_ID('dbo.fnRandomInt') IS NOT NULL
DROP FUNCTION dbo.fnRandomInt
GO
IF OBJECT_ID('tblNumbers') IS NOT NULL
DROP TABLE dbo.tblNumbers
GO
IF OBJECT_ID('Branches') IS NOT NULL
DROP TABLE Branches
GO
IF OBJECT_ID('OpeningHours') IS NOT NULL
DROP TABLE OpeningHours
GO
IF OBJECT_ID('Bookings') IS NOT NULL
DROP TABLE Bookings
GO
CREATE VIEW vwRandomNumber
AS
SELECT Rand() RandomNumber;
GO
CREATE FUNCTION dbo.fnRandNumber()
RETURNS FLOAT
AS
BEGIN
RETURN (SELECT TOP 1 RandomNumber FROM vwRandomNumber)
END;
GO
CREATE FUNCTION dbo.fnRandomInt(#FromNumber INT, #ToNumber INT)
RETURNS INT
AS
BEGIN
RETURN (#FromNumber + ROUND(dbo.fnRandNumber()*(#ToNumber - #FromNumber),0))
END;
GO
CREATE TABLE tblNumbers
(
NumberID INT PRIMARY KEY
)
CREATE TABLE Branches
(
BranchID INT
,BranchName NVARCHAR(100)
);
GO
;WITH cteNumbers AS (
SELECT 1 N
UNION ALL
SELECT N+1 FROM cteNumbers WHERE N<100
)
INSERT INTO
Branches
SELECT N, CAST(NEWID() AS NVARCHAR(100)) FROM cteNumbers
OPTION(MAXRECURSION 0)
CREATE TABLE OpeningHours
(
BranchID INT
, Date DATETIME
, OpenFrom DATETIME
, OpenTo DATETIME
);
GO
CREATE CLUSTERED INDEX CIX_OpeningHours
ON OpeningHours ([Date], [BranchID])
GO
CREATE TABLE Bookings
(
BranchID INT
, BookingDate DATETIME
, BookingFrom DATETIME
, BookingTo DATETIME
)
CREATE CLUSTERED INDEX CIX_Bookings
ON Bookings ([BookingDate],[BranchID])
DECLARE #StartDate DATETIME = DATEADD(month,0,DATEADD(D,0,DATEDIFF(d,0,GETDATE())))
;WITH cteNumbers AS (
SELECT 1 N
UNION ALL
SELECT N+1 FROM cteNumbers WHERE N<2000
)
INSERT INTO
OpeningHours
(
BranchID
, Date
, OpenFrom
, OpenTo
)
SELECT
Branches.BranchID
, Dates.Day
, DATEADD(hour,7,Dates.Day)
, DATEADD(hour,19,Dates.Day)
FROM
(
SELECT
DATEADD(d,N,#StartDate) Day
FROM
cteNumbers
) Dates
CROSS JOIN
Branches
OPTION(MAXRECURSION 0);
INSERT INTO Bookings
SELECT
OpeningHours.BranchID
,OpeningHours.Date
,BookingHours.StartDate
,BookingHours.ToDate
FROM
OpeningHours
CROSS APPLY
(
SELECT DATEADD(hour, dbo.fnRandomInt(0,3), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(4,9), OpeningHours.OpenFrom) ToDate UNION ALL
SELECT DATEADD(hour, dbo.fnRandomInt(1,5), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(6,9), OpeningHours.OpenFrom) UNION ALL
SELECT DATEADD(hour, dbo.fnRandomInt(2,5), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(5,8), OpeningHours.OpenFrom) TODate UNION ALL
SELECT DATEADD(hour, dbo.fnRandomInt(0,3), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(4,9), OpeningHours.OpenFrom) ToDate UNION ALL
SELECT DATEADD(hour, dbo.fnRandomInt(1,5), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(6,9), OpeningHours.OpenFrom) UNION ALL
SELECT DATEADD(hour, dbo.fnRandomInt(2,5), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(5,8), OpeningHours.OpenFrom) TODate UNION ALL
SELECT DATEADD(hour, dbo.fnRandomInt(0,3), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(4,9), OpeningHours.OpenFrom) ToDate UNION ALL
SELECT DATEADD(hour, dbo.fnRandomInt(1,5), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(6,9), OpeningHours.OpenFrom) UNION ALL
SELECT DATEADD(hour, dbo.fnRandomInt(2,5), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(5,8), OpeningHours.OpenFrom) TODate UNION ALL
SELECT DATEADD(hour, dbo.fnRandomInt(0,3), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(4,9), OpeningHours.OpenFrom) ToDate UNION ALL
SELECT DATEADD(hour, dbo.fnRandomInt(1,5), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(6,9), OpeningHours.OpenFrom) UNION ALL
SELECT DATEADD(hour, dbo.fnRandomInt(2,5), OpeningHours.OpenFrom) StartDate
,DATEADD(hour, dbo.fnRandomInt(5,8), OpeningHours.OpenFrom) TODate
) BookingHours;
;WITH cteNumbers AS (
SELECT 1 N
UNION ALL
SELECT N+1 FROM cteNumbers WHERE N<5000
)
INSERT INTO
tblNumbers
SELECT N FROM cteNumbers
OPTION(MAXRECURSION 0)
--SELECT COUNT(*) FROM Bookings WHERE
Scripts to get periods with no bookings:
SET NOCOUNT ON
IF OBJECT_ID('tblBranchFreePeriods') IS NOT NULL
DROP TABLE tblBranchFreePeriods
IF OBJECT_ID('tblFreeMinutes') IS NOT NULL
DROP TABLE tblFreeMinutes
CREATE TABLE tblBranchFreePeriods
(
BranchID INT
, Date DATETIME
, PeriodStartDate DATETIME
, PeriodEndDate DATETIME
)
CREATE TABLE tblFreeMinutes
(
BranchID INT
,Date DATETIME
,FreeMinute INT
)
IF OBJECT_ID('dbo.tblStartDates') IS NOT NULL
DROP TABLE tblStartDates
CREATE TABLE tblStartDates
(
BranchID INT
, Date DATETIME
, PeriodStartDate DATETIME
)
CREATE CLUSTERED INDEX CIX_tblStartDates
ON tblStartDates([BranchID],[Date])
IF OBJECT_ID('dbo.tblEndDates') IS NOT NULL
DROP TABLE tblEndDates
CREATE TABLE tblEndDates
(
BranchID INT
, Date DATETIME
, PeriodEndDate DATETIME
)
CREATE CLUSTERED INDEX CIX_tblEndDate
ON tblEndDates ([BranchID],[Date])
CREATE CLUSTERED INDEX CIX_tblFreeMinutes
ON tblFreeMinutes ([BranchID],[Date],FreeMinute)
DECLARE #ProcessFromDate DATETIME, #ProcessTo DATETIME
SELECT #ProcessFromDate = MIN(OpenFrom), #ProcessTo = DATEADD(year,2,#ProcessFromDate) FROM OpeningHours
DECLARE #BatchSize INT = 3
DECLARE #StartTime DATETIME = GETDATE()
WHILE (#ProcessFromDate <= #ProcessTo) BEGIN
TRUNCATE TABLE tblFreeMinutes
TRUNCATE TABLE tblStartDates
TRUNCATE TABLE tblEndDates
SET #StartTime = GETDATE()
DECLARE #DateFrom DATETIME = #ProcessFromDate, #DateTo DATETIME = DATEADD(d,#BatchSize,#ProcessFromDate)
PRINT 'Date From ' + CAST(#DateFrom AS NVARCHAR(50))
PRINT 'Date To ' + CAST(#DateTO AS NVARCHAR(50))
INSERT INTO
tblFreeMinutes
SELECT
OpeningHours.BranchID
,OpeningHours.Date
,tblOpeningHourMinutes.NumberID Minute
FROM
OpeningHours
INNER JOIN
tblNumbers tblOpeningHourMinutes
ON
NumberID
BETWEEN DATEDIFF(minute,OpeningHours.Date,OpeningHours.OpenFrom)
AND
DATEDIFF(minute,OpeningHours.Date,OpeningHours.OpenTo)
LEFT OUTER JOIN
Bookings
ON
Bookings.BookingDate = OpeningHours.Date
AND
Bookings.BranchID = OpeningHours.BranchID
AND
tblOpeningHourMinutes.NumberID
BETWEEN
DATEDIFF(minute,Bookings.BookingDate,Bookings.BookingFrom)
AND
DATEDIFF(minute,Bookings.BookingDAte,Bookings.BookingTo)
WHERE
OpeningHours.Date BETWEEN #DateFrom AND #DateTo
AND
Bookings.BookingDate IS NULL
OPTION ( FORCE ORDER )
PRINT 'Populate free minutes ' + CAST(DATEDIFF(millisecond,#StartTime,GETDATE()) AS NVARCHAR(50))
SET #StartTime = GETDATE()
INSERT INTO
tblStartDates
SELECT
tblFreeMinutes.BranchID
, tblFreeMinutes.Date
, DATEADD(minute,tblFreeMInutes.FreeMinute,tblFreeMinutes.Date)
FROM
tblFreeMinutes
LEFT OUTER JOIN
tblFreeMinutes tblFreeMinutesIn
ON
tblFreeMinutesIn.Date = tblFreeMinutes.Date
AND
tblFreeMinutesIn.BranchID = tblFreeMinutes.BranchID
AND
tblFreeMinutesIn.FreeMinute = tblFreeMinutes.FreeMinute-1
WHERE
tblFreeMinutesIn.BranchID IS NULL
PRINT 'Populate start dates ' + CAST(DATEDIFF(millisecond,#StartTime,GETDATE()) AS NVARCHAR(50))
SET #StartTime = GETDATE()
INSERT INTO
tblEndDates
SELECT
tblFreeMinutes.BranchID
, tblFreeMinutes.Date
, DATEADD(minute,tblFreeMInutes.FreeMinute,tblFreeMinutes.Date)
FROM
tblFreeMinutes
LEFT OUTER JOIN
tblFreeMinutes tblFreeMinutesIn
ON
tblFreeMinutesIn.Date = tblFreeMinutes.Date
AND
tblFreeMinutesIn.BranchID = tblFreeMinutes.BranchID
AND
tblFreeMinutesIn.FreeMinute = tblFreeMinutes.FreeMinute+1
WHERE
tblFreeMinutesIn.BranchID IS NULL
PRINT 'Populate end dates ' + CAST(DATEDIFF(millisecond,#StartTime,GETDATE()) AS NVARCHAR(50))
SET #StartTime = GETDATE()
INSERT INTO
tblBranchFreePeriods
SELECT
tblStartDates.BranchID
, tblStartDates.Date
, tblStartDates.PeriodStartDate
, tblEndDate.PeriodEndDate
FROM
tblStartDates
CROSS APPLY
(
SELECT TOP 1
*
FROM
tblEndDates
WHERE
tblEndDates.BranchID = tblStartDates.BranchID
AND
tblEndDates.Date = tblStartDates.Date
AND
tblEndDates.PeriodEndDate > tblStartDates.PeriodStartDate
ORDER BY
PeriodEndDate ASC
) tblEndDate
PRINT 'Return intervals ' + CAST(DATEDIFF(millisecond,#StartTime,GETDATE()) AS NVARCHAR(50))
SET #StartTime = GETDATE()
SET #ProcessFromDate = DATEADD(d,#BatchSize+1,#ProcessFromDate)
PRINT ''
PRINT ''
RAISERROR ('',0,0) WITH NOWAIT
--SELECT * FROM tblBranchFreePeriods
--BREAK
END
SELECT
*
FROM
tblBranchFreePeriods
ORDER BY
1,2,3