Get the week start date and week end date from week number - sql

I have a query that counts member's wedding dates in the database.
SELECT
SUM(NumberOfBrides) AS [Wedding Count]
, DATEPART( wk, WeddingDate) AS [Week Number]
, DATEPART( year, WeddingDate) AS [Year]
FROM MemberWeddingDates
GROUP BY DATEPART(year, WeddingDate), DATEPART(wk, WeddingDate)
ORDER BY SUM(NumberOfBrides) DESC
How do I work out when the start and end of each week represented in the result set?
SELECT
SUM(NumberOfBrides) AS [Wedding Count]
, DATEPART(wk, WeddingDate) AS [Week Number]
, DATEPART(year, WeddingDate) AS [Year]
, ??? AS WeekStart
, ??? AS WeekEnd
FROM MemberWeddingDates
GROUP BY DATEPART(year, WeddingDate), DATEPART(wk, WeddingDate)
ORDER BY SUM(NumberOfBrides) DESC

You can find the day of week and do a date add on days to get the start and end dates..
DATEADD(dd, -(DATEPART(dw, WeddingDate)-1), WeddingDate) [WeekStart]
DATEADD(dd, 7-(DATEPART(dw, WeddingDate)), WeddingDate) [WeekEnd]
You probably also want to look at stripping off the time from the date as well though.

