Calculate working days - Monday to Fri in Tsql - tsql

How can i calculate the last working five days which is monday to Friday. my current script gets the last monday's date, but i cannot get the last friday's date. Please help
declare #StartDate datetime
declare #EndDate datetime
--Calculate date range for report
select #EndDate = Cast(convert(char(10), getdate(), 101)+' 00:00:00' as datetime)
select #StartDate = DateAdd(d, -7, #EndDate)
select #EndDate = Cast(convert(char(10), getdate(), 101)+' 23:59:59' as datetime)
select #StartDate startdate
select #EndDate enddate

Datepart offers you an easy way to get the week day:
SET DATEFIRST 1 -- monday is first day of the week
SELECT DATEPART(weekday, '20110725')
-- result is 1
See T-SQL Date functions and SET DATEFIRST for more information.
Using the weekday, you can work out how many days ago last monday and friday are and use 'AddDate' (like you are doing now) to calculate those.
Note that you should really use DATEDIFF for this type of date range selection. If you select everything up to 23:59:59 there's always a chance that some records are left out. For example 23:59:59.001 is out of range but it's still on the same day. With DATEDIFF you can test whether it's on the same day, discarding the time part. No need to bother with casting to string, adding time and casting back.

The answer is more complex than people are assuming. What you need is to go 5 days back and find the first monday before that, and the first friday after that. You can eather use ##datefirst, a calculation or 'set firstdate 1' for that. I prefer not using the last one, because it can't be done in functions. As you can see i used the calculation, ##datefirst is just as good.
Assuming you want the last group of monday to friday that is in the past. This query will get that. If you trust your current monday, you can just add 5 days and subtract 1 minute (I wouldn't trust it, it only returns last monday if you run the query on a monday).
In my sql, I am not aiming for simplicity, I am aiming for effectivity.
DECLARE #getdate datetime = dateadd(day, cast(getdate() as int), 0)
-- the 'Declare' can also be written like this thanks to #Andriy M
--DECLARE #getdate = CAST(GETDATE() - 0.5 AS int)
SELECT #getdate - 5 - CAST(#getdate- 5 as int) % 7 monday,
dateadd(minute, -1, #getdate) - CAST(#getdate- 5 as int) % 7 friday
Result:
Monday Friday
2011-07-18 0:00:00 2011-07-22 23:59:00
*first solution was a day off #AndriyM pointed it out, it has been solved.
Answer to #Andriy M
For some reason it acted different than I expected. I can't explain it but try this
select cast(dateadd(day, cast(getdate() as int) - .5, 0) as datetime),
cast(dateadd(day, cast(getdate() as int), 0) as datetime),
cast(dateadd(day, cast(getdate() as int) + .5, 0) as datetime)
in the morning the last 2 fields has same value, in the evening the first 2 fields has the same value. I am as surprised as you are, I wish I could explain it. It was tested here
https://data.stackexchange.com/stackoverflow/query/new

These two questions:
Find last sunday
How to get last day of last week in sql?
might be of some help.
Although, if you already know how to find the last Monday, you can easily find the corresponding Friday by adding 4 days to the Monday date using the DATEADD() function. For example:
SELECT #EndDate = DATEADD(DAY, 4, #StartDate)

It's all relative to TODAY's date, right?
So find out what today is DATEPART(weekday, getdate())
And then turn that into an adjustment variable -- e.g.:
declare #adjustment int
set #adjustment = CASE DATEPART(weekday, getdate()) WHEN 'MONDAY' THEN 0 WHEN 'TUESDAY' THEN 1, ...END
So then the Monday you want would be today - 7 - #adjustment
...and the Friday you want would be Monday + 5
Declare #myMonday smalldatetime,
#myFriday smalldatetime
set #myMonday = getdate() - 7 - #adjustment
set #myFriday = #myMonday + 5

DECLARE #my int
DECLARE #myDeduct int
DECLARE #day INT
DECLARE #mydate DATETIME
SET #mydate = '2012-08-01'
SET #myDeduct = 0
SET DateFirst 1 -- Set it monday=1 (value)
--Saturday and Sunday on the first and last day of a month will Deduct 1
IF (DATEPART(weekday,(DATEADD(dd,-(DAY(#mydate)-1),#mydate))) > 5)
SET #myDeduct = #myDeduct + 1
IF (DATEPART(weekday,(DATEADD(dd,-(DAY(DATEADD(mm,1,#mydate))),DATEADD(mm,1,#mydate)))) > 5)
SET #myDeduct = #myDeduct + 1
SET #my = day(DATEADD(dd,-(DAY(DATEADD(mm,1,#mydate))),DATEADD(mm,1,#mydate)))
select (((#my/7) * 5 + (#my%7)) - #myDeduct) as Working_Day_per_month

I'll throw my hat in the ring too. :-)
DECLARE #Date datetime = '01/11/2015'
DECLARE #StartDate datetime = DATEADD(d, (1 - (DATEDIFF(d, CAST('1899.12.31' AS datetime), #Date - 6) % 7)), #Date - 6) -- MONDAY
DECLARE #EndDate datetime = DATEADD(d, (5 - (DATEDIFF(d, CAST('1899.12.31' AS datetime), #Date - 6) % 7)), #Date - 6) -- FRIDAY
SELECT '#Date' as Variable ,CONVERT(date, #Date) as DateValue ,DATENAME(dw, #Date) as DayOfTheWeek
UNION SELECT '#StartDate' as Variable ,CONVERT(date, #StartDate) as DateValue ,DATENAME(dw, #StartDate) as DayOfTheWeek
UNION SELECT '#EndDate' as Variable ,CONVERT(date, #EndDate) as DateValue ,DATENAME(dw, #EndDate) as DayOfTheWeek
-- Variable DateValue DayOfTheWeek
-- ---------- ---------- ------------
-- #Date 2015-01-11 Sunday
-- #StartDate 2015-01-05 Monday
-- #EndDate 2015-01-09 Friday
BONUS: Here you can generate a quick table of the 5 weekdays using the same technique.
SELECT DATENAME(dw, DATEADD(d, TT.DaysToAdd, DATEADD(d, (1 - (DATEDIFF(d, CAST('1899.12.31' AS datetime), #Date - 6) % 7)), #Date - 6))) as DayOfTheWeek
, DATEADD(d, TT.DaysToAdd, DATEADD(d, (1 - (DATEDIFF(d, CAST('1899.12.31' AS datetime), #Date - 6) % 7)), #Date - 6)) as DateValue
FROM (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 as DaysToAdd FROM (VALUES(0),(0),(0),(0),(0)) a(n)) as TT
-- DayOfTheWeek DateValue
-- ------------------------------ -----------------------
-- Monday 2015-01-05 00:00:00.000
-- Tuesday 2015-01-06 00:00:00.000
-- Wednesday 2015-01-07 00:00:00.000
-- Thursday 2015-01-08 00:00:00.000
-- Friday 2015-01-09 00:00:00.000
Here's an explanation:
1) First we need to know what date to begin our evaluation from. For this example, we chose to use Sunday, January 11th, 2015.
DECLARE #Date2 datetime = '01/11/2015'
1b) Here's a nice to have bonus technique of getting the name for the day of the week, given a date value
SELECT #Date2 as DateValue, DATENAME(dw, #Date2) as DayOfTheWeek
2) Next we need to know deterministically (based on the US calendar) what the given day of the week is numerically between 1 and 7
NOTE: 1899.12.31 is the first Sunday before 1900.01.01, which is the MINIMUM value for the SmallDateTime data type.
NOTE: Yes, you could use DATEPART(dw, #Date) like this more simply, but it is not deterministic given that certain server environments could have different configurations
RESULTS: 1 = Sunday | 2 = Monday | 3 = Tuesday | 4 = Wednesday | 5 = Thursday | 6 = Friday | 7 = Saturday
SELECT ((DATEDIFF(d, CAST('1899.12.31' AS datetime), #Date2) % 7) + 1) [DayOfWeek Deterministic (Based on US)]
3) Now, given any date, you should have a deterministic way of determining the Monday for that given week
SELECT DATEADD(d, (1 - (DATEDIFF(d, CAST('1899.12.31' AS datetime), #Date2) % 7)), #Date2) as [Monday Day of the Week - Deterministic (Based on US)]
4) Now, given any date, you should have a deterministic way of determining the Friday for that given week
SELECT DATEADD(d, (5 - (DATEDIFF(d, CAST('1899.12.31' AS datetime), #Date2) % 7)), #Date2) as [Monday Day of the Week - Deterministic (Based on US)]
5) The last date manipulation technique we need is to know how to get into a week that has the first full week of weekdays happening before it. For instance, if we are on a Sunday and we subtract 1 day from it, then we get to Saturday, which puts us in the previous week, which is the first full week of weekdays. Alternatively, if we also subtracted 1 day from Monday, it would only get us to Sunday, which is not the previous week, so subtracting 1 day is not enough. On the flip side, if we were on a Saturday and subtracted 7 days, it would take us past the previous full week of weekday, into the week before it, which is too far. Here's a run down of the analysis to figure out what the magic numbers is that you can subtract by that will work with any day of the week. As you can see below, the magic number is 6.
-- DAYS TO SUBTRACT
-- Day of the Week - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7
-- =============== ==== ==== ==== ==== ==== ==== ==== ====
-- Sunday Bad Good Good Good Good Good Good Good
-- Monday Bad Bad Good Good Good Good Good Good
-- Tuesday Bad Bad Bad Good Good Good Good Good
-- Wednesday Bad Bad Bad Bad Good Good Good Good
-- Thursday Bad Bad Bad Bad Bad Good Good Good
-- Friday Bad Bad Bad Bad Bad Bad Good Good
-- Saturday Good Good Good Good Good Good Good Bad
BONUS) If you want to have all the weekdays in little table, then you would want to also use a quick zero based "tally table". There are many ways to do this, so pick your flavor. Here are few.
SELECT * FROM (SELECT 0 as DaysToAdd UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4) as TT
SELECT * FROM (SELECT TOP 5 ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 as DaysToAdd FROM sys.all_columns a CROSS JOIN sys.all_columns b) as TT
SELECT * FROM (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 as DaysToAdd FROM (VALUES(0),(0),(0),(0),(0)) a(n)) as TT

Related

To get weekday between two date in Postgresql

I need to get all week days in a given time interval.
In postgresql, there are dow and isodow
By mixing them together, may I write a function to retrieve weekdays?
demo:db<>fiddle
SELECT
generated_date,
to_char(generated_date, 'Day'), -- 1
EXTRACT(isodow FROM generated_date), -- 2
EXTRACT(dow FROM generated_date) -- 3
FROM
generate_series('2020-11-01'::date, '2020-11-10'::date, interval '1 day') AS generated_date
Returns the name for the weekday
Returns the number of the weekday (Monday = 1, Sunday = 7)
Returns the number of the weekday (Sunday = 0, Saturday = 6)
Edit:
If you want to get the days without weekend, you can filter by the dow/isodow values, e.g.:
SELECT
generated_date::date
FROM
generate_series('2020-11-01'::date, '2020-11-10'::date, interval '1 day') AS generated_date
WHERE
EXTRACT(isodow FROM generated_date) < 6
As far as I understand you need to extract all Monday..Fridays between two dates. Here is an illustration with 2020-11-30 as the beginning of the interval and 2020-12-12 as the end of it.
select d
from generate_series('2020-11-30'::date, '2020-12-12'::date, '1 day'::interval) t(d)
where extract(isodow from d) between 1 and 5;

postgresql list of time slots from 'Monday' | 09:00:00 | 11:00:00

I’m building a booking system where a user will set their availability eg: I’m available Monday’s from 9am to 11am, Tuesdays from 9am to 5pm etc… and need to generate a list of time slots 15mins apart from their availability.
I have the following table (but am flexible to changing this):
availabilities(day_of_week text, start_time: time, end_time: time)
which returns records like:
‘Monday’ | 09:00:00 | 11:00:00
‘Monday’ | 13:00:00 | 17:00:00
‘Tuesday’ | 08:00:00 | 17:00:00
So I’m trying to build a stored procedure to generate a list of time slots so far I've got this:
create or replace function timeslots ()
return setof timeslots as $$
declare
rec record;
begin
for rec in select * from availabilities loop
/*
convert 'Monday' | 09:00:00 | 11:00:00 into:
2020-02-03 09:00:00
2020-02-03 09:15:00
2020-02-03 09:30:00
2020-02-03 09:45:00
2020-02-03 10:00:00
and so on...
*/
return next
end loop
$$ language plpgsql stable;
I return a setof instead of a table as I'm using Hasura and it needs to return a setof so I just create a blank table.
I think I'm on the right track but am currently stuck on:
how do I create a timestamp from 'Monday' 09:00:00 for the next monday as I only care about timeslots from today onwards?
how do I convert 'Monday' | 09:00:00 | 11:00:00 into a list of time slots 15 mins apart?
how do I create a timestamp from 'Monday' 09:00:00 for the next monday
as I only care about timeslots from today onwards?
You can use date_trunc for this (see this question for more info):
SELECT date_trunc('week', current_date) + interval '1 week';
From the docs re week:
The number of the ISO 8601 week-numbering week of the year. By
definition, ISO weeks start on Mondays
So taking this value and adding a week gives next Monday (you may need to ammend this behaviour based upon what you want to do if today is monday!).
how do I convert 'Monday' | 09:00:00 | 11:00:00 into a list of time
slots 15 mins apart?
This is a little tricker; generate_series will give you the timeslots but the trick is getting it into a result set. The following should do the job (I have included your sample data; change the values bit to refer to your table) - dbfiddle :
with avail_times as (
select
date_trunc('week', current_date) + interval '1 week' + case day_of_week when 'Monday' then interval '0 day' when 'Tuesday' then interval '1 day' end + start_time as start_time,
date_trunc('week', current_date) + interval '1 week' + case day_of_week when 'Monday' then interval '0 day' when 'Tuesday' then interval '1 day' end + end_time as end_time
from
(
values
('Monday','09:00:00'::time,'11:00:00'::time),
('Monday','13:00:00'::time,'17:00:00'::time),
('Tuesday','08:00:00'::time,'17:00:00'::time)
) as availabilities (day_of_week,
start_time,
end_time) )
select
g.ts
from
(
select
start_time,
end_time
from
avail_times) avail,
generate_series(avail.start_time, avail.end_time - interval '1ms', '15 minutes') g(ts);
A few notes:
The CTE avail_times is used to simplify things; it generates two columns (start_time and end_time) which are the full timestamps (so including the date). In this example the first row is "2020-02-03 09:00:00, 2020-02-03 11:00:00" (I'm running this on 2020-02-02 so 2020-02-03 is next Monday).
The way I'm converting 'monday' etc to a day of the week is a bit of a hack (and I have not bothered to do the full week); there is probably a better way but storing the day of week as an integer would make this simpler.
I subtract 1ms from the end time because I'm assuming you dont want this in the result set.
The main query is using a LATERAL Subquery. See this question for more info.
Aditional Question
how to adjust this so I can pass in a start and end date so I can get
time slots for a particular period
You could do something like the following (just adjust the dates CTE to return whatever days you want to include; you could convert to a function or just pass the dates in as parameters).
Note that as #Belayer mentions my original solution did not cater for shifts over midnight so this addresses that too.
with dates as (
select
day
from
generate_series('2020-02-20'::date, '2020-03-10'::date, '1 day') as day ),
availabilities as (
select
*
from
(
values (1,'09:00:00'::time,'11:00:00'::time),
(1,'13:00:00'::time,'17:00:00'::time),
(2,'08:00:00'::time,'17:00:00'::time),
(3,'23:00:00'::time,'01:00:00'::time)
) as availabilities
(day_of_week, -- 1 = monday
start_time,
end_time) ) ,
avail_times as (
select
d.day + start_time as start_time,
case
end_time > start_time
when true then d.day
else d.day + interval '1 day' end + end_time as end_time
from
availabilities a
inner join dates d on extract(ISODOW from d.day) = a.day_of_week )
select
g.ts
from
(
select
start_time,
end_time
from
avail_times) avail,
generate_series(avail.start_time, avail.end_time - interval '1ms', '15 minutes') g(ts)
order by
g.ts;
The following uses much of the techniques mentioned by #Brits. They present some very good information, so I'll not repeat but suggest you review it (and the links).
I do however take a slightly different approach. First a couple table changes. I use the ISO day of week 1-7 (Monday-Sunday) rather than the day name. The day name is easily extracted for the dater later.
Also I use interval instead to time for start and end times. ( A time data type works for most scenarios but there is one it doesn't (more later).
One thing your description does not make clear is whether the ending time is included it the available time or not. If included the last interval would be 11:00-11:15. If excluded the last interval is 10:45-11:00. I have assumed to excluded it. In the final results the end time is to be read as "up to but not including".
-- setup
create table availabilities (weekday integer, start_time interval, end_time interval);
insert into availabilities (weekday , start_time , end_time )
select wkday
, start_time
, end_time
from (select *
from (values (1, '09:00'::interval, '11:00'::interval)
, (1, '13:00'::interval, '17:00'::interval)
, (2, '08:00'::interval, '17:00'::interval)
, (3, '08:30'::interval, '10:45'::interval)
, (4, '10:30'::interval, '12:45'::interval)
) as v(wkday,start_time,end_time)
) r ;
select * from availabilities;
The Query
It begins with a CTE (next_week) generates a entry for each day of the week beginning Monday and the appropriate ISO day number for it. The main query joins these with the availabilities table to pick up times for matching days. Finally that result is cross joined with a generated timestamp to get the 15 minute intervals.
-- Main
with next_week (wkday,tm) as
(SELECT n+1, date_trunc('week', current_date) + interval '1 week' + n*interval '1 day'
from generate_series (0, 6) n
)
select to_char(gdtm,'Day'), gdtm start_time, gdtm+interval '15 min' end_time
from ( select wkday, tm, start_time, end_time
from next_week nw
join availabilities av
on (av.weekday = nw.wkday)
) s
cross join lateral
generate_series(start_time+tm, end_time+tm- interval '1 sec', interval '15 min') gdtm ;
The outlier
As mentioned there is one scenario where a time data type does not work satisfactory, but you may not nee it. What happens when a shift worker says they available time is 23:00-01:30. Believe me when a shift worker goes to work at 22:00 of Friday, 01:30 is still Friday night, even though the calendar might not agree. (I worked that shift for many years.) The following using interval handles that issue. Loading the same data as prior with an addition for the this case.
insert into availabilities (weekday, start_time, end_time )
select wkday
, start_time
, end_time + case when end_time < start_time
then interval '1 day'
else interval '0 day'
end
from (select *
from (values (1, '09:00'::interval, '11:00'::interval)
, (1, '13:00'::interval, '17:00'::interval)
, (2, '08:00'::interval, '17:00'::interval)
, (3, '08:30'::interval, '10:45'::interval)
, (5, '23:30'::interval, '02:30'::interval) -- Friday Night - Saturday Morning
) as v(wkday,start_time,end_time)
) r
;
select * from availabilities;
Hope this helps.

Always return data from last week's Monday thru Sunday

How to write a sql statement that always returns data from last Monday to the last Sunday? Any guidance is appreciated.
Thanks.
t-clausen.dk's answer does work, but it may not be clear why it works (neither to you nor to the developer who comes after you). Since added clarity sometimes comes at the cost of conciseness and performance, I'd like to explain why it works in case you'd prefer to use that shorter syntax.
SELECT t.*
FROM <table> t CROSS JOIN
(SELECT DATEDIFF(day, 0, getdate() - DATEDIFF(day, 0, getdate()) %7) lastmonday) a
WHERE t.yourdate >= a.lastmonday - 7 and yourdate < a.lastmonday
How SQL Server Stores datetime Internally
SQL Server stores datetime as two 4-byte integers; the first four bytes represents the number of days since 1/1/1900 (or before 1/1/1900, for negative numbers) and the second four bytes represents the number of milliseconds since midnight.
Using datetime with int or decimal
Because datetime is stored as two 4-byte integers, it is easy to move between numeric and date data types in T-SQL. For example, SELECT GETDATE() + 1 returns the same as SELECT DATEADD(day, 1, GETDATE()), and CAST(40777.44281 AS datetime) is the same as 2011-08-24 10:37:38.783.
1/1/1900
Since the first integer portion of datetime is, as was mentioned above, the number of days since 1/1/1900 (also called the SQL Server Epoch), CAST(0 as datetime) is by definition equivalent to
1900-01-01 00:00:00
DATEDIFF(day, 0, GETDATE())
Here's where things start to get both tricky and fun. First, we've already established that when 0 is treated as a date, it's the same as 1/1/1900, so DATEDIFF(day, '1/1/1900', GETDATE()) is the same as DATEDIFF(day, 0, GETDATE())—both will return the current number of days since 1/1/1900. But, wait: the current number of days is exactly what is represented by the first four bytes of datetime! In a single statement we have done two things: 1) we've removed the "time" portion returned by GETDATE() and we've got an integer value we can use to make the calculation for finding the most recent Sunday and the previous Monday a little easier.
Modulo 7 for Monday
DATEDIFF(day, 0, GETDATE()) % 7 takes advantage of the fact that DATEPART(day, 0) (which, at the risk of overemphasizing the point, is the same as DATEPART(day, '1/1/1900')) returns 2 (Monday).1 Therefore, DATEDIFF(day, 0, GETDATE()) % 7 will always yield the number of days from Monday for the current date.
1 Unless you have changed the default behavior by using DATEFIRST.
Most Recent Monday
It's almost too trivial for its own heading, but at this point we can put together everything we've got so far:
GETDATE() - DATEDIFF(day, 0, GETDATE()) %7 gives you the most recent Monday
DATEDIFF(day, 0, GETDATE() - DATEDIFF(day, 0, GETDATE()) %7) gives you the most recent Monday as a whole date, with no time portion.
But the poster wanted everything from Monday to Sunday...
Instead of using the BETWEEN operator, which is inclusive, the posted answer excluded the last day, so that there was no need to do any shifting or calculating to get the right date range:
t.yourdate >= a.lastmonday - 7 returns records with dates since (or including) midnight on the second-most-recent Monday
t.yourdate < a.lastmonday returns records with dates before (but not including) midnight on the most recent Monday.
I am a big proponent of writing code that is easy to understand, both for you and for the developer who comes after you a year later (which might also be you). I believe that the answer I posted earlier should be understandable even to novice T-SQL programmers. However, t-clausen.dk's answer is concise, performs well, and could be encountered in production code, so I wanted to give some explanation to help future visitors understand why it works.
I realized my code was too complex, so I changed my answer to this. The minus one after getdate() corrects this to return the last Monday on Sunday instead of this weeks Monday (tomorrow if today is Sunday).
SELECT t.*
FROM <table> t CROSS JOIN
(SELECT DATEDIFF(week, 0, getdate() - 1) * 7 lastmonday) a
WHERE t.yourdate >= a.lastmonday - 7 and yourdate < a.lastmonday
Old code
SELECT t.*
FROM <table> t CROSS JOIN
(SELECT DATEDIFF(day, 0, getdate() - DATEDIFF(day, 0, getdate()) %7) lastmonday) a
WHERE t.yourdate >= a.lastmonday - 7 and yourdate < a.lastmonday
You did not specify which SQL dialect, so I will answer for T-SQL, which is what I know best, and you've used the tsql tag.
In T-SQL, use the DATEPART function to find the day of the week. When you know the current day of the week, you can get the date of the most recent Sunday and the Monday before it.
In a stored procedure, it's easier—at least, more readable and easier to maintain, in my opinion—to calculate the values for the most recent Sunday and the preceding Monday and store the values in variables. Then those variables can be used in calculations later in the procedure.
CREATE PROCEDURE SomeProcedure
AS
DECLARE #CurrentDayOfWeek int, #LastSunday date, #LastMonday date
SET #CurrentWeekday = DATEPART(weekday, GETDATE())
-- Count backwards from today to get to the most recent Sunday.
-- (#CurrentWeekday % 7) - 1 will give the number of days since Sunday;
-- -1 negates for subtraction.
SET #LastSunday = DATEADD(day, -1 * (( #CurrentWeekday % 7) - 1), GETDATE())
-- Preceding Monday is obviously six days before last Sunday.
SET #LastMonday = DATEADD(day, -6, #LastSunday)
SELECT ReportColumn1, ReportColumn2
FROM ReportTable
WHERE DateColumn BETWEEN #LastMonday AND #LastSunday
If you need to be able to do the calculation in a SELECT statement or a view, it's trivial now that we've worked out the steps, though the query itself is a little messier:
SELECT ReportColumn1, ReportColumn2
FROM ReportTable
WHERE DateColumn
BETWEEN
(
-- Last Monday is six days before...
DATEADD(day, -6,
-- ... last Sunday.
DATEADD(day, -1 * (( DATEPART(weekday, GETDATE()) % 7) - 1), GETDATE())
)
)
AND
(
-- Last Sunday has to be calculated again each time it is used inline.
DATEADD(day, -1 * (( DATEPART(weekday, GETDATE()) % 7) - 1), GETDATE())
)
The parentheses I added are not necessary, but are only there to help you see how the WHERE clause is built.
Finally, note that these use the SQL 2008 date data type; for the datetime data type, you may need to perform your own conversion/truncation to compare whole dates instead of date-plus-time values.
The short code in #t-clausen.dk answer doesn't work for Sunday nor does the first result I found on Google. To test them out, try.
DECLARE #date as DATETIME;
SET #date = '2014-11-23 10:00:00'; -- Sunday at 10a
SELECT DATEADD(wk, DATEDIFF(wk,0,#date), 0) -- 2014-11-24 00:00:00
SELECT CAST(DATEDIFF(week, 0, #date)*7 as datetime) -- 2014-11-24 00:00:00
SELECT CAST(DATEDIFF(day, 0, CAST(#date as DATETIME) - DATEDIFF(day, 0,#date) %7) as DATETIME) --2014-11-17 00:00:00
SELECT DATEADD(wk, DATEDIFF(wk,0,#date-1), 0) --2014-11-17 00:00:00
Thus, you should use the long code from the original answer or modified short code.
SELECT t.*
FROM <table> t CROSS JOIN
(SELECT DATEDIFF(week, 0, getdate() - 1) * 7 lastmonday) a
WHERE t.yourdate >= a.lastmonday - 7 and yourdate < a.lastmonday

T-sql Monday before date

I work at a college and our student management systems academic year start date is determined by the Monday on or before the 1st of August. I need to match this in my query, is there a way to easily get the date of the Monday on or before this date.
The accepted answer didn't work for me because I needed both a Sunday week and a Monday week in the same query. This works across different "datefirst" settings:
SELECT DATEADD(d, -((DATEPART(WEEKDAY, '20110515') - DATEPART(dw, '19000101') + 7) % 7), '20110515')
"DATEPART(dw, '19000101')" will determine your "datefirst" setting since 1900-01-01 was on a Monday. If you want a Tuesday based week, you can change 19000101 to 19000102.
BTW, '20110515' is the only date format that works across all SQL Server culture settings. Dates like '2011-05-06' will get mis-interpreted in certain countries. (credit to Itzik Ben-Gan for pointing this out)
set datefirst 1; -- Make Monday the first day of the week
select dateadd(dd, -1*(datepart(dw, '2009-08-01')-1), '2009-08-01')
Returns July 27th, 2009, which is the Monday on or before August 1. Change it to 2005 when Aug 1 was a Monday and the query will return 08-01
You could use datepart to get the weekday, and then do a little math to back into your monday. This example is using the US default of datefirst 7 (in which Monday is day 2 of the week). Adjust the days to add to be which day of the week Monday is for your locale.
select dateadd(dd, -datepart(dw, '2009-08-01') + 2, '2009-08-01')
very hacky
DECLARE #weekday int
SELECT #weekday = DATEPART(WEEKDAY, '1-Aug-2009')
SELECT CASE
WHEN #weekday = 1 THEN '1-Aug-2009' ELSE DATEADD ( dd,(#weekday-2)*-1, '1-Aug-2009')
END
This is a generic algorithm that will return the first Monday of any month (#inputdate):
DATEADD(wk, DATEDIFF(wk, 0, dateadd(dd, 6 - datepart(day, #inputDate), #inputDate)), 0)
It is a common method for getting the first monday of the month in SQL Server. This link explains how the above calculation works along with many other date calculations.
Here is how the above algorithm could be used to get the Monday on or before the 1st day of a month:
-- Set month to get Monday before or at 1st of month.
DECLARE #inputDate DATETIME
SET #inputDate = '2009-08-01'
-- Get first Monday of month.
DECLARE #firstMonday DATETIME
SET #firstMonday = DATEADD(wk, DATEDIFF(wk, 0, dateadd(dd, 6 - datepart(day, #inputDate), #inputDate)), 0)
-- Determine date for first Monday on or before 1st of month.
DECLARE #startDate DATETIME
SET #startDate = #firstMonday
IF #firstMonday > #inputDate
SET #startDate = DATEADD(wk, -1, #firstMonday)
SELECT #startDate

Is it possible to set start of week for T-SQL DATEDIFF function?

I use DATEDIFF function to filter records added this week only:
DATEDIFF(week, DateCreated, GETDATE()) = 0
and I noticed what it's assumed what week starts on Sunday. But in my case I would prefer to set start of week on Monday. Is it possible somehow in T-SQL?
Thanks!
Update:
Below is an example showing what DATEDIFF doesn't check ##DATEFIRST variable so I need another solution.
SET DATEFIRST 1;
SELECT
DateCreated,
DATEDIFF(week, DateCreated, CAST('20090725' AS DATETIME)) AS D25,
DATEDIFF(week, DateCreated, CAST('20090726' AS DATETIME)) AS D26
FROM
(
SELECT CAST('20090724' AS DATETIME) AS DateCreated
UNION
SELECT CAST('20090725' AS DATETIME) AS DateCreated
) AS T
Output:
DateCreated D25 D26
----------------------- ----------- -----------
2009-07-24 00:00:00.000 0 1
2009-07-25 00:00:00.000 0 1
(2 row(s) affected)
26 Jul 2009 is Sunday, and I want DATEDIFF returns 0 in third column too.
Yes it possible
SET DATEFIRST 1; -- Monday
from http://msdn.microsoft.com/en-us/library/ms181598.aspx
It appears datediff doesn't respect the Datefirst, so make it do so run it like this
create table #testDates (id int identity(1,1), dateAdded datetime)
insert into #testDates values ('2009-07-09 15:41:39.510') -- thu
insert into #testDates values ('2009-07-06 15:41:39.510') -- mon
insert into #testDates values ('2009-07-05 15:41:39.510') -- sun
insert into #testDates values ('2009-07-04 15:41:39.510') -- sat
SET DATEFIRST 7 -- Sunday (Default
select * from #testdates where datediff(ww, DATEADD(dd,-##datefirst,dateadded), DATEADD(dd,-##datefirst,getdate())) = 0
SET DATEFIRST 1 -- Monday
select * from #testdates where datediff(ww, DATEADD(dd,-##datefirst,dateadded), DATEADD(dd,-##datefirst,getdate())) = 0
Stolen from
http://social.msdn.microsoft.com/Forums/en-US/transactsql/thread/8cc3493a-7ae5-4759-ab2a-e7683165320b
I have another solution.
This should be easier to understand, correct me if I am wrong
SET DATEFIRST 1
select DATEDIFF(week, 0, DATEADD(day, -##DATEFIRST, '2018-04-15 00:00:00.000'))
We subtract '-1' from date and Sunday will become Saturday (which is 7nth day of week)
and Mondфy(2) will first day of week
So if i'm getting this correctly,
the only thing we need to do is remove 1 day from both dates on our datediff as following :
DATEDIFF(week,dateadd(day,-1,cast(GETDATE() as date)),
dateadd(day,-1,cast([Date] as date))) as RollingWeek