Unique Week (Monday - Sunday) Number Regardless Of What Year It Is - sql
I would like to get the number of a week from a [Dates] column, starting with Monday as the first day of the week. I would like each week to be uniquely identified. Rather than rolling over at 52 to 1, to continue counting to week 54, 55, etc.
P.S. I need this for correct sort of [Week Date Range] column in Tableau.
Otherwise, Tableau sorts grouped data this way:
08/08/2022 - 08/14/2022
08/09/2021 - 08/15/2021
08/15/2022 - 08/21/2022
08/16/2021 - 08/22/2021
Code:
CREATE TABLE Dates
(Id INT, Dates DATE);
INSERT INTO Dates
VALUES
(1, '2022-12-19'),
(2, '2022-12-22'),
(3, '2022-12-25'),
(4, '2022-12-26'),
(5, '2022-12-29'),
(6, '2022-12-31'),
(7, '2023-01-01'),
(8, '2023-01-06'),
(9, '2023-01-07'),
(10, '2023-01-09')
SELECT *,
CONCAT(CONVERT(VARCHAR(10), DATEADD(DAY, DATEDIFF(DAY, '19000101', Dates) / 7 * 7, '19000101'), 101), ' - ', CONVERT(VARCHAR(10), DATEADD(DAY, (DATEDIFF(DAY, '19000101', Dates) / 7 + 1) * 7, '18991231'), 101)) AS [Week Date Range (Monday - Sunday)],
DATEPART(ISO_WEEK, Dates) AS [ISO Week Number]
FROM Dates
Id
Dates
Week Date Range (Monday - Sunday)
ISO Week Number
1
2022-12-19
12/19/2022 - 12/25/2022
51
2
2022-12-22
12/19/2022 - 12/25/2022
51
3
2022-12-25
12/19/2022 - 12/25/2022
51
4
2022-12-26
12/26/2022 - 01/01/2023
52
5
2022-12-29
12/26/2022 - 01/01/2023
52
6
2022-12-31
12/26/2022 - 01/01/2023
52
7
2023-01-01
12/26/2022 - 01/01/2023
52
8
2023-01-06
01/02/2023 - 01/08/2023
1
9
2023-01-07
01/02/2023 - 01/08/2023
1
10
2023-01-09
01/09/2023 - 01/15/2023
2
Your problem is not really that you are in need of a running week number. Your problem is that the "Week Date Range" that you have asked Tableau to sort is a string formatted in part as MM/DD/YYYY, which will never sort correctly across years.
The simplest solution is to provide either a true date value such as the calculated week start date or that same date as a string formatted as "yyyy-mm-dd" using CONVERT(VARCHAR(10), WeekStart, 120).
However, if you really want a unique week number, you can use the DATEDIFF(day, ...) / 7 function relative to a "reference Monday" to give you a continuous integer week number. (The "reference Monday" must precede all dates in the data to avoid negative diffs.)
The following includes both:
SET DATEFIRST 1
DECLARE #ReferenceMonday DATE = '1900-01-01' -- This just happens to be a Monday
SELECT
*,
DATENAME(weekday, DATES) AS Weekday,
CONCAT(CONVERT(VARCHAR(10), DATEADD(DAY, DATEDIFF(DAY, '19000101', Dates) / 7 * 7, '19000101'), 101), ' - ', CONVERT(VARCHAR(10), DATEADD(DAY, (DATEDIFF(DAY, '19000101', Dates) / 7 + 1) * 7, '18991231'), 101)) AS [Week Date Range (Monday - Sunday)],
DATEDIFF(day, #ReferenceMonday, Dates) / 7 AS SortableWeek,
CONVERT(VARCHAR(10), DATEADD(DAY, DATEDIFF(DAY, '19000101', Dates) / 7 * 7,'19000101'), 120) AS [SortableWeekStart]
FROM Dates
ORDER BY Id
(I have also added weekday name to the select list as a reference.)
The week start and finish date calculations can also be simplified by combining DATEPART(weekday,...) with DATEADD(day).
DATEADD(day, 1-DATEPART(weekday, D.Dates), D.Dates) AS WeekStart
DATEADD(day, 7-DATEPART(weekday, D.Dates), D.Dates) AS WeekFinish
A technique that may improve readability and reduce duplication of expressions is to use CROSS APPLY to separate calculations from the final select list.
Combining the above could result in a query rewritten as follows:
SET DATEFIRST 1
DECLARE #ReferenceMonday DATE = '1900-01-01' -- This just happens to be a Monday
SELECT
D.*,
DATENAME(weekday, DATES) AS Weekday,
DS.[Week Date Range (Monday - Sunday)],
WK.SortableWeek,
DS.SortableWeekStart
FROM Dates D
CROSS APPLY (
SELECT
DATEADD(day, 1-DATEPART(weekday, D.Dates), D.Dates) AS WeekStart,
DATEADD(day, 7-DATEPART(weekday, D.Dates), D.Dates) AS WeekFinish,
DATEDIFF(day, #ReferenceMonday, Dates) / 7 AS SortableWeek
) WK
CROSS APPLY (
SELECT
CONCAT(CONVERT(VARCHAR(10), WK.WeekStart, 101), ' - ', CONVERT(VARCHAR(10), WK.WeekFinish, 101)) AS [Week Date Range (Monday - Sunday)],
CONVERT(VARCHAR(10), WeekStart, 120) AS SortableWeekStart
) DS
ORDER BY Id
Both of the above queries yield the following results:
Id
Dates
Weekday
Week Date Range (Monday - Sunday)
SortableWeek
SortableWeekStart
1
2022-12-19
Monday
12/19/2022 - 12/25/2022
6416
2022-12-19
2
2022-12-22
Thursday
12/19/2022 - 12/25/2022
6416
2022-12-19
3
2022-12-25
Sunday
12/19/2022 - 12/25/2022
6416
2022-12-19
4
2022-12-26
Monday
12/26/2022 - 01/01/2023
6417
2022-12-26
5
2022-12-29
Thursday
12/26/2022 - 01/01/2023
6417
2022-12-26
6
2022-12-31
Saturday
12/26/2022 - 01/01/2023
6417
2022-12-26
7
2023-01-01
Sunday
12/26/2022 - 01/01/2023
6417
2022-12-26
8
2023-01-06
Friday
01/02/2023 - 01/08/2023
6418
2023-01-02
9
2023-01-07
Saturday
01/02/2023 - 01/08/2023
6418
2023-01-02
10
2023-01-09
Monday
01/09/2023 - 01/15/2023
6419
2023-01-09
See this db<>fiddle for a working example.
I think you're mostly on the right path. Logic as follows
Find the start of the week
With that start of the week, calculate the relevant week number
Convert that week number into a 'grouping' value that is sorted
You've done the first two steps above; to get the third, I get the 'grouping value' for Tableau to be an int value calculated as 100 * YEAR(date) + (week number) - meaning that in your data above, the grouping value will range from 202251 to 202302.
Note that this needs to be done on the 'WeekStart' field, not 'Dates', because 20230101 will return a YEAR of 2023 rather than 2022.
-- Make Monday the first day of week
SET DATEFIRST 1;
-- Calculate 'ISOwkRef' as sort variable
WITH Dates_with_weekstart AS
(SELECT ID,
Dates,
DATEADD(day, -1 * (DATEPART(weekday, dates)-1), Dates) AS WeekStart
FROM dates)
SELECT *,
100*YEAR(Weekstart) + DATEPART(iso_week, WeekStart) AS ISOwkRef
FROM Dates_with_weekstart
ORDER BY ISOwkRef;
db<>fiddle with answer above here.
Results
ID Dates WeekStart ISOwkRef
1 2022-12-19 2022-12-19 202251
2 2022-12-22 2022-12-19 202251
3 2022-12-25 2022-12-19 202251
4 2022-12-26 2022-12-26 202252
5 2022-12-29 2022-12-26 202252
6 2022-12-31 2022-12-26 202252
7 2023-01-01 2022-12-26 202252
8 2023-01-06 2023-01-02 202301
9 2023-01-07 2023-01-02 202301
10 2023-01-09 2023-01-09 202302
Edit: Alternatively, if you prefer to have the weeks numbered from the first week and then increasingly, you can do it similar to above, but
First calculate the week number for the earliest week
Calculate the difference in weeks from that earliest week, and add it to the number
I think it's a bit more convoluted, but here is an approach to do it:
WITH Dates_with_weekstart AS
(SELECT ID,
Dates,
DATEADD(day, -1 * (DATEPART(weekday, dates)-1), Dates) AS WeekStart
FROM dates)
SELECT Dates_with_weekstart.ID,
Dates_with_weekstart.Dates,
Dates_with_weekstart.Weekstart,
DATEPART(ISO_week, First_Weekstart) + DATEDIFF(week, First_Weekstart, Dates_with_weekstart.Weekstart) AS ISOWkNum_rolling
FROM Dates_with_weekstart
CROSS JOIN
(SELECT MIN(WeekStart) AS First_Weekstart
FROM Dates_with_weekstart
) AS FirstWkStart
ORDER BY ISOWkNum_rolling;
Results:
ID Dates Weekstart ISOWkNum_rolling
1 2022-12-19 2022-12-19 51
2 2022-12-22 2022-12-19 51
3 2022-12-25 2022-12-19 51
4 2022-12-26 2022-12-26 52
5 2022-12-29 2022-12-26 52
6 2022-12-31 2022-12-26 52
7 2023-01-01 2022-12-26 52
8 2023-01-06 2023-01-02 53
9 2023-01-07 2023-01-02 53
10 2023-01-09 2023-01-09 54
Fiddle above has been adjusted with this second approach.
Feel free to add the varchar 'Date range' field you have; this answer is just working with the key data.
Related
SUM of production counts for "overnight work shift" in MS SQL (2019)
I need some help regarding sum of production count for overnight shifts. The table just contains a timestamp (that is automaticaly generated by SQL server during INSERT), the number of OK produced pieces and the number of NOT OK produced pieces in that given timestamp. CREATE TABLE [machine1]( [timestamp] [datetime] NOT NULL, [OK] [int] NOT NULL, [NOK] [int] NOT NULL ) ALTER TABLE [machine1] ADD DEFAULT (getdate()) FOR [timestamp] The table holds values like these (just an example, there are hundreds of lines each day and the time stamps are not fixed like each hour or each 30mins): timestamp OK NOK 2022-08-01 05:30:00.000 15 1 2022-08-01 06:30:00.000 18 3 ... ... ... 2022-08-01 21:30:00.000 10 12 2022-08-01 22:30:00.000 0 3 ... ... ... 2022-08-01 23:59:00.000 1 2 2022-08-02 00:01:00.000 7 0 ... ... ... 2022-08-02 05:30:00.000 12 4 2022-08-02 06:30:00.000 9 3 The production works in shifts like so: morning shift: 6:00 -> 14:00 afternoon shift: 14:00 -> 22:00 night shift: 22:00 -> 6:00 the next day I have managed to get sums for the morning and afternoon shifts without issues but I can't figure out how to do the sum for the night shift (I have these SELECTs for each shift stored as a VIEW for easy access). For the morning shift: SELECT CAST(timestamp AS date) AS Morning, SUM(OK) AS SUM_OK, SUM(NOK) AS SUM_NOK FROM [machine1] WHERE DATEPART(hh,timestamp) >= 6 AND DATEPART(hh,timestamp) < 14 GROUP BY CAST(timestamp AS date) ORDER BY Morning ASC For the afternoon shift: SELECT CAST(timestamp AS date) AS Afternoon, SUM(OK) AS SUM_OK, SUM(NOK) AS SUM_NOK FROM [machine1] WHERE DATEPART(hh,timestamp) >= 14 AND DATEPART(hh,timestamp) < 22 GROUP BY CAST(timestamp AS date) ORDER BY Afternoon ASC Since we identify the date of each shift by its start, my idea would be that the result for such SUM of night shift would be Night SUM_OK SUM_NOK 2022-08-01 xxx xxx for interval 2022-08-01 22:00:00.000 -> 2022-08-02 05:59:59.999 2022-08-02 xxx xxx for interval 2022-08-02 22:00:00.000 -> 2022-08-03 05:59:59.999 2022-08-03 xxx xxx for interval 2022-08-03 22:00:00.000 -> 2022-08-04 05:59:59.999 2022-08-04 xxx xxx for interval 2022-08-04 22:00:00.000 -> 2022-08-05 05:59:59.999 ... ... ...
After few days of trial and error I have probably managed to find the needed solution. Using a subquery I shift all the times in range 00:00:00 -> 05:59:59 to the previous day and then I use that result in same approach as for morning and afternon shift (because now all the production data from night shift are in the same date between 22:00:00 and 23:59:59). In case anyone needs it in future: SELECT CAST(nightShift.shiftedTime AS date) AS Night, SUM(nightShift.OK) AS SUM_OK, SUM(nightShift.NOK) AS SUM_NOK FROM (SELECT CASE WHEN (DATEPART(hh, timestamp) < 6 AND DATEPART(hh, timestamp) >= 4) THEN DATEADD(HOUR, -6, timestamp) WHEN (DATEPART(hh, timestamp) < 4 AND DATEPART(hh, timestamp) >= 2) THEN DATEADD(HOUR, -4, timestamp) WHEN (DATEPART(hh, timestamp) < 2 AND DATEPART(hh, timestamp) >= 0) THEN DATEADD(HOUR, -2, timestamp) END AS shiftedTime, [OK], [NOK] FROM [machine1] WHERE (DATEPART(hh, cas) >= 0 AND DATEPART(hh, cas) < 6)) nightShift WHERE DATEPART(hh,nightShift.shiftedTime) >= 22 GROUP BY CAST(nightShift.shiftedTime AS date) ORDER BY Night ASC PS: If there is anything wrong with this approach, please feel free to correct me as I'm just newbie in SQL. So far this seems to do exactly what I needed.
Create Week Date Range column based on the service date
I need to create WeekDateRange column based on the ServiceDate column. Each WeekDateRange value should START with Monday date and END with Sunday date associated with ServiceDate in (mm/dd/yyyy - mm/dd/yyyy) format. SELECT s.ServiceDate FROM ServiceInfo AS s ServiceDate 2022-01-03 2022-01-07 2022-01-15 2022-01-26 2022-01-29 2022-02-01 2022-02-04 2022-02-06 2022-02-07 I tried to use below query, however 2022-02-06 ServiceDate assigns to 02/07/2022 - 02/13/2022 week. SELECT s.ServiceDate, CONCAT(CONVERT(VARCHAR, DATEADD(DAY, 2 - DATEPART(WEEKDAY, s.ServiceDate), CAST(s.ServiceDate AS DATE)), 101), ' - ', CONVERT(VARCHAR, DATEADD(DAY, 8 - DATEPART(WEEKDAY, s.ServiceDate), CAST(s.ServiceDate AS DATE)), 101)) AS WeekDateRange FROM ServiceInfo AS s The output: ServiceDate WeekDateRange 2022-01-03 01/03/2022 - 01/09/2022 2022-01-07 01/03/2022 - 01/09/2022 2022-01-15 01/10/2022 - 01/16/2022 2022-01-26 01/24/2022 - 01/30/2022 2022-01-29 01/24/2022 - 01/30/2022 2022-02-01 01/31/2022 - 02/06/2022 2022-02-04 01/31/2022 - 02/06/2022 2022-02-06 02/07/2022 - 02/13/2022 (Needs to be 01/31/2022 - 02/06/2022) 2022-02-07 02/07/2022 - 02/13/2022
You can use a reference date that is Monday to calculate the difference in days. Perform integer division by 7 and then multiply by 7 will gives you back no of days that starts on Monday 1st Jan, 1900 is a Monday, StartDate : DATEADD(DAY, DATEDIFF(DAY, '19000101', s.ServiceDate) / 7 * 7, '19000101') For End date, just add 1 week to the difference in days and subtract 1 day from the reference date which is 31 Dec 1899 DATEADD(DAY, (DATEDIFF(DAY, '19000101', s.ServiceDate) / 7 + 1) * 7, '18991231') dbfiddle
How to return specific dates based on multiple conditions in SQL?
I have the table below (its test dates, not all dates are included) and I need to return specific dates based on quarterly and fiscal year periods. reportdate periodtype 2021-11-12 wk 2021-11-19 wk 2021-11-30 mth 2021-12-03 wk 2021-12-10 wk 2021-12-31 mth 2022-01-07 wk 2022-01-14 wk 2022-01-28 wk 2022-01-31 mth 2022-02-04 wk I have a condition which I am having difficulty writing in SQL. For the fiscal year I need to return the all the month end report dates plus the latest weekly reportdate. This I am able to to do using this code: reportDate between #YTD_Start_Dt and #report_dt and (periodtype = 'mth' or reportDate =#report_dt) There is a case which occurs the first week of every month (in this case 2022-02-04) where the last monthly report is sometimes not available until the second week. Therefore I need to return all available monthly dates plus the latest weekly plus the last weeks report in the month prior. So for 2022-02-04 I would need to return: --if current day is less than 8 then return: 2021-11-30 2021-12-31 2022-01-28 2022-02-04 --if current day is greater or equal to 8 then return 2021-11-30 2021-12-31 2022-01-31 2022-02-04 I don't know how to write the logic so that it returns the correct combo of reportdates depending on what is available. Any help is appreciated.
How about.. where (reportDate between #YTD_Start_Dt and DATEADD(day, -8, #report_dt) and periodtype = 'mth') or (reportDate between DATEADD(day, -6, #report_dt) and #report_dt and periodtype = 'wk') or (DAY(#report_dt) < 8 AND periodtype = 'wk' and reportDate between DATEADD(d, -6, EOMONTH(#report_dt, -1)) AND EOMONTH(#n, -1)) .. where it's a mth type and the record is between year start and 8 days prior (removes the latest mth if it's the first week of the month after) or wk type in the last week or it's the first week of the month and is a wk report from the ultimate 7 days of last month
Auto pickup dates from SQL Server
I am looking for some T-SQL code that should pick the date which is the "last Sunday in the month of January". For example: Current day expected result 2017-01-29 2016-01-31 2017-02-05 2017-01-29 2017-02-19 2017-01-29 2018-01-28 2017-01-29 2018-02-04 2018-01-28 This is because the year starts from last Sunday in January I have some T-SQL code which is being used in SQL Server 2014: select convert(varchar(10), DATEADD(day, DATEDIFF(day, '19000107', DATEADD(month, DATEDIFF(MONTH, 0, CONVERT(date, CONVERT(VARCHAR(4), GETDATE(), 112) + '0101')), 30)) / 7 * 7, '19000107'), 120) If the current date is between February to December then the above code returns the correct answer. But if we set current date as '2017-01-25' (or any other dates in January), then the result is wrong. For the above date (2017-01-25) answer should be '2016-01-31'
The issue might be that the query is finding the first day of Jan for the year supplied, but this is going to be different if Jan is the supplied month. The change is to determine if Jan is the supplied month, and return last year as the year. select convert(varchar(10), DATEADD(day, DATEDIFF(day, '19000107', DATEADD(month, DATEDIFF(MONTH, 0, CONVERT(date, CONVERT(VARCHAR(4), (CASE WHEN MONTH(GetDate()) = 1 THEN CONVERT(VARCHAR(4), GetDate(), 112) - 1 ELSE CONVERT(VARCHAR(4), GetDate(), 112) END), 112) + '0101')), 30)) / 7 * 7, '19000107'), 120) The CASE statement within the CONVERT(date, CONVERT(VARCHAR(4)...is where the change was made.
A lookup table is the best way to solve this type of problem WITH lookup(d) AS ( VALUES( ('2016-01-31'), ('2017-01-29'), ('2018-01-28') ) select MAX(d) FROM lookup WHERE d <= GETDATE()
The rightmost column of this will calculate the previous last Sunday in January for any date. I've left the other columns in as a simple way to document and experiment with the logic. There is no branching logic, it is all pure date math so it should aggregate reasonably fast and should be able to use indexes on a date column. DECLARE #Date DATE = '20170205'; SELECT SampleDate= #Date , PreviousLastSundayInJanuaryForSampleDate = DATEADD(DAY,(DATEPART(WEEKDAY,EOMONTH(DATEADD(MONTH,(DATEPART(MONTH ,DATEADD(DAY,((DATEPART(DAYOFYEAR,DATEADD(DAY,(DATEPART(WEEKDAY,EOMONTH(DATEADD(MONTH,(DATEPART(MONTH,#Date)-1)*-1,#Date)))-1)*-1,EOMONTH(DATEADD(MONTH,(DATEPART(MONTH,#Date)-1)*-1,#Date)))))-1) * -1 ,#Date))-1)*-1 ,DATEADD(DAY,((DATEPART(DAYOFYEAR,DATEADD(DAY,(DATEPART(WEEKDAY,EOMONTH(DATEADD(MONTH,(DATEPART(MONTH,#Date)-1)*-1,#Date)))-1)*-1,EOMONTH(DATEADD(MONTH,(DATEPART(MONTH,#Date)-1)*-1,#Date)))))-1) * -1 ,#Date))))-1)*-1,EOMONTH(DATEADD(MONTH,(DATEPART(MONTH ,DATEADD(DAY,((DATEPART(DAYOFYEAR,DATEADD(DAY,(DATEPART(WEEKDAY,EOMONTH(DATEADD(MONTH,(DATEPART(MONTH,#Date)-1)*-1,#Date)))-1)*-1,EOMONTH(DATEADD(MONTH,(DATEPART(MONTH,#Date)-1)*-1,#Date)))))-1) * -1 ,#Date))-1)*-1 ,DATEADD(DAY,((DATEPART(DAYOFYEAR,DATEADD(DAY,(DATEPART(WEEKDAY,EOMONTH(DATEADD(MONTH,(DATEPART(MONTH,#Date)-1)*-1,#Date)))-1)*-1,EOMONTH(DATEADD(MONTH,(DATEPART(MONTH,#Date)-1)*-1,#Date)))))-1) * -1 ,#Date)))) To use this against a table, replace #Date with your own date field. Updated with sample results: SampleDate PreviousLastSundayInJanuaryForSampleDate 2008-06-03 2008-01-27 2008-12-20 2008-01-27 2009-07-08 2009-01-25 2010-01-24 2009-01-25 2010-08-12 2010-01-31 2011-02-28 2011-01-30 2011-09-16 2011-01-30 2012-04-03 2012-01-29 2012-10-20 2012-01-29 2013-05-08 2013-01-27 2013-11-24 2013-01-27 2014-06-12 2014-01-26 2014-12-29 2014-01-26 2015-07-17 2015-01-25 2016-02-02 2016-01-31 2016-08-20 2016-01-31 2017-03-08 2017-01-29 2017-09-24 2017-01-29 2018-04-12 2018-01-28 2018-10-29 2018-01-28 2019-05-17 2019-01-27 2019-12-03 2019-01-27 2020-06-20 2020-01-26
SQL Server: date conversion problem
I have a query like this: SELECT 'Last 7 Days' AS Date_Range, CONVERT(smalldatetime, GETDATE()) - 6 AS Begin_Date, CONVERT(smalldatetime, GETDATE()) AS End_Date FROM sys.columns produces output Last 7 Days 2011-03-20 07:35:00 2011-03-26 07:35:00 Last 7 Days 2011-03-20 07:35:00 2011-03-26 07:35:00 How to get this? Last 7 Days 2011-03-20 00:00:00 2011-03-26 00:00:00 Last 7 Days 2011-03-20 00:00:00 2011-03-26 00:00:00
Do a DateAdd operation on the DT value you're getting back. This essentially removes the time component: DateAdd(Day, DateDiff(Day, 0, GetDate()), 0)
You could just use date in the convert function instead of smalldatetime and then append "00:00:00" as string to the result.