Here is a DATEFIRST agnostic solution:
SET DATEFIRST 4 /* or use any other weird value to test it */
DECLARE #d DATETIME
SET #d = GETDATE()
SELECT
#d ThatDate,
DATEADD(dd, 0 - (##DATEFIRST + 5 + DATEPART(dw, #d)) % 7, #d) Monday,
DATEADD(dd, 6 - (##DATEFIRST + 5 + DATEPART(dw, #d)) % 7, #d) Sunday

you can also use this:
SELECT DATEADD(day, DATEDIFF(day, 0, WeddingDate) /7*7, 0) AS weekstart,
DATEADD(day, DATEDIFF(day, 6, WeddingDate-1) /7*7 + 7, 6) AS WeekEnd

Here is another version. If your Scenario requires Saturday to be 1st day of Week and Friday to be last day of Week, the below code will handle that:
DECLARE #myDate DATE = GETDATE()
SELECT #myDate,
DATENAME(WEEKDAY,#myDate),
DATEADD(DD,-(CHOOSE(DATEPART(dw, #myDate), 1,2,3,4,5,6,0)),#myDate) AS WeekStartDate,
DATEADD(DD,7-CHOOSE(DATEPART(dw, #myDate), 2,3,4,5,6,7,1),#myDate) AS WeekEndDate

Expanding on #Tomalak's answer. The formula works for days other than Sunday and Monday but you need to use different values for where the 5 is. A way to arrive at the value you need is
Value Needed = 7 - (Value From Date First Documentation for Desired Day Of Week) - 1
here is a link to the document: https://msdn.microsoft.com/en-us/library/ms181598.aspx
And here is a table that lays it out for you.
| DATEFIRST VALUE | Formula Value | 7 - DATEFIRSTVALUE - 1
Monday | 1 | 5 | 7 - 1- 1 = 5
Tuesday | 2 | 4 | 7 - 2 - 1 = 4
Wednesday | 3 | 3 | 7 - 3 - 1 = 3
Thursday | 4 | 2 | 7 - 4 - 1 = 2
Friday | 5 | 1 | 7 - 5 - 1 = 1
Saturday | 6 | 0 | 7 - 6 - 1 = 0
Sunday | 7 | -1 | 7 - 7 - 1 = -1
But you don't have to remember that table and just the formula, and actually you could use a slightly different one too the main need is to use a value that will make the remainder the correct number of days.
Here is a working example:
DECLARE #MondayDateFirstValue INT = 1
DECLARE #FridayDateFirstValue INT = 5
DECLARE #TestDate DATE = GETDATE()
SET #MondayDateFirstValue = 7 - #MondayDateFirstValue - 1
SET #FridayDateFirstValue = 7 - #FridayDateFirstValue - 1
SET DATEFIRST 6 -- notice this is saturday
SELECT
DATEADD(DAY, 0 - (##DATEFIRST + #MondayDateFirstValue + DATEPART(dw,#TestDate)) % 7, #TestDate) as MondayStartOfWeek
,DATEADD(DAY, 6 - (##DATEFIRST + #MondayDateFirstValue + DATEPART(dw,#TestDate)) % 7, #TestDate) as MondayEndOfWeek
,DATEADD(DAY, 0 - (##DATEFIRST + #FridayDateFirstValue + DATEPART(dw,#TestDate)) % 7, #TestDate) as FridayStartOfWeek
,DATEADD(DAY, 6 - (##DATEFIRST + #FridayDateFirstValue + DATEPART(dw,#TestDate)) % 7, #TestDate) as FridayEndOfWeek
SET DATEFIRST 2 --notice this is tuesday
SELECT
DATEADD(DAY, 0 - (##DATEFIRST + #MondayDateFirstValue + DATEPART(dw,#TestDate)) % 7, #TestDate) as MondayStartOfWeek
,DATEADD(DAY, 6 - (##DATEFIRST + #MondayDateFirstValue + DATEPART(dw,#TestDate)) % 7, #TestDate) as MondayEndOfWeek
,DATEADD(DAY, 0 - (##DATEFIRST + #FridayDateFirstValue + DATEPART(dw,#TestDate)) % 7, #TestDate) as FridayStartOfWeek
,DATEADD(DAY, 6 - (##DATEFIRST + #FridayDateFirstValue + DATEPART(dw,#TestDate)) % 7, #TestDate) as FridayEndOfWeek
This method would be agnostic of the DATEFIRST Setting which is what I needed as I am building out a date dimension with multiple week methods included.

If sunday is considered as week start day, then here is the code
Declare #currentdate date = '18 Jun 2020'
select DATEADD(D, -(DATEPART(WEEKDAY, #currentdate) - 1), #currentdate)
select DATEADD(D, (7 - DATEPART(WEEKDAY, #currentdate)), #currentdate)

Below query will give data between start and end of current week
starting from sunday to saturday
SELECT DOB FROM PROFILE_INFO WHERE DAY(DOB) BETWEEN
DAY( CURRENT_DATE() - (SELECT DAYOFWEEK(CURRENT_DATE())-1))
AND
DAY((CURRENT_DATE()+(7 - (SELECT DAYOFWEEK(CURRENT_DATE())) ) ))
AND
MONTH(DOB)=MONTH(CURRENT_DATE())

Let us break the problem down to two parts:
1) Determine the day of week
The DATEPART(dw, ...) returns a number, 1...7, relative to DATEFIRST setting (docs). The following table summarizes the possible values:
##DATEFIRST
+------------------------------------+-----+-----+-----+-----+-----+-----+-----+-----+
| | 1 | 2 | 3 | 4 | 5 | 6 | 7 | DOW |
+------------------------------------+-----+-----+-----+-----+-----+-----+-----+-----+
| DATEPART(dw, /*Mon*/ '20010101') | 1 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
| DATEPART(dw, /*Tue*/ '20010102') | 2 | 1 | 7 | 6 | 5 | 4 | 3 | 2 |
| DATEPART(dw, /*Wed*/ '20010103') | 3 | 2 | 1 | 7 | 6 | 5 | 4 | 3 |
| DATEPART(dw, /*Thu*/ '20010104') | 4 | 3 | 2 | 1 | 7 | 6 | 5 | 4 |
| DATEPART(dw, /*Fri*/ '20010105') | 5 | 4 | 3 | 2 | 1 | 7 | 6 | 5 |
| DATEPART(dw, /*Sat*/ '20010106') | 6 | 5 | 4 | 3 | 2 | 1 | 7 | 6 |
| DATEPART(dw, /*Sun*/ '20010107') | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 7 |
+------------------------------------+-----+-----+-----+-----+-----+-----+-----+-----+
The last column contains the ideal day-of-week value for Monday to Sunday weeks*. By just looking at the chart we come up with the following equation:
(##DATEFIRST + DATEPART(dw, SomeDate) - 1 - 1) % 7 + 1
2) Calculate the Monday and Sunday for given date
This is trivial thanks to the day-of-week value. Here is an example:
WITH TestData(SomeDate) AS (
SELECT CAST('20001225' AS DATETIME) UNION ALL
SELECT CAST('20001226' AS DATETIME) UNION ALL
SELECT CAST('20001227' AS DATETIME) UNION ALL
SELECT CAST('20001228' AS DATETIME) UNION ALL
SELECT CAST('20001229' AS DATETIME) UNION ALL
SELECT CAST('20001230' AS DATETIME) UNION ALL
SELECT CAST('20001231' AS DATETIME) UNION ALL
SELECT CAST('20010101' AS DATETIME) UNION ALL
SELECT CAST('20010102' AS DATETIME) UNION ALL
SELECT CAST('20010103' AS DATETIME) UNION ALL
SELECT CAST('20010104' AS DATETIME) UNION ALL
SELECT CAST('20010105' AS DATETIME) UNION ALL
SELECT CAST('20010106' AS DATETIME) UNION ALL
SELECT CAST('20010107' AS DATETIME) UNION ALL
SELECT CAST('20010108' AS DATETIME) UNION ALL
SELECT CAST('20010109' AS DATETIME) UNION ALL
SELECT CAST('20010110' AS DATETIME) UNION ALL
SELECT CAST('20010111' AS DATETIME) UNION ALL
SELECT CAST('20010112' AS DATETIME) UNION ALL
SELECT CAST('20010113' AS DATETIME) UNION ALL
SELECT CAST('20010114' AS DATETIME)
), TestDataPlusDOW AS (
SELECT SomeDate, (##DATEFIRST + DATEPART(dw, SomeDate) - 1 - 1) % 7 + 1 AS DOW
FROM TestData
)
SELECT
FORMAT(SomeDate, 'ddd yyyy-MM-dd') AS SomeDate,
FORMAT(DATEADD(dd, -DOW + 1, SomeDate), 'ddd yyyy-MM-dd') AS [Monday],
FORMAT(DATEADD(dd, -DOW + 1 + 6, SomeDate), 'ddd yyyy-MM-dd') AS [Sunday]
FROM TestDataPlusDOW
Output:
+------------------+------------------+------------------+
| SomeDate | Monday | Sunday |
+------------------+------------------+------------------+
| Mon 2000-12-25 | Mon 2000-12-25 | Sun 2000-12-31 |
| Tue 2000-12-26 | Mon 2000-12-25 | Sun 2000-12-31 |
| Wed 2000-12-27 | Mon 2000-12-25 | Sun 2000-12-31 |
| Thu 2000-12-28 | Mon 2000-12-25 | Sun 2000-12-31 |
| Fri 2000-12-29 | Mon 2000-12-25 | Sun 2000-12-31 |
| Sat 2000-12-30 | Mon 2000-12-25 | Sun 2000-12-31 |
| Sun 2000-12-31 | Mon 2000-12-25 | Sun 2000-12-31 |
| Mon 2001-01-01 | Mon 2001-01-01 | Sun 2001-01-07 |
| Tue 2001-01-02 | Mon 2001-01-01 | Sun 2001-01-07 |
| Wed 2001-01-03 | Mon 2001-01-01 | Sun 2001-01-07 |
| Thu 2001-01-04 | Mon 2001-01-01 | Sun 2001-01-07 |
| Fri 2001-01-05 | Mon 2001-01-01 | Sun 2001-01-07 |
| Sat 2001-01-06 | Mon 2001-01-01 | Sun 2001-01-07 |
| Sun 2001-01-07 | Mon 2001-01-01 | Sun 2001-01-07 |
| Mon 2001-01-08 | Mon 2001-01-08 | Sun 2001-01-14 |
| Tue 2001-01-09 | Mon 2001-01-08 | Sun 2001-01-14 |
| Wed 2001-01-10 | Mon 2001-01-08 | Sun 2001-01-14 |
| Thu 2001-01-11 | Mon 2001-01-08 | Sun 2001-01-14 |
| Fri 2001-01-12 | Mon 2001-01-08 | Sun 2001-01-14 |
| Sat 2001-01-13 | Mon 2001-01-08 | Sun 2001-01-14 |
| Sun 2001-01-14 | Mon 2001-01-08 | Sun 2001-01-14 |
+------------------+------------------+------------------+
* For Sunday to Saturday weeks you need to adjust the equation just a little, like add 1 somewhere.

I just encounter a similar case with this one, but the solution here seems not helping me.
So I try to figure it out by myself. I work out the week start date only, week end date should be of similar logic.
Select
Sum(NumberOfBrides) As [Wedding Count],
DATEPART( wk, WeddingDate) as [Week Number],
DATEPART( year, WeddingDate) as [Year],
DATEADD(DAY, 1 - DATEPART(WEEKDAY, dateadd(wk, DATEPART( wk, WeddingDate)-1, DATEADD(yy,DATEPART( year, WeddingDate)-1900,0))), dateadd(wk, DATEPART( wk, WeddingDate)-1, DATEADD(yy,DATEPART( year, WeddingDate)-1900,0))) as [Week Start]
FROM MemberWeddingDates
Group By DATEPART( year, WeddingDate), DATEPART( wk, WeddingDate)
Order By Sum(NumberOfBrides) Desc

The most voted answer works fine except for the 1st week and last week of a year. For example, if the value of WeddingDate is '2016-01-01', the result will be 2015-12-27 and 2016-01-02, but the right answer is 2016-01-01 and 2016-01-02.
Try this:
Select
Sum(NumberOfBrides) As [Wedding Count],
DATEPART( wk, WeddingDate) as [Week Number],
DATEPART( year, WeddingDate) as [Year],
MAX(CASE WHEN DATEPART(WEEK, WeddingDate) = 1 THEN CAST(DATEADD(YEAR, DATEDIFF(YEAR, 0, WeddingDate), 0) AS date) ELSE DATEADD(DAY, 7 * DATEPART(WEEK, WeddingDate), DATEADD(DAY, -(DATEPART(WEEKDAY, DATEADD(YEAR, DATEDIFF(YEAR, 0, WeddingDate), 0)) + 6), DATEADD(YEAR, DATEDIFF(YEAR, 0, WeddingDate), 0))) END) as WeekStart,
MAX(CASE WHEN DATEPART(WEEK, WeddingDate) = DATEPART(WEEK, DATEADD(DAY, -1, DATEADD(YEAR, DATEDIFF(YEAR, 0, WeddingDate) + 1, 0))) THEN DATEADD(DAY, -1, DATEADD(YEAR, DATEDIFF(YEAR, 0, WeddingDate) + 1, 0)) ELSE DATEADD(DAY, 7 * DATEPART(WEEK, WeddingDate) + 6, DATEADD(DAY, -(DATEPART(WEEKDAY, DATEADD(YEAR, DATEDIFF(YEAR, 0, WeddingDate), 0)) + 6), DATEADD(YEAR, DATEDIFF(YEAR, 0, WeddingDate), 0))) END) as WeekEnd
FROM MemberWeddingDates
Group By DATEPART( year, WeddingDate), DATEPART( wk, WeddingDate)
Order By Sum(NumberOfBrides) Desc;
The result looks like:
It works for all weeks, 1st or others.

Week Start & End Date From Date For Power BI Dax Formula
WeekStartDate = [DateColumn] - (WEEKDAY([DateColumn])-1)
WeekEndDate = [DateColumn] + (7-WEEKDAY([DateColumn]))

This is my solution
SET DATEFIRST 1; /* change to use a different datefirst */
DECLARE #date DATETIME
SET #date = CAST('2/6/2019' as date)
SELECT DATEADD(dd,0 - (DATEPART(dw, #date) - 1) ,#date) [dateFrom],
DATEADD(dd,6 - (DATEPART(dw, #date) - 1) ,#date) [dateTo]

Get Start Date & End Date by Custom Date
DECLARE #Date NVARCHAR(50)='05/19/2019'
SELECT
DATEADD(DAY,CASE WHEN DATEPART(WEEKDAY, #Date)=1 THEN -6 ELSE 2 - DATEPART(WEEKDAY, #Date) END, CAST(#Date AS DATE)) [Week_Start_Date]
,DATEADD(DAY,CASE WHEN DATEPART(WEEKDAY, #Date)=1 THEN 0 ELSE 8 - DATEPART(WEEKDAY, #Date) END, CAST(#Date AS DATE)) [Week_End_Date]

Another way to do it:
declare #week_number int = 6280 -- 2020-05-07
declare #start_weekday int = 0 -- Monday
declare #end_weekday int = 6 -- next Sunday
select
dateadd(week, #week_number, #start_weekday),
dateadd(week, #week_number, #end_weekday)
Explanation:
#week_number is the week number since the initial calendar date '1900-01-01'. It can be computed this way: select datediff(week, 0, #wedding_date) as week_number
#start_weekday for the week first day: 0 for Monday, -1 if Sunday
#end_weekday for the week last day: 6 for next Sunday, 5 if Saturday
dateadd(week, #week_number, #end_weekday): adds the given number of weeks and the given number of days into the initial calendar date '1900-01-01'

This doesn't come from me, but it got the job done regardless:
SELECT DATEADD(wk, -1, DATEADD(DAY, 1-DATEPART(WEEKDAY, GETDATE()), DATEDIFF(dd, 0, GETDATE()))) --first day previous week
SELECT DATEADD(wk, 0, DATEADD(DAY, 1-DATEPART(WEEKDAY, GETDATE()), DATEDIFF(dd, 0, GETDATE()))) --first day current week
SELECT DATEADD(wk, 1, DATEADD(DAY, 1-DATEPART(WEEKDAY, GETDATE()), DATEDIFF(dd, 0, GETDATE()))) --first day next week
SELECT DATEADD(wk, 0, DATEADD(DAY, 0-DATEPART(WEEKDAY, GETDATE()), DATEDIFF(dd, 0, GETDATE()))) --last day previous week
SELECT DATEADD(wk, 1, DATEADD(DAY, 0-DATEPART(WEEKDAY, GETDATE()), DATEDIFF(dd, 0, GETDATE()))) --last day current week
SELECT DATEADD(wk, 2, DATEADD(DAY, 0-DATEPART(WEEKDAY, GETDATE()), DATEDIFF(dd, 0, GETDATE()))) --last day next week
I found it here.

I have a way other, It is select day Start and day End of Week Current:
DATEADD(d, -(DATEPART(dw, GETDATE()-2)), GETDATE()) is date time Start
and
DATEADD(day,7-(DATEPART(dw,GETDATE()-1)),GETDATE()) is date time End

Not sure how useful this is, but I ended up here from looking for a solution on Netezza SQL and couldn't find one on stack overflow.
For IBM netezza you would use something (for week start mon, week end sun) like:
select
next_day (WeddingDate, 'SUN') -6 as WeekStart,
next_day (WeddingDate, 'SUN') as WeekEnd

for Access Queries, you can use in the below format as a field
"FirstDayofWeek:IIf(IsDate([ForwardedForActionDate]),CDate(Format([ForwardedForActionDate],"dd/mm/yyyy"))-(Weekday([ForwardedForActionDate])-1))"
direct Calculation allowed..

Related

SQL - How to determine fiscal quarter when quarters are determined by # of days?

I have a situation I've never seen before, where a fiscal year started on 2/4/2018 is broken down like this:
Q1 - 111 days long (2/4/2018 - 5/26/2018)
Q2 - 83 days long (5/27/2018 - 8/18/2018)
Q3 - 83 days long (8/19/2018 - 11/10/2018)
Q4 - 83 days long (11/11/2018 - 2/2/2019)
That is considered FY 2018
The next year, FY 2019, starts on 2/3/2019 and has the same quarter lengths, and Q4 would end on 2/1/2020. FY 2020 then starts on 2/2/2020.
I need to be able to determine the fiscal year and quarter for a given date (#testdate in my code below). The following works for FY 2018:
declare #startdate date = '2/4/2018'
, #startyear int = 2018
, #testdate date = '2/5/2018'
select 'Fiscal Year' = case when datediff(dd, #startdate, #testdate) between 0 and 363 then #startyear else 0 end
select 'Fiscal Quarter' = case when datediff(dd, #startdate, #testdate) between 0 and 111 then 'Q1'
when datediff(dd, #startdate, #testdate) between 112 and 195 then 'Q2'
when datediff(dd, #startdate, #testdate) between 196 and 279 then 'Q3'
when datediff(dd, #startdate, #testdate) between 280 and 363 then 'Q4'
else 'Q0' end
The problem is when I have a date after the end of FY 2018 Q4 (2/2/2019). I'm not sure how to get any date past that to automatically fall into the day ranges (0-111, 112-195, 196-279, 280-363). Manually, for FY 2019, I can subtract 364 from the date and that seems to work. For FY 2020, I can subtract 364 * 2 (728). For each year after that, keep subtracting 364 * n where n is the number of years between whatever future fiscal year and FY 2018.
This works for FY 2020:
declare #startdate date = '2/4/2018'
, #startyear int = 2018
, #testdate date = '5/28/2020'
, #testdate2 date = null
set #testdate2 = dateadd(d, -728, #testdate)
select 'Fiscal Year' = case when datediff(dd, #startdate, #testdate2) between 0 and 363 then #startyear + 2 else 0 end
select 'Fiscal Quarter' = case when datediff(dd, #startdate, #testdate2) between 0 and 111 then 'Q1'
when datediff(dd, #startdate, #testdate2) between 112 and 195 then 'Q2'
when datediff(dd, #startdate, #testdate2) between 196 and 279 then 'Q3'
when datediff(dd, #startdate, #testdate2) between 280 and 363 then 'Q4'
else 'Q0' end
I'm guessing the solution revolves around how to calculate that -728 automatically (which would be 364 * n), and how to increment #startyear accordingly.
Any ideas on how to determine the fiscal year and quarter for a given date with these odd fiscal quarters?
Thanks!
Here is a solution which does an integer division to get the number of years after 2018. We then subtract 364 times this number from DATEDIFF the base year to get the number of days from the start of the current tax year. We can use your case statement to determine the quarter.
create function dbo.quarter (#date date)
returns varchar(100)
as
begin
declare #days int = datediff(dd,'2018-04-02',#date)
declare #years int = #days / 364
set #days = #days - (#years * 364)
set #years = #years + 2018
declare #quarter char(2)= case
when #days between 0 and 111 then 'Q1'
when #days between 112 and 195 then 'Q2'
when #days between 196 and 279 then 'Q3'
when #days between 280 and 363 then 'Q4'
else 'Q0' end
return concat(#years,'-', #quarter)
end
GO
✓
select dbo.quarter('5/4/2018') quarter;
select dbo.quarter('2020-04-02') quarter;
select dbo.quarter('2022-01-24') quarter;
select dbo.quarter(GETDATE()) quarter;
select dbo.quarter('2021-12-25') quarter;
GO
| quarter |
| :------ |
| 2018-Q1 |
| quarter |
| :------ |
| 2020-Q1 |
| quarter |
| :------ |
| 2021-Q4 |
| quarter |
| :------ |
| 2021-Q4 |
| quarter |
| :------ |
| 2021-Q3 |
db<>fiddle here

SQL Server - Get calander month begining and end

I've tried searching but cannot find anything.
I am trying to get the first and last date of the calander month.
So for example the calander month for January 2020 actually starts on December 30th 2019 and ends on February 2nd 2020. (Week 1 - 5)
|---------------------|-------------------|-------------------|
| Week number | From Date | To Date |
|---------------------|-------------------|-------------------|
| Week 01 | December 30, 2019 | January 5, 2020 |
|---------------------|-------------------|-------------------|
| Week 05 | January 27, 2020 | February 2, 2020 |
|---------------------|-------------------|-------------------|
Using this website to get week numbers
Is this possible?
Many thanks.
If you are using MSSQL-2012 or onwards.
DECLARE #DATE DATETIME='29-JAN-2020'
SELECT DATEADD(DAY, 2 - CASE WHEN DATEPART(WEEKDAY, #DATE-DAY(#DATE)+1)=1 THEN 8 ELSE DATEPART(WEEKDAY, #DATE-DAY(#DATE)+1) END, CAST( #DATE-DAY(#DATE)+1 AS DATE)) [MONTH_START_DATE],
DATEADD(DAY, 8 - CASE WHEN DATEPART(WEEKDAY, EOMONTH(#DATE))=1 THEN 8 ELSE DATEPART(WEEKDAY, EOMONTH(#DATE)) END , CAST(EOMONTH(#DATE) AS DATE)) [MONTH_END_DATE];
You can try on below link:-
https://dbfiddle.uk/?rdbms=sqlserver_2012&fiddle=9747ea25d0d0bc343be8dbcc90803303
You can use this logic:
select convert(date, dateadd(day, 1 - day(getdate()), getdate())) as month_first,
dateadd(day, 1, eomonth(getdate(), -1)) as alternative_month_first,
eomonth(getdate()) as month_last
Of course, you would use whatever date you wanted instead of getdate().

Select based on the next business day in a calendar table

I have a calendar table (Calendar_Date, Is_Business_Day) already filled in.
I have already managed to do a SELECT on this basis :
If today is before the 3rd day of the current month, select all the days before this day until the last day of the penultimate month
For example : Today is 2018-05-02, this is my output :
Calendar_Date | Is_Business_Day
2000-01-01 | 0
... |
2018-03-29 | 1
2018-03-30 | 1
2018-03-31 | 0
If today is after the 3rd day of the current month, select all the days before this day until the last day of the last month.
For example : Tomorrow, 2018-05-03 this will be my output :
Calendar_Date | Is_Business_Day
2000-01-01 | 0
... |
2018-04-28 | 0
2018-04-29 | 0
2018-04-30 | 1
This is my query :
SELECT Calendar_Date, Is_Business_Day
FROM Calendar_Table
WHERE (Calendar_Date <= (CASE WHEN DATEPART(day, GETDATE()) >= 3 THEN EOMONTH(DATEADD(MONTH, - 1, GETDATE())) ELSE EOMONTH(DATEADD(MONTH, - 2, GETDATE())) END))
This is working perfectly, but what i would like it to do now is to switch after the first business day after the 3rd day of the month, instead of switching after the 3rd day of the month.
How can I use the information about business days in my calendar table to do this?
I think following query should work.
;WITH CTE AS
(
SELECT Calendar_Date, Is_Business_Day
FROM Calendar_Table
WHERE (Calendar_Date <= (CASE WHEN DATEPART(day, GETDATE()) >= 3
THEN EOMONTH(DATEADD(MONTH, - 1, GETDATE()))
ELSE EOMONTH(DATEADD(MONTH, - 2, GETDATE())) END))
)
SELECT * FROM CTE
WHERE Calendar_Date >= (SELECT MIN(Calendar_Date) FROM CTE WHERE Is_Business_Day=1)

MSSQL - Getting last 6 weeks returns last 8 weeks

I am having a problem with week numbers. The customers week starts on a Tuesday, so ends on a Monday. So I have done:
Set DateFirst 2
When I then use
DateAdd(ww,#WeeksToShow, Date)
It occasionally gives me 8 weeks of information. I think it is because it goes over to the previous year, but I am not sure how to fix it.
If I do:
(DatePart(dy,Date) / 7) - #WeeksToShow
Then it works better, but obviously doesn't work going through to previous years as it just goes to minus figures.
Edit:
My currently SQL (If it helps at all without any data)
Set DateFirst 2
Select
DATEPART(yyyy,SessionDate) as YearNo,
DATEPART(ww,SessionDate) as WeekNo,
DATEADD(DAY, 1 - DATEPART(WEEKDAY, SessionDate + SessionTime), CAST(SessionDate +SessionTime AS DATE)) [WeekStart],
DATEADD(DAY, 7 - DATEPART(WEEKDAY, SessionDate + SessionTime), CAST(SessionDate + SessionTime AS DATE)) [WeekEnd],
DateName(dw,DATEADD(DAY, 7 - DATEPART(WEEKDAY, SessionDate + SessionTime), CAST(SessionDate + SessionTime AS DATE))) as WeekEndName,
Case when #ConsolidateSites = 1 then 0 else SiteNo end as SiteNo,
Case when #ConsolidateSites = 1 then 'All' else CfgSites.Name end as SiteName,
GroupNo,
GroupName,
DeptNo,
DeptName,
SDeptNo,
SDeptName,
PluNo,
PluDescription,
SUM(Qty) as SalesQty,
SUM(Value) as SalesValue
From
PluSalesExtended
Left Join
CfgSites on PluSalesExtended.SiteNo = CfgSites.No
Where
Exists (Select Descendant from DescendantSites where Parent in (#SiteNo) and Descendant = PluSalesExtended.SiteNo)
AND (DATEPART(WW,SessionDate + SessionTime) !=DATEPART(WW,GETDATE()))
AND SessionDate + SessionTime between DATEADD(ww,#NumberOfWeeks * -1,#StartingDate) and #StartingDate
AND TermNo = 0
AND PluEntryType <> 4
Group by
DATEPART(yyyy,SessionDate),
DATEPART(ww,SessionDate),
DATEADD(DAY, 1 - DATEPART(WEEKDAY, SessionDate + SessionTime), CAST(SessionDate +SessionTime AS DATE)),
DATEADD(DAY, 7 - DATEPART(WEEKDAY, SessionDate + SessionTime), CAST(SessionDate + SessionTime AS DATE)),
Case when #ConsolidateSites = 1 then 0 else SiteNo end,
Case when #ConsolidateSites = 1 then 'All' else CfgSites.Name end,
GroupNo,
GroupName,
DeptNo,
DeptName,
SDeptNo,
SDeptName,
PluNo,
PluDescription
order by WeekEnd
There are two issues here, the first is that I suspect you are defining 8 weeks of data as having 8 different values for DATEPART(WEEK, in which case you can replicate the root cause of the issue by looking at what ISO would define as the first week of 2015:
SET DATEFIRST 2;
SELECT Date, Week = DATEPART(WEEK, Date)
FROM (VALUES
('20141229'), ('20141230'), ('20141231'), ('20150101'),
('20150102'), ('20150103'), ('20150104')
) d (Date);
Which gives:
Date Week
-----------------
2014-12-29 52
2014-12-30 53
2014-12-31 53
2015-01-01 1
2015-01-02 1
2015-01-03 1
2015-01-04 1
So although you only have 7 days, you have 3 different week numbers. The problem is that DATEPART(WEEK is quite a simplistic function, and will simply return the number of week boundaries passed since the first day of the year, a better function would be ISO_WEEK since this takes into account year boundaries nicely:
SET DATEFIRST 2;
SELECT Date, Week = DATEPART(ISO_WEEK, Date)
FROM (VALUES
('20141229'), ('20141230'), ('20141231'), ('20150101'),
('20150102'), ('20150103'), ('20150104')
) d (Date);
Which gives:
Date Week
-----------------
2014-12-29 1
2014-12-30 1
2014-12-31 1
2015-01-01 1
2015-01-02 1
2015-01-03 1
2015-01-04 1
The problem is, that this does not take into account that the week starts on Tuesday, since the ISO week runs Monday to Sunday, you could adapt your usage slightly to get the week number of the day before:
SET DATEFIRST 2;
SELECT Date, Week = DATEPART(ISO_WEEK, DATEADD(DAY, -1, Date))
FROM (VALUES
('20141229'), ('20141230'), ('20141231'), ('20150101'),
('20150102'), ('20150103'), ('20150104')
) d (Date);
Which would give:
Date Week
-----------------
2014-12-29 52
2014-12-30 1
2014-12-31 1
2015-01-01 1
2015-01-02 1
2015-01-03 1
2015-01-04 1
So Monday the 29th December is now recognized as the previous week. The problem is that there is no ISO_YEAR built in function, so you will need to define your own. This is a fairly trivial function, even so I almost never create scalar functions because they perform terribly, instead I use an inline table valued function, so for this I would use:
CREATE FUNCTION dbo.ISOYear (#Date DATETIME)
RETURNS TABLE
AS
RETURN
( SELECT IsoYear = DATEPART(YEAR, #Date) +
CASE
-- Special cases: Jan 1-3 may belong to the previous year
WHEN (DATEPART(MONTH, #Date) = 1 AND DATEPART(ISO_WEEK, #Date) > 50) THEN -1
-- Special case: Dec 29-31 may belong to the next year
WHEN (DATEPART(MONTH, #Date) = 12 AND DATEPART(ISO_WEEK, #Date) < 45) THEN 1
ELSE 0
END
);
Which just requires a subquery to be used, but the extra typing is worth it in terms of performance:
SET DATEFIRST 2;
SELECT Date,
Week = DATEPART(ISO_WEEK, DATEADD(DAY, -1, Date)),
Year = (SELECT ISOYear FROM dbo.ISOYear(DATEADD(DAY, -1, Date)))
FROM (VALUES
('20141229'), ('20141230'), ('20141231'), ('20150101'),
('20150102'), ('20150103'), ('20150104')
) d (Date);
Or you can use CROSS APPLY:
SET DATEFIRST 2;
SELECT Date,
Week = DATEPART(ISO_WEEK, DATEADD(DAY, -1, Date)),
Year = y.ISOYear
FROM (VALUES
('20141229'), ('20141230'), ('20141231'), ('20150101'),
('20150102'), ('20150103'), ('20150104')
) d (Date)
CROSS APPLY dbo.ISOYear(d.Date) y;
Which gives:
Date Week Year
---------------------------
2014-12-29 52 2014
2014-12-30 1 2015
2014-12-31 1 2015
2015-01-01 1 2015
2015-01-02 1 2015
2015-01-03 1 2015
2015-01-04 1 2015
Even with this method, by simply getting a date 6 weeks ago you sill still end up with 7 weeks if the date you are using is not a Tuesday, because you will have 5 full weeks, and a part week at the start and a part week at the end, this is the second issue. So you need to make sure your start date is a Tuesday. The following will get you Tuesday of 7 weeks ago:
SELECT CAST(DATEADD(DAY, 1 - DATEPART(WEEKDAY, GETDATE()), DATEADD(WEEK, -6, GETDATE())) AS DATE);
The logic of this is explained better in this answer, the following is the part that will get the start of the week (based on your datefirst settings):
SELECT DATEADD(DAY, 1 - DATEPART(WEEKDAY, GETDATE()), GETDATE());
Then all I have done is substitute the second GETDATE() with DATEADD(WEEK, -6, GETDATE()) so that it is getting the start of the week 6 weeks ago, then there is just a cast to date to remove the time element from it.
This will get you current week + 5 previous weeks starting tuesday:
WHERE dateadd(week, datediff(d, 0, getdate()-1)/7 - 4, 1) <= yourdatecolumn
This will show examples:
DECLARE #wks int = 6 -- Weeks To Show
SELECT
dateadd(week, datediff(d, 0, getdate()-1)/7 - 4, 1) tuesday5weeksago,
dateadd(week, datediff(d, 0, getdate()-1)/7 - 5, 1) tuesday6weeksago,
dateadd(week, datediff(d, 0, getdate()-1)/7 - 6, 1) tuesday7weeksago,
dateadd(week, datediff(d, 0, getdate()-1)/7 - #wks + 1, 1) tuesdaydynamicweeksago
Result:
tuesday5weeksago tuesday6weeksago tuesday7weeksago tuesdaydynamicweeksago
2015-01-27 2015-01-20 2015-01-13 2015-01-20

SQL: get next relative day of week. (Next Monday, Tuesday, Wed.....)

What I need is a date for the next given day (Monday, Tuesday, Wed...) following today's date.
The user is allowed to select what day following they want and that is stored as an int in a table. "Call me next Tuesday (3)"
Sunday = 1
Monday = 2
Tuesday = 3
...
So my table looks like this.
UserID, NextDayID
What I have come up with is:
select dateadd(dd,(7 - datepart(dw,GETDATE()) + NextDayID ) % 7, getdate())
It seems to work and will return today's date if you ask for the next whatever day today is which I can add a week if needed.
What I am wondering is, is that a good solution or is there something that I'm missing?
1) Your solution uses a non-deterministic function: datepart(dw...) . Because of this aspect, changing DATEFIRST setting will gives different results. For example, you should try:
SET DATEFIRST 7;
your solution;
and then
SET DATEFIRST 1;
your solution;
2) Following solution is independent of DATEFIRST/LANGUAGE settings:
DECLARE #NextDayID INT = 0 -- 0=Mon, 1=Tue, 2 = Wed, ..., 5=Sat, 6=Sun
SELECT DATEADD(DAY, (DATEDIFF(DAY, #NextDayID, GETDATE()) / 7) * 7 + 7, #NextDayID) AS NextDay
Result:
NextDay
-----------------------
2013-09-23 00:00:00.000
This solution is based on following property of DATETIME type:
Day 0 = 19000101 = Mon
Day 1 = 19000102 = Tue
Day 2 = 19000103 = Wed
...
Day 5 = 19000106 = Sat
Day 6 = 19000107 = Sun
So, converting INT value 0 to DATETIME gives 19000101.
If you want to find the next Wednesday then you should start from day 2 (19000103/Wed), compute days between day 2 and current day (20130921; 41534 days), divide by 7 (in order to get number of full weeks; 5933 weeks), multiple by 7 (41531 fays; in order to get the number of days - full weeks between the first Wednesday/19000103 and the last Wednesday) and then add 7 days (one week; 41538 days; in order to get following Wednesday). Add this number (41538 days) to the starting date: 19000103.
Note: my current date is 20130921.
Edit #1:
DECLARE #NextDayID INT;
SET #NextDayID = 1; -- Next Sunday
SELECT DATEADD(DAY, (DATEDIFF(DAY, ((#NextDayID + 5) % 7), GETDATE()) / 7) * 7 + 7, ((#NextDayID + 5) % 7)) AS NextDay
Result:
NextDay
-----------------------
2013-09-29 00:00:00.000
Note: my current date is 20130923.
A calendar table is an alternative to using a bunch of date functions and date arithmetic. A minimal calendar table for this particular problem might look something like this.
2013-09-20 Fri
2012-09-21 Sat
2012-09-22 Sun
2012-09-23 Mon
2012-09-24 Tue
...
So a query to get the next Monday might look like this.
select min(cal_date)
from calendar
where cal_date > current_date
and day_of_week = 'Mon';
In practice, you'll probably want a lot more columns in the calendar table, because you'll find a lot of uses for it.
Also, code that uses a calendar table can usually be seen to be obviously correct. Reading the code above is simple: select the minimum calendar date that's after today and that falls on Monday. It's pretty rare to see code that relies on date functions and date arithmetic that's obviously correct.
A calendar table in PostgreSQL
It's an old question. But I'm sure that posting better solution worth it.
-- 0 = 1st Mon, 1 = 1st Tue, 2 = 1st Wed, ..., 5 = 1st Sat, 6 = 1st Sun
-- 7 = 2nd Mon, 8 = 2nd Tue, ...
declare #NextDayID int = 0, #Date date = getdate()
select cast (cast (
-- last Monday before [Date] inclusive, starting from 1900-01-01
datediff (day, #NextDayID % 7, #Date) / 7 * 7
-- shift on required number of days
+ #NextDayID + 7
as datetime) as date)
This solution is improved solution of #Bogdan Sahlean.
It can operate #NextDayID that is greater than 6.
So you can, for example, find 2nd Monday from today.
Following query shows that my solution works correctly.
select [Date]
, convert (char(5), [0], 10) as Mon1
, convert (char(5), [1], 10) as Tue1
, convert (char(5), [2], 10) as Wed1
, convert (char(5), [3], 10) as Thu1
, convert (char(5), [4], 10) as Fri1
, convert (char(5), [5], 10) as Sat1
, convert (char(5), [6], 10) as Sun1
, convert (char(5), [7], 10) as Mon2
, convert (char(5), [8], 10) as Tue2
from (
select [Date], NextDayID
, cast (cast (
datediff (day, NextDayID % 7, [Date]) / 7 * 7 -- last Monday before [Date] inclusive, starting from 1900-01-01
+ NextDayID + 7 -- shift on required number of days
as datetime) as date) as NextDay
from (
select datefromparts (2018, 5, dt) as [Date]
from (values(14),(15),(16),(17),(18),(19),(20))t_(dt)
) d
cross join (values(0),(1),(2),(3),(4),(5),(6),(7),(8))nd(NextDayID)
) t
pivot (
min (NextDay) for NextDayID in ([0], [1], [2], [3], [4], [5], [6], [7], [8])
) pvt
Result:
Date | Mon1 | Tue1 | Wed1 | Thu1 | Fri1 | Sat1 | Sun1 | Mon2 | Tue2
-----------+-------+-------+-------+-------+-------+-------+-------+-------+------
2018-05-14 | 05-21 | 05-15 | 05-16 | 05-17 | 05-18 | 05-19 | 05-20 | 05-28 | 05-22
2018-05-15 | 05-21 | 05-22 | 05-16 | 05-17 | 05-18 | 05-19 | 05-20 | 05-28 | 05-29
2018-05-16 | 05-21 | 05-22 | 05-23 | 05-17 | 05-18 | 05-19 | 05-20 | 05-28 | 05-29
2018-05-17 | 05-21 | 05-22 | 05-23 | 05-24 | 05-18 | 05-19 | 05-20 | 05-28 | 05-29
2018-05-18 | 05-21 | 05-22 | 05-23 | 05-24 | 05-25 | 05-19 | 05-20 | 05-28 | 05-29
2018-05-19 | 05-21 | 05-22 | 05-23 | 05-24 | 05-25 | 05-26 | 05-20 | 05-28 | 05-29
2018-05-20 | 05-21 | 05-22 | 05-23 | 05-24 | 05-25 | 05-26 | 05-27 | 05-28 | 05-29
This solution doesn't depend on ##datefirst.
I think this is the best way of doing of finding the next Monday
CONVERT(VARCHAR(11),DateAdd(DAY,case
when (DateName(WEEKDAY, NextVisitDate) ='Tuesday') Then 6
when (DateName(WEEKDAY, NextVisitDate) ='Wednesday') Then 5
when (DateName(WEEKDAY, NextVisitDate) ='Thursday') Then 4
when (DateName(WEEKDAY, NextVisitDate) ='Friday') Then 3
when (DateName(WEEKDAY, NextVisitDate) ='Saturday') Then 2
when (DateName(WEEKDAY, NextVisitDate) ='Sunday') Then 1
else 0 end, DateAdd(DAY, DateDiff(DAY, 0, NextVisitDate), 0)),106) AS Monday,
Find the next upcoming day including today if today is that day which needs to be found out.
Just make a tweak... Set the variable #weekdayno as follows:
1 = Sunday, 2 = Monday, 3 = Tuesday, 4 = Wednesday, 5 = Thursday, 6 = Friday, 7 = Saturday
DECLARE #weekdayno INT
DECLARE #todayno INT
SET #weekdayno = 2 ---For Monday----
SET #todayno = DATEPART(dw,GETDATE())
SELECT CASE
WHEN (#todayno = #weekdayno)
THEN CONVERT(varchar, GETDATE(), 101)
WHEN (#todayno < #weekdayno)
THEN CONVERT(varchar, (#weekdayno - #todayno + GETDATE()), 101)
WHEN (#todayno > #weekdayno)
then CONVERT(varchar,(GETDATE() - (#todayno - #weekdayno) + 7), 101)
END AS UpcomingOrToday
The following function enables to you generate the table on-the-fly...this is how I usually do it...I don't like the idea of a perm date table...seems unnecessary, but every person and situation are different :-)
CREATE function [dbo].[fxDateTable]
(
#begindate datetime = null
, #enddate datetime = null
)
RETURNS #dates TABLE
(
EventDate datetime primary key not null
)
as
begin
select #enddate = isnull(#enddate, getdate())
select #begindate = isnull(#begindate, dateadd(day, -3, #enddate))
insert #dates
select dateadd(day, number, #begindate)
from
(select distinct number from master.dbo.spt_values
where name is null
) n
where dateadd(day, number, #begindate) < #enddate
return
end
Try this: This will give the date for required weekday in a month.
declare #monthstartdate date='2020-01-01',#monthenddate date='2020-01-31',#weekday char(9)='thursday',#weeknum int=4
; with cte(N,WeekDayName_C,Date_C) as
(select 1,datename(WEEKDAY,#monthstartdate),#monthstartdate
union all
select n+1,datename(WEEKDAY,dateadd(day,n,#monthstartdate)),dateadd(day,n,#monthstartdate) from cte where n<31 and Date_C<=#monthenddate )
select * from (select *,ROW_NUMBER() over (partition by WeekDayName_C order by Date_C asc)Weeknum from cte)a
where WeekDayName_C=#weekday and Weeknum=#weeknum
I made this as a function, in which the procedure uses available streamlined knowledge thus it's, I think, a robust solution.
CREATE FUNCTION [nilnul.time_._dated.date].[NextWeekday]
(
#nextWeekDay int -- sunday as firstday is 1.
)
RETURNS datetime
AS
BEGIN
declare #time datetime;
set #time=getdate();
declare #weekday int;
set #weekday = datepart(weekday, #time) ;
declare #diff int;
set #diff= #nextWeekDay-#weekday;
--modulo 7 bijectively
declare #moduloed int;
set #moduloed = case
when #diff <=0 then #diff+7
else #diff
end;
return dateadd(day, #moduloed, #time);
END
I couldn't resist sharing my version. This is primitive for next business/weekday, but just change the "not in ('Saturday','Sunday')" part to be the day you are looking for inside the next week.
select top 1 [date]=convert(date, [date]),
, DayName = datename(dw, [date])
, Offset = [day]
from (
select [0]=getdate()
, [1]=getdate()+1
, [2]=getdate()+2
, [3]=getdate()+3
, [4]=getdate()+4
, [5]=getdate()+5
, [6]=getdate()+6) PVT
unpivot([date] for [day] in ([0],[1],[2],[3],[4],[5],[6])) as unpvt
where datename(dw,[date]) not in ('Saturday','Sunday') --wut day you lookin for?
and getdate() <> [date] --do you want today to be part of the results?
order by [date] asc