Query based on day-of-week/time range - sql

I'm on SQL Server 2014. I have a table with columns like this:
id (int, PK, identity)
effectiveDate (datetime)
expirationDate (datetime)
1
2022-07-06 18:00:00.000
2022-07-06 23:00:00.000
2
2022-07-08 22:00:00.000
2022-07-09 02:00:00.000
I need to select rows where the current date/time (GETDATE()) lands within the day-of-week/time range represented by these datetimes, beginning on the effectiveDate. So think of row 1 as the range Wednesday 18:00 -> Wednesday 23:00 and row 2 as Friday 22:00 -> Saturday 02:00. (Keep in mind the day-of-week/time range can span multiple days, as in the 2nd row.)
Examples:
GETDATE() = 2022-07-06 19:30:00.000 (i.e Wednesday at 19:30)
Selects row 1
GETDATE() = 2022-07-30 01:00:00.000 (i.e. Saturday at 01:00)
Selects row 2
GETDATE() = 2022-06-30 19:00:00.000 (i.e. Wednesday at 19:00 which matches row 1 on the day-of-week/time range, but is before the effective date)
Selects nothing
I'm not quite sure how to attack this. Maybe we could date adjust each row's effectiveDate and expirationDate as many weeks forward as needed to place the effectiveDate before GETDATE() (assuming the effectiveDate <= GETDATE()). Any thoughts?

The DATEPART with weekday is the key to checking the weekday range. There are two cases. For example, Mon to Wed is different than Wed to Mon. Mon to Wed is easy with a test of Mon<=day<= Wed. For Wed to Mon, it becomes day < Mon or day > Wed. However, this is the same as NOT Mon<=day<=Wed. The query might not be exactly what you need, but should be a good start.
with TestData as (
select *
from (
values
(1, CAST('2022-07-06 18:00:00.000' as datetime), CAST('2022-07-06 23:00:00.000' as datetime)),
(2, CAST('2022-07-08 22:00:00.000' as datetime), CAST('2022-07-09 02:00:00.000' as datetime)),
(3, CAST('2022-07-09 22:00:00.000' as datetime), CAST('2022-07-11 10:00:00.000' as datetime))
) t (id, effectiveDate, expirationDate)
), TestSamples as (
select *
from (
values
(CAST('2022-07-06 19:30:00.000' as datetime)),
(CAST('2022-07-30 01:00:00.000' as datetime)),
(CAST('2022-07-31 01:00:00.000' as datetime)),
(CAST('2022-06-30 19:00:00.000' as datetime)),
(CAST('2022-08-04 19:00:00.000' as datetime))
) t (testDate)
), WeekDayRange as (
select id, effectiveDate, expirationDate,
datename(weekday, effectiveDate) as WDNAME1, datename(weekday, expirationDate) as WDNAME2,
datepart(weekday, effectiveDate) as WD1, datepart(weekday, expirationDate) as WD2
from TestData
) select t.testDate, datename(weekday, t.testDate) as [WDNAME], datepart(weekday, t.testDate) as [WD], r.*
from TestSamples t
left join WeekDayRange r
on (r.WD1 <= r.WD2 and datepart(weekday, t.testDate) BETWEEN r.WD1 and r.WD2)
or (r.WD1 > r.WD2 and not datepart(weekday, t.testDate) BETWEEN r.WD2 and r.WD1)
where t.testDate > r.effectiveDate
testDate WDNAME WD id effectiveDate expirationDate WDNAME1 WDNAME2 WD1 WD2
----------------------- --------- ----------- ----------- ----------------------- ----------------------- --------- --------- ----------- -----------
2022-07-06 19:30:00.000 Wednesday 4 1 2022-07-06 18:00:00.000 2022-07-06 23:00:00.000 Wednesday Wednesday 4 4
2022-07-30 01:00:00.000 Saturday 7 2 2022-07-08 22:00:00.000 2022-07-09 02:00:00.000 Friday Saturday 6 7
2022-07-31 01:00:00.000 Sunday 1 3 2022-07-09 22:00:00.000 2022-07-11 10:00:00.000 Saturday Monday 7 2

