Related
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.
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.
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
For Example,
I had data something like this :-
batch MIN MAX TIME
X 10 20 2018-07-12 10:29:00.000
X 30 50 2018-07-12 10:30:00.000
X 50 30 2018-07-12 10:31:00.000
| | | |
X 40 20 2018-07-12 11:45:00.000
Now I want hourly data based on start time, For example :-
DURATION MIN
2018-07-12 10:29:00.000-2018-07-12 11:29:00.000 10
2018-07-12 11:30:00.000-2018-07-12 12:30:00.000 10
How can I get this?(Get Min Value For every hour based on Start Time)
dateadd function allows you to add or subtract days,hours, minutes to a date.
Consider The below query
select dateadd(HOUR, -1, getdate()) as time_added,
getdate() as curr_date
The -1 is for subtracting one hour (adding negative one hour)
The result of above query is :
timeadded curr_date
2018-07-12 13:25:31.603 2018-07-12 14:25:31.603
Instead of getdate() use your startdate
In your case it would be
select min from table where time<#starttime and time> dateadd(HOUR, -1, #starttime)
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