I need to split and save ISO_WEEK and YEAR between two dates in a table.
To get that data I use below query.
DECLARE #_start DATE = '2019-01-01'
DECLARE #_end DATE = '2019-12-31'
SELECT DATEPART(YEAR,dateadd(week, number, #_start)) AS YEAR
,DATEPART(ISO_WEEK,dateadd(week, number, #_start)) AS CW
FROM (SELECT x.number FROM master..spt_values x WHERE type = 'P'
AND #_end >= dateadd(week, number, #_start)) date_part
This logic fails on last week of the year where I get CW as 1 and Year as 2019. But actually, CW is 1 and YEAR is 2020.
YEAR CW
----------- -----------
2019 1
2019 2
. .
. .
2019 52
2019 1
Any suggestions to handle this?
Try this
DECLARE #_start DATE = '2019-01-01'
DECLARE #_end DATE = '2019-12-31'
SELECT DATEPART(year, s.Sunday) AS YEAR
, DATEPART(iso_week, s.Sunday) AS CW
FROM (
SELECT DATEADD(week, x.number, DATEADD(day, 7 - (DATEPART(dw, #_start) + ##DATEFIRST - 1) % 7, #_start)) AS Sunday
FROM master..spt_values x
WHERE type = 'P'
) s
WHERE #_end >= s.Sunday
This first offsets #_start to following Sunday and then loop all Sundays from then on.
The idea is that DATEPART(YEAR, ...) for Sundays returns less surprising year for DATEPART(ISO_WEEK, ...)
Related
s_term
w_due
2
2
4
2
Given a table as shown above, I want to extract the corresponding date for the month of August/September (dd-mm-yyyy) as follows:
<s_term, w_due>
(2,2) means a service started on 2nd Monday of August and I need to return the date as the 4th Monday of August as indicated by w_due which indicates the stepsize from the starting point.
Similarly for (4, 2): I want to return the 2nd Monday in September as the service start date is on 4th Monday of August with a w_due of 2.
A Calendar Table would be well worth the effort.
Here is an option where we use an ad-hoc tally table to create an ad-hoc calendar table.
Example
Declare #BaseDate date = '2021-08-01'
;with cte as (
Select D
,WN = row_number() over (partition by month(d) order by D)
From ( Select Top 90 D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),#BaseDate) From master..spt_values n1 ) D
Where datename(WEEKDAY,D)='Monday'
)
Select A.*
,Date1 = B.D
,Date2 = C.D
From YourTable A
Join cte B on s_term=B.WN and month(B.D)=month(#BaseDate)
Join cte C on w_due =C.WN and month(C.D)=month(dateadd(month,1,#BaseDate))
Results
s_term w_due Date1 Date2
2 2 2021-08-09 2021-09-13
4 2 2021-08-23 2021-09-13
DECLARE #referenceMonth DATE = '2021-08-01';
DECLARE #tbl TABLE (s_term int, w_due int);
INSERT INTO #tbl VALUES (2, 2), (4, 2);
SELECT a.first_monday, s_term, b.s_term_monday, w_due, c.w_due_monday
FROM #tbl
CROSS APPLY (
SELECT DATEADD(d, (9 - DATEPART(dw, #referenceMonth)) % 7, #referenceMonth) as first_monday
) a
CROSS APPLY
(
SELECT DATEADD(WEEK, s_term - 1, a.first_monday) as s_term_monday
) b
CROSS APPLY (
SELECT DATEADD(WEEK, w_due, b.s_term_monday) as w_due_monday
) c;
The first part (a) just gets the first monday of the reference month.
The second part (b) calculates the Nth monday of the reference month (s_term).
The last part (c) calculates de Nth monday from that s_term monday (w_due)
The result is as follows:
As you specified august/2021 as the reference date, I just hard-coded it as a parameter.
I also split the DATE opperations in multiple CROSS APPLY for ease of read, but you could nest the DATEADD inside one another right in the SELECT. Something like this:
SELECT s_term, w_due, DATEADD(WEEK, w_due,
DATEADD(WEEK, s_term - 1,
DATEADD(d, (9 - DATEPART(dw, #referenceMonth)) % 7, #referenceMonth)))
FROM #tbl
Edit: Explaning that misterious logic of DATEADD(d, (9 - DATEPART(dw, #referenceMonth)) % 7, #referenceMonth)
We take the weekday of the first day of that month: DATEPART(dw, #referenceMonth)
Sunday is 1, Monday is 2 and so on...
Now we find out how many days it would need to get from our reference week day to the desired week day (Monday). We do that with that formulae: (9 - X) % 7
That 9 is the number of days minus our WeekDay that would equal to 7 (the number of days in a week). 7 (days of the week) + 2 (monday) = 9.
If you where to find, say, the first Thursday of the week, that 9 would become 13. 7 (days of the week) + 5 (thursday) = 13.
When we take the modulus of 7 we are looking at the number of week days we are away from our desired week day. Let's run a few simulations:
refenceDate is a Monday (2): 9 - 2 % 7 = 0
nothing to do!
refenceDate is a Sunday (1): 9 - 1 % 7 = 1
we are 1 day away from the next monday
refenceDate is a Thursday (5): 9 - 5 % 7 = 4
we are 4 days away from the next monday
Now we finish by adding that number of days to our reference date: DATEADD(d, y, #referenceMonth)
With that, the first day of the month becomes the first Monday of said month.
maybe ?
CREATE TABLE #temptable(
s_term INTEGER
,w_due INTEGER
);
INSERT INTO #temptable(s_term,w_due) VALUES (2,2) ,(4,2);
WITH Mondays AS (
SELECT ROW_NUMBER () OVER (ORDER BY (SELECT NULL)) RN ,
DATEADD(DAY, n, M_fom) AS Mday
FROM (
SELECT fom, DATEADD(DAY, DATEDIFF(DAY, 0, fom) / 7 * 7, 0)
FROM ( VALUES ( DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0)) ) d ( fom )
) e ( fom, M_fom )
CROSS JOIN ( VALUES ( 7), ( 14), ( 21), ( 28), ( 35),(42),(49),(56),(63) ) f ( n )
)
SELECT m1.Mday AS M1, m2.Mday AS M2
FROM #temptable AS t INNER JOIN
Mondays AS m1 ON t.s_term = m1.RN INNER JOIN
Mondays AS m2 ON t.s_term + t.w_due = m2.RN
I need a simple solution to get 4 weeks for a month based on current date (each week starting from Monday - Friday).
For each week I need to update a table that already has current date and place a counter from Week 1 - 4 and continue to the following month starting from Week 6 - 8. and start from the beginning after week 8.
The query below is returning week number but for 7 days:
can I use something similar just for 5 days?
DECLARE #MyDate DATETIME = '2020-08-03'
--This assumes the weeks starts from Monday - Sunday
DECLARE #WeekNumber INTEGER = (DATEPART(DAY, DATEDIFF(DAY, 0, #MyDate)/7 * 7)/7 +1)
SELECT #WeekNumber
The previous answer was not useful so I got rid of it. This should do what you're looking for
declare #date datetime= '2020-08-03';
select dateadd(d, -4, dt.dt) start_dt,
dt.dt end_dt,
row_number() over (order by v.n) n
from
(select datefromparts(year(#date),month(#date),1) first_dt) fd
cross apply
(select datediff(week, 0, fd.first_dt) wk_diff) wd
cross apply
(values (1),(2),(3),(4),(5),(6)) v(n)
cross apply
(select dateadd(d, -((datepart(weekday, fd.first_dt) + 1 + ##datefirst) % 7), fd.first_dt) calc_dt) calc_dt
cross apply
(select dateadd(d, (v.n-1)*7, calc_dt) dt) dt
where
dt.dt>=fd.first_dt;
Results
start_dt end_dt n
2020-08-03 2020-08-07 1
2020-08-10 2020-08-14 2
2020-08-17 2020-08-21 3
2020-08-24 2020-08-28 4
2020-08-31 2020-09-04 5
I have to get/create date from the user input of week of month (week number in that month - 1st,2nd,3rd,4th and last) and day of week (sunday,monday..) in SQL server.
Examples:
4th Sunday of every month, Last Friday of every month, First Monday etc.
I was able to do it easily in .net but SQL server does seem limited in the date functions.
I am having to use lot of logic to get the date. To calculate the date using the above two parameters I had to use lot of datepart function.
Any suggestions on how to come up with the optimal SQL query for such a function?
I created a function other day for another OP GET Month, Quarter based on Work Week number
This function takes the current year as default it can be further modified to take Year as a parameter too.
an extension to that function can produce the results you are looking for ....
WITH X AS
(
SELECT TOP (CASE WHEN YEAR(GETDATE()) % 4 = 0 THEN 366 ELSE 365 END)-- to handle leap year
DATEADD(DAY
,ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1
, CAST(YEAR(GETDATE()) AS VARCHAR(4)) + '0101' )
DayNumber
From master..spt_values
),DatesData AS(
SELECT DayNumber [Date]
,DATEPART(WEEKDAY,DayNumber) DayOfTheWeek
,DATEDIFF(WEEK,
DATEADD(WEEK,
DATEDIFF(WEEK, 0, DATEADD(MONTH,
DATEDIFF(MONTH, 0, DayNumber), 0)), 0)
, DayNumber- 1) + 1 WeekOfTheMonth
FROM X )
SELECT * FROM DatesData
WHERE DayOfTheWeek = 6 -- A function would expect these two parameters
AND WeekOfTheMonth = 4 -- #DayOfTheWeek and #WeekOfTheMonth
Here is a general formula:
declare #month as datetime --set to the first day of the month you wish to use
declare #week as int --1st, 2nd, 3rd...
declare #day as int --Day of the week (1=sunday, 2=monday...)
--Second monday in August 2015
set #month = '8/1/2015'
set #week = 2
set #day = 2
select dateadd(
day,
((7+#day) - datepart(weekday, #month)) % 7 + 7 * (#week-1),
#month
)
You can also find the last, 2nd to last... etc with this reverse formula:
--Second to last monday in August 2015
set #month = '8/1/2015'
set #week = 2
set #day = 2
select
dateadd(
day,
-((7+datepart(weekday, dateadd(month,1,#month)-1)-#day)) % 7 - 7 * (#week-1),
dateadd(month,1,#month)-1
)
Could you please help me select a date which is the beginning of a particular following month, e.g. April?
For example, if it is Jan 08 2013, it should select April 01 2013, but if it is June 08 2013, it should select April 01 2014.
Thanks.
I would create a calendar table, then you can simply do something like this:
select
min([Date])
from
dbo.Calendar
where
MonthNumber = 4 and
DayNumber = 1 and
[Date] > getdate()
Querying a calendar table is usually clearer, simpler and more flexible than using date functions. You might also want to consider what happens if today is April 1: do you want today's date, or next year's?
If you're interested in April 1 because it's the start of a financial year, you can add that information to your calendar table directly:
select
min([Date])
from
dbo.Calendar
where
IsStartOfFinancialYear = 0x1 and
[Date] > getdate()
Use the DATEADD and the DATEPART function, like this short example:
DECLARE #Date Datetime
SET #Date = '2013.01.08 00:00:00'
SELECT DATEADD(year,
CASE WHEN DATEPART(month, #Date) < 4
THEN 0
ELSE 1 END,
DATEADD(day, -DATEPART(day, #Date) + 1,
DATEADD(month, -DATEPART(month, #Date) + 4, #Date)))
The time is: (m/d/yyyy) => 2009/01/04
Using this command using datepart(wk,'20090104') I can get the week number (for any given date).
So :
SELECT datepart(wk,'20090101') //1
SELECT datepart(wk,'20090102') //1
SELECT datepart(wk,'20090103') //1
SELECT datepart(wk,'20090104') //2
So far so good.
The problem :
Those 3 first dates are not part of a full week, so I can't put them in a fixed 52-week chart.
Our company needs to see information about each whole week in the 52 weeks of a year. (Each year has 52 whole weeks).
So 20090101 doesn't belong to the first week of 2009 !
It belongs to the previous year (which is irrelevant to my question)
So I need a UDF (I've been searching a lot, and ISOWEEK is not answering my needs) which by a given datetime, will give me the Week Number (week = whole week, so partial weeks aren't considered).
Example :
calcweekNum ('20090101') //52 ...from the last year
calcweekNum ('20090102') //52 ...from the last year
calcweekNum ('20090103') //52 ...from the last year
calcweekNum ('20090104') //1
..
..
calcweekNum ('20090110') //1
calcweekNum ('20090111') //2
calcweekNum ('20090112') //2
...
Here's a different approach. All you need to supply is the year:
DECLARE #year INT = 2009;
DECLARE #start SMALLDATETIME;
SET #start = DATEADD(YEAR, #year-1900, 0);
;WITH n AS
(
SELECT TOP (366) -- in case of leap year
d = DATEADD(DAY, ROW_NUMBER() OVER (ORDER BY name)-1, #start)
FROM sys.all_objects
),
x AS
(
SELECT md = MIN(d) FROM n
WHERE DATEPART(WEEKDAY, d) = 1 -- assuming DATEFIRST is Sunday
),
y(d,wk) AS
(
SELECT n.d, ((DATEPART(DAYOFYEAR, n.d) - DATEDIFF(DAY, #start, x.md)-1)/7) + 1
FROM n CROSS JOIN x
WHERE n.d >= x.md
AND n.d < DATEADD(YEAR, 1, #start)
)
SELECT [date] = d, [week] = wk
FROM y WHERE wk < 53
ORDER BY [date];
Results:
date week
---------- ----
2009-01-04 1
2009-01-05 1
2009-01-06 1
2009-01-07 1
2009-01-08 1
2009-01-09 1
2009-01-10 1
2009-01-11 2
2009-01-12 2
...
2009-12-25 51
2009-12-26 51
2009-12-27 52
2009-12-28 52
2009-12-29 52
2009-12-30 52
2009-12-31 52
Note that week 52 won't necessarily be a full week, and that in some cases (e.g. 2012), the last day or two of the year might fall in week 53, so they're excluded.
An alternative approach is to repeat the MIN expression twice:
DECLARE #year INT = 2009;
DECLARE #start SMALLDATETIME;
SET #start = DATEADD(YEAR, #year-1900, 0);
;WITH n AS
(
SELECT TOP (366) -- in case of leap year
d = DATEADD(DAY, ROW_NUMBER() OVER (ORDER BY name)-1, #start)
FROM sys.all_objects
),
y(d,wk) AS
(
SELECT n.d, ((DATEPART(DAYOFYEAR, n.d) - DATEDIFF(DAY, #start, (SELECT MIN(d)
FROM n WHERE DATEPART(WEEKDAY, d) = 1))-1)/7) + 1
FROM n
WHERE n.d >= (SELECT md = MIN(d) FROM n WHERE DATEPART(WEEKDAY, d) = 1)
AND n.d < DATEADD(YEAR, 1, #start)
)
SELECT [date] = d, [week] = wk
FROM y WHERE wk < 53
ORDER BY d;
Here's a function for you to calculate it on the fly:
CREATE FUNCTION dbo.WholeWeekFromDate (
#Date datetime
)
RETURNS tinyint
AS BEGIN
RETURN (
SELECT DateDiff(Day, DateAdd(Year, DateDiff(Year, 0, CalcDate), 0), CalcDate) / 7 + 1
FROM (SELECT DateAdd(Day, (DateDiff(Day, 0, #Date) + 1) / 7 * 7, 0)) X (CalcDate)
);
END;
I don't recommend you use it, as it may perform badly due to being called once for every row. If you absolutely must have a function to use in real queries, then convert it to an inline function returning a single column and row, and use it as so:
SELECT
OtherColumns,
(SELECT WeekNumber FROM dbo.WholeWeekFromDate(DateColumn)) WeekNumber
FROM
YourTable;
This will allow it to be "inlined" in the execution plan and perform significantly better.
But even better, as others have suggested, is to use a BusinessDate table. Here's a head start on creating one for you:
CREATE TABLE dbo.BusinessDate (
BusinessDate date NOT NULL CONSTRAINT PK_BusinessDate PRIMARY KEY CLUSTERED,
WholeWeekYear smallint NOT NULL
CONSTRAINT CK_BusinessDate_WholeWeekYear_Valid
CHECK (WholeWeekYear BETWEEN 1900 AND 9999),
WholeWeekNumber tinyint NOT NULL
CONSTRAINT CK_BusinessDate_WholeWeekNumber_Valid
CHECK (WholeWeekNumber BETWEEN 1 AND 53),
Holiday bit CONSTRAINT DF_BusinessDate_Holiday DEFAULT (0),
Weekend bit CONSTRAINT DF_BusinessDate_Weekend DEFAULT (0),
BusinessDay AS
(Convert(bit, CASE WHEN Holiday = 0 AND Weekend = 0 THEN 1 ELSE 0 END)) PERSISTED
);
And I'll even populate it from 1900-01-01 through 2617-09-22 (Is that enough for the projected life of your product? And it's only 7.8MB so don't fret over size):
WITH A (N) AS (SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1),
B (N) AS (SELECT 1 FROM A F, A A, A L, A C, A O, A N),
C (N) AS (SELECT Row_Number() OVER (ORDER BY (SELECT 1)) FROM B),
Dates AS (
SELECT
N,
DateAdd(Day, N, '18991231') Dte,
DateAdd(Day, N / 7 * 7, '19000101') CalcDate
FROM C
)
INSERT dbo.BusinessDate
SELECT
Dte,
Year(CalcDate),
DateDiff(Day, DateAdd(Year, DateDiff(Year, 0, CalcDate), 0), CalcDate) / 7 + 1,
0,
(N + 6) % 7 / 5 -- calculate weekends
FROM Dates; -- 3-7 seconds or so on my VM server
Then join to the table on the date, and use the WholeWeekNumber column for your output. You might also consider adding a WeekNumberYear because it's going to be a tad difficult to figure out that the 52 of 2009-01-01 really belongs to 2008 without this... a strange data point in there for sure if you don't (laugh).
Example table contents:
BusinessDate WholeWeekYear WholeWeekNumber Holiday Weekend BusinessDay
------------ ------------- --------------- ------- ------- -----------
1/1/2009 2008 52 0 0 1
1/2/2009 2008 52 0 0 1
1/3/2009 2008 52 0 1 0
1/4/2009 2009 1 0 1 0
1/5/2009 2009 1 0 0 1
1/6/2009 2009 1 0 0 1
1/7/2009 2009 1 0 0 1
1/8/2009 2009 1 0 0 1
1/9/2009 2009 1 0 0 1
1/10/2009 2009 1 0 1 0
1/11/2009 2009 2 0 1 0
If you really don't want to use this as a general business date calculation table, you can drop the last 3 columns, otherwise, update the Holiday column to 1 for company holidays.
Note: if you actually make the above table, and your access to it most often uses JOIN or WHERE conditions on a different column than BusinessDate, then make the primary key nonclustered and add a clustered index starting with the alternate column.
Some of the above scripts require SQL 2005 or higher.
It would be relatively easy to setup a custom calendar table with one row for each date of the year in it, and then have other fields that will allow you to rollup however you want. I do this when I have clients using varying calendars, i.e. fiscal years, and it makes the query logic very simple.
Then you just join date-to-date and get the week-number that you want.
date | reporting year | reporting week
-----------|----------------|---------------
2009-01-01 | 2008 | 52
2009-01-02 | 2008 | 52
2009-01-03 | 2008 | 52
2009-01-04 | 2009 | 01
2009-01-05 | 2009 | 01
etc.
and then to use it ( for example to get total sales rollup by your custom weeks, didn't validated my sql):
select reporting_year, reporting_month, sum(sales)
from sales
inner join custom_date_table cdt on cdt.sysdate = sales.sysdate
group by reporting_year, reporting_month
where report_year=2009
DECLARE #StartDate DATE;
SET #StartDate = '20120101';
WITH Calendar AS (
SELECT #StartDate AS DateValue
,DATEPART(DW, #StartDate) AS DayOfWeek
,CASE WHEN DATEPART(DW, #StartDate) = 1 THEN 1 ELSE 0 END AS WeekNumber
UNION ALL
SELECT DATEADD(d, 1, DateValue)
,DATEPART(DW, DATEADD(d, 1, DateValue)) AS DayOfWeek
,CASE WHEN DayOfWeek = 7 THEN WeekNumber + 1 ELSE WeekNumber END
FROM Calendar
WHERE DATEPART(YEAR, DateValue) = DATEPART(YEAR, #StartDate)
)
SELECT DateValue, WeekNumber
FROM Calendar
WHERE WeekNumber BETWEEN 1 AND 52
AND DATEPART(YEAR, DateValue) = DATEPART(YEAR, #StartDate)
OPTION (MAXRECURSION 0);
Don't use a UDF, use a calendar table instead, then you can define week numbers exactly as your company requires and simply query them from the table, which will be much easier and possibly much faster than using a UDF.
A calendar table has numerous uses in SQL (search this site or Google) so you should probably create it anyway.
There is no good answer for this.
A year is NOT 52 weeks long.
It is 52 weeks and one day in normal years, and 52 weeks and two days in leap years.