I think I got this working with a bit of math:
DECLARE #myGetDate DATETIME;
SET #myGetDate = '2022-07-30 01:00:00.000';
WITH AdjustedWeeklyDates AS (
SELECT
ID,
DATEADD(WEEK, DATEDIFF(day, effectiveDate, #myGetDate) / 7, effectiveDate) AS adjustedEffectiveDate,
DATEADD(WEEK, DATEDIFF(day, effectiveDate, #myGetDate) / 7, expirationDate) AS adjustedExpirationDate
FROM
dbo.myTable
WHERE
effectiveDate <= #myGetDate
)
SELECT
mt.ID, mt.effectiveDate, mt.expirationDate, awd.adjustedEffectiveDate, awd.adjustedExpirationDate
FROM dbo.myTable mt
INNER JOIN AdjustedWeeklyDates awd ON mt.ID = awd.ID
WHERE
awd.adjustedEffectiveDate <= #myGetDate
AND awd.adjustedExpirationDate > #myGetDate
To explain:
DATEDIFF(day, effectiveDate, #myGetDate) returns the number of days between the effectiveDate and the current date. So for example, say it was 20 days ago.
/ 7 gets the number of weeks as an int, since the DATEDIFF returns an int. This also results in the floor of the quotient. So, with our example 20 days / 7, the quotient is about 2.86, but this will result in an even 2
DATEADD adds the number of weeks to bring us up to or before the current date/time. We add the same number of weeks to the expiration date, resulting in the same range as the original effective/expiration dates, which may or may not extend around the current date.
Finally the check for effectiveDate <= #myGetDate guarantees the current date is after or equal to the effective date.

Related

Get the date for custom day but current month in sql

I am working on some reporting module, where I need to implement the logic which gets a date as below cases -
My table :-
Id
Day
1
8
2
14
3
22
4
29
Now I have to write a query to get result as below -
Case 1- If current date (GETDATE()) is 2022-9-5 00:00:00.000
result
2022-9-8 00:00:00.000
2022-9-14 00:00:00.000
2022-9-22 00:00:00.000
2022-9-29 00:00:00.000
Case 2- If current date (GETDATE()) is 2022-9-16 00:00:00.000
result
2022-10-8 00:00:00.000
2022-10-14 00:00:00.000
2022-9-22 00:00:00.000
2022-9-29 00:00:00.000
Note : The query should work with any month / year.
select dateadd(day, day, eomonth(getdate(), case when day < datepart(day, getdate()) then 0 else -1 end)) as result
from t
result
2022-10-08 00:00:00.000
2022-10-14 00:00:00.000
2022-09-22 00:00:00.000
2022-09-29 00:00:00.000
Fiddle
Some DATEADD willhep, as you first need to know, the first daty of the next month and then you can add the das from your table
The moth seledcted will be determined if the day of the Selct run is smaller than the day in the table
CREATE TABLE table1
([Id] int, [Day] int)
;
INSERT INTO table1
([Id], [Day])
VALUES
(1, 8),
(2, 14),
(3, 22),
(4, 29)
;
4 rows affected
SELECT getdate()
(No column name)
2022-09-20 18:55:21.917
SELECT
DATEADD(DAY, [Day] -1,DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE())+
(CASE WHEN DAY(GETDATE()) < [Day] THEN 0 ELSE 1 END), 0))
FROM table1
(No column name)
2022-10-08 00:00:00.000
2022-10-14 00:00:00.000
2022-09-22 00:00:00.000
2022-09-29 00:00:00.000
fiddle
An IF ELSE is probably what you need
https://learn.microsoft.com/en-us/sql/t-sql/language-elements/if-else-transact-sql?view=sql-server-ver16
So if the day is greater than the day in the other table, add one month to the date.

Selecting only 'month-day time' from Datetime field in SQL (server 2012)

I'm looking at changing a specific date to a different date in a case statement, however, I only want it to apply to a day, month and time.
For example, I want to get the case statement to change any date which falls on 31/12 # 23:59:00 to 01/01 # 00:00:00 but unless I write the case to include each year for the next 40 years to cover myself, I've not been able to resolve this.
I am writing this from the UK with date format being dd/mm/yyyy (above example is 31st December and 1st January).
The format of the datetime field in the database is 'datetime': 2019-07-01 13:14:47).
I can't tell if you want the return type to be a date or datetime. If a date, you can do:
(case when year(dateadd(minute, 1, datecol)) <> year(datecol)
then datefromparts(year(datecol) + 1, month(datecol), day(datecol))
else cast(datecol as date)
end)
The logic would be similar for a datetime, assuming datecol is already a datetime:
(case when year(dateadd(minute, 1, datecol)) <> year(datecol)
then datefromparts(year(datecol) + 1, month(datecol), day(datecol))
else datecol
end)
If I understand correctly you want to round the dates inside last minute of year into the next year. You can do this:
SELECT datecol, CASE
WHEN MONTH(datecol) = 12 AND DAY(datecol) = 31 AND CAST(datecol AS TIME(3)) >= '23:59:00' THEN CAST(DATEADD(MINUTE, 1, datecol) AS DATE)
ELSE datecol
END
FROM (VALUES
(CAST('2018-12-31 23:58:59.997' AS DATETIME)),
(CAST('2018-12-31 23:59:00.000' AS DATETIME)),
(CAST('2018-12-31 23:59:59.997' AS DATETIME)),
(CAST('2019-01-01 00:00:00.000' AS DATETIME))
) AS v(datecol)
Result:
2018-12-31 23:58:59.997 2018-12-31 23:58:59.997
2018-12-31 23:59:00.000 2019-01-01 00:00:00.000
2018-12-31 23:59:59.997 2019-01-01 00:00:00.000
2019-01-01 00:00:00.000 2019-01-01 00:00:00.000

Find all instances of a specific day of the week between two dates in SQL

I'm trying to find all instances of a day of a week between two given dates. The day of the week can change. I've seen a similar question posted on here, but it doesn't seem to work for variables
SET DATEFIRST 1;
DECLARE #earliestStartDate DATETIME = '2016-08-01 00:00:00.000';
DECLARE #latestStartDate DATETIME = '2016-09-30 00:00:00.000';
DECLARE #weeklyCoursesStartDay INT = 1;
DECLARE #maxCourses INT = 30;
CREATE TABLE #Dates(CourseDate DATETIME);
WITH CTE(dt)
AS
(SELECT #earliestStartDate
UNION ALL
SELECT DATEADD(day, #weeklyCoursesStartDay, dt) FROM CTE--SELECT DATEADD(day, #weeklyCoursesStartDay, dt) FROM CTE
WHERE dt < #latestStartDate)
INSERT INTO #Dates(CourseDate)
SELECT TOP(#maxCourses) dt
FROM CTE
WHERE DATEPART(DW, dt) = #weeklyCoursesStartDay
OPTION (MAXRECURSION 0);
SELECT * FROM #Dates
DROP TABLE #Dates
This returns the below, as expected.
2016-08-01 00:00:00.000
2016-08-08 00:00:00.000
2016-08-15 00:00:00.000
2016-08-22 00:00:00.000
2016-08-29 00:00:00.000
2016-09-05 00:00:00.000
2016-09-12 00:00:00.000
2016-09-19 00:00:00.000
2016-09-26 00:00:00.000
But when the #weeklyCourseStartDay is changed to say, 5 (indicating a friday) it only returns two results:
2016-08-26 00:00:00.000
2016-09-30 00:00:00.000
The line
SELECT DATEADD(day, #weeklyCoursesStartDay, dt) FROM CTE
should actually be:
SELECT DATEADD(day, 1, dt) FROM CTE
This means that the recursive CTE will generate every day between #earliestStartDate and #latestStartDate, and, at a later stage, the WHERE DATEPART(DW, dt) = #weeklyCoursesStartDay clause will make sure only the days with the correct week day will remain.

Get weekStart and weekEnd dynamically?

I have spent so much time trying to figure out how this can be done but could not do it. So please help me out.
I have this data:
ID employee_id worked_date start_time finish_time
1 1 2013-09-25 09:00:00 17:30:00
2 1 2013-09-26 07:00:00 17:00:00
8 1 2013-10-01 09:00:00 17:00:00
9 1 2013-10-04 09:00:00 17:00:00
12 1 2013-10-07 09:00:00 17:00:00
13 1 2013-10-10 09:00:00 17:00:00
14 1 2013-10-11 09:00:00 17:00:00
My first day of the week is Wednesday. The base date is 2013-09-25 which is Wednesday. I need to be able to get the weekStart and weekEnd dynamically. For example, based on the data I have above, there are three weeks between 2013-09-25 to 2013-10-11. I order my data with the latest worked_date first. If the user requested for week3, then the weekStart should be 2013-09-25 and weekEnd should be 2013-10-01. If the user requested for week2, then the weekStart should be 2013-10-02 to 2013-10-08 and so on.
The parameter week will be passed dynamically. Thanks for your help.
Here is the result I want to achieve when the requested week is 3:
ID employee_id worked_date start_time finish_time weekStart weekEnd
1 1 2013-09-25 09:00:00 17:30:00 2013-09-25 2013-10-01
2 1 2013-09-26 07:00:00 17:00:00 2013-09-25 2013-10-01
8 1 2013-10-01 09:00:00 17:00:00 2013-09-25 2013-10-01
Using this query will give me the weekStart and weekEnd for each record
SELECT *, dateadd(week, datediff(day,'20000105',worked_date) / 7, '20000105') AS WeekStart ,
dateadd(week, datediff(day,'20000105',worked_date) / 7, '20000105') + 6 AS WeekEnd
FROM Timesheet
But I do not want this. I want only a particular week that the user requested.
It sounds like you might need a calendar table.
create table calendar (
calendarId int identity(1,1) primary key,
year int,
month int,
week int,
startDate date,
endDate date)
then you could say 'give me all the records for week 2'
declare #week int = 2
declare #year int = 2013
;with employeeCalendar as (
select
employee.employeeid
,startDate
,endDate
from
employee
cross apply
calendar
where
calendar.week = #week )
select
employeeCalendar.EmployeeId
,employeeShift.[date]
,start_time
,finish_time
,startDate
,endDate
from
employeeCalendar
left join
employeeShift
on employeeShift.employeeid = employeeCalendar.employeeid
and employeeShift.worked_date
between employeeCalendar.startDate and employeeCalendar.endDate
You could populate that table with a query like this:
;with calendarCte as (
select
1 as week
,convert(date,'2013-09-25') as startDate
,convert(date,'2013-10-01') as endDate
union all
select
week + 1
,dateadd(week,1,startDate)
,dateadd(week,1,endDate)
from
calendarCte
where
calendarCte.startDate < convert(date,'2043-09-25') )
insert into calendar( [year],[month],[week],startdate,stopdate)
select
datepart(year,startDate) as [year]
,datepart(month,startDate) as [month]
,week as [week]
,startDate
,endDate
from calendarCte option (maxrecursion 0)
You said you are looking for a stored procedure. Are you looking for something like this?
CREATE PROCEDURE ProcedureName #week int AS
SELECT * FROM Timesheet
WHERE worked_date >= dateadd(week, #week, '2013-09-25')
AND worked_date < dateadd(day,7,dateadd(week, #week, '2013-09-25'))
If you want your weekStart and weekEnd you could add them in to the SELECT as you did before.
If you want this in descending order from the current week backwards you could write it as follows.
CREATE PROCEDURE ProcedureName #week int AS
SELECT * FROM Timesheet
WHERE worked_date >= dateadd(week, -#week, '2013-10-16')
AND worked_date < dateadd(day,7,dateadd(week, -#week, '2013-10-16'))
You could also parameterize the Date you are passing into the stored procedure as the current week will always be changing.

Need Sql query output like this format

I have one time sheet table when i fetch the records it will display like this for one week.
This record is from one week from June 10 to June 16
EmpId Monday Tuesday Wednesday Thursday Friday Saturday Sunday StartDate EndDate
1 08:00 08:12 00:00 04:00 00:00 03:00 00:00 05/10/2013 05/16/2013
Need the output like this
Empid Monday startdate EndDate
1 08:00 05/10/2013 05/10/2013
1 08:12 05/11/2013 05/11/2013
1 04:00 05/13/2013 05/13/2013
1 03:00 05/15/2013 05/15/2013
This is basically an unpivot query. Because of the time fields, this version chooses to do it explicitly (using cross join and case) rather than using unpivot:
select t.*
from (select h.empid,
(case when n = 0 then Monday
when n = 1 then Tuesday
when n = 2 then Wednesday
when n = 3 then Thursday
when n = 4 then Friday
when n = 5 then Saturday
when n = 6 then Sunday
end) as hours,
(startdate + n) as StartDate,
(startdate + n) as EndDate
from hours h join
(select 0 as n union all
select 1 union all
select 2 union all
select 3 union all
select 4 union all
select 5 union all
select 6
) n
) t
where hours > 0;
You can see the SQLFiddle here. And there is no problem running this on larger amounts of data.
This should get you started:
declare #Hours as Table ( EmpId Int, Monday Time, Tuesday Time, Wednesday Time,
Thursday Time, Friday Time, Saturday Time, Sunday Time, StartDate Date, EndDate Date );
insert into #Hours
( EmpId, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday, StartDate, EndDate )
values
( 1, '08:00', '08:12', '00:00', '04:00', '00:00', '03:00', '00:00', '20130510', '20130516' );
select * from #Hours;
with Week as (
-- Build a table of all of the dates in the work week.
select StartDate as WorkDate, EndDate
from #Hours
union all
select DateAdd( day, 1, WorkDate ), EndDate
from Week
where WorkDate < EndDate )
-- Output the result.
select EmpId,
case DatePart( weekday, W.WorkDate )
when 1 then H.Monday
when 2 then H.Tuesday
when 3 then H.Wednesday
when 4 then H.Thursday
when 5 then H.Friday
when 6 then H.Saturday
when 7 then H.Sunday
end as Hours,
WorkDate as StartDate, WorkDate as EndDate,
DatePart( weekday, W.WorkDate ) as DayOfWeek
from Week as W inner join
#Hours as H on H.StartDate <= W.WorkDate and W.WorkDate <= H.EndDate;
with Week as (
-- Build a table of all of the dates in the work week.
select StartDate as WorkDate, EndDate
from #Hours
union all
select DateAdd( day, 1, WorkDate ), EndDate
from Week
where WorkDate < EndDate ),
DaysHours as (
-- Build a table of the hours assigned to each date.
select EmpId,
case DatePart( weekday, W.WorkDate )
when 1 then H.Monday
when 2 then H.Tuesday
when 3 then H.Wednesday
when 4 then H.Thursday
when 5 then H.Friday
when 6 then H.Saturday
when 7 then H.Sunday
end as Hours,
WorkDate as StartDate, WorkDate as EndDate
from Week as W inner join
#Hours as H on H.StartDate <= W.WorkDate and W.WorkDate <= H.EndDate )
-- Output the non-zero hours.
select EmpId, Hours, StartDate, EndDate
from DaysHours
where Hours <> Cast( '00:00' as Time );
This works for a single row, but you will need to make some changes if your dataset grows.