How can i create backward scheduling using TSQL. In my old system i have something like this. Is it possible to implement the same logic using TSQL?
Field Name: A-NEST BY
and here is the formula for A-NEST BY. note [A-CUT BY] is a date field
IF(weekday(datesub(A-CUT BY, 7)) = 7, datesub(datesub(A-CUT BY, 7),1),
if(weekday(datesub(A-CUT BY, 7)) = 1, datesub(datesub(A-CUT BY, 7),2),
datesub(A-CUT BY, 7)))
[A-CUT BY] is calculated date field which on the first line here IF(weekday(datesub(A-CUT BY, 7)) = 7, datesub(datesub(A-CUT BY, 7),1), if the date is 7 thge seventh date or Saturday moves the schedule to Friday. if sunday or 1 move the schedule to Monday
weekday(date) or weekday(date,format):
Returns the day of week of a date. The second input is optional, but if given must be one of "number",
"name", or "abbrev". If "number" or omitted, the result will be a number, with 1 for Sunday, 2 for
Monday, up to 7 for Saturday. If "name", the result will be the word Sunday, Monday, etc. If "abbrev",
the result will be the word Sun, Mon, etc.
datesub(date, N)or datesub(date, N,unit):
Subtracts N days from date, that is, returns the date N days before the input date. If N is negative, it
will return the date N days after the input date. The third input is optional; if provided, it must be one of
years, months, days, hours, or minutes. If not provided, days will be assumed.
I am totally guessing here because this question lacks any clarity. I think you just want to use a case expression. Here is an example of what I think you might want.
set datefirst 7 --Sunday
declare #ACUTBY datetime = getdate()
select
case datepart(WEEKDAY, #ACUTBY)
when 1 then 2
when 7 then 5
else datepart(WEEKDAY, #ACUTBY)
end as [A-CUT BY]
--EDIT--
SqlZim made an excellent suggestion to add datefirst to avoid any issues if using another language or the first day of the week is changed for whatever reason.
You can read more about DATEFIRST here. https://learn.microsoft.com/en-us/sql/t-sql/statements/set-datefirst-transact-sql
Related
I'm trying to show the DateTimeRaised field as a week ending Thursday. The FkIssueGroupID isn't unique but each row should have the same date. The MAX is being used to bring back a single row.
Select distinct w.fkIssueGroupID
,[FormDateTimeRaised] =MAX ([DateTimeRaised])
,[WEnd Raised - Thur]= max (CONVERT (date, DATEADD(dd, 5 - (DATEPART(dw, [DateTimeRaised])), [DateTimeRaised]) ))
Whilst the week ending value for Thursday is correct the week days contained within it don't line up i.e.:
Week ending Thursday will have a date of the 21/05/20 (Thursday) but the date values covered by the data are 18/05 to 24/05 (Mon-Sun). I want the date range covered to be 15/05 to 21/05 (Fri-Thur). How do i get the date range to shift from Mon-Sun to Fri-Thur?
You can set the first day of the week to Friday like this:
SET DATEFIRST 5;
I am using the following query to get the difference between two dates. The date ranges are tolling 12 months interval.
CY stand for Current year while PY stands for Previous Year. The dates in Current year are used to calculate the previous year dates
When I execute my query I have the following output, where the month is 11 and day 364. But I want my months to be twelve and the day 365 or (366 for leap year).
DECLARE #CY_StartDate date =CAST(DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-13, 0) AS DATE),
#CY_EndDate date =CAST(DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-1, -1) AS DATE); --- Rolling 12 months
DECLARE #PY_startDate date =DATEADD(YEAR,-1,#CY_StartDate),
#PY_EndDate date =DATEADD(YEAR,-1,#CY_EndDate)
SELECT
#CY_StartDate AS CY_Start,
#CY_EndDate AS CY_End,
#PY_StartDate AS PY_Start,
#PY_EndDate AS PY_End,
DATEDIFF(year, #CY_StartDate, #CY_EndDate) AS yr,
DATEDIFF(month, #CY_StartDate, #CY_EndDate) AS month,
DATEDIFF(day, #CY_StartDate, #CY_EndDate) AS day
Current Output
CY_Start CY_End PY_Start PY_End yr month day
2017-10-01 2018-09-30 2016-10-01 2017-09-30 1 11 364
Expected output
CY_Start CY_End PY_Start PY_End yr month day
2017-10-01 2018-09-30 2016-10-01 2017-09-30 1 12 365
The values you are getting make sense. DATEDIFF counts the ticks between 2 dates, where a tick is the value of the first parameter. So, for example: DATEDIFF(MONTH, '20180101','20180228') will return 1, as only 1 tick has occured (2 - 1 = 1). Seems, here, you simply need to add 1:
DECLARE #CY_StartDate date =CAST(DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-13, 0) AS DATE),
#CY_EndDate date =CAST(DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-1, -1) AS DATE); --- Rolling 12 months
DECLARE #PY_startDate date =DATEADD(YEAR,-1,#CY_StartDate),
#PY_EndDate date =DATEADD(YEAR,-1,#CY_EndDate)
select
#CY_StartDate as CY_Start,
#CY_EndDate AS CY_End,
#PY_StartDate AS PY_Start,
#PY_EndDate AS PY_End,
DATEDIFF(year,#CY_StartDate,DATEADD(DAY,1,#CY_EndDate)) as yr,
DATEDIFF(month,#CY_StartDate,DATEADD(DAY,1,#CY_EndDate)) as month,
DATEDIFF(day,#CY_StartDate,DATEADD(DAY,1,#CY_EndDate)) as day
The reason I used a further DATEADD is because this makes it consistent with every expression. The value of yr was correct, however, for dates like 20170101 and 20171231, the value of yr would be 0. Hence adding 1 day the the value of #CY_EndDate makes this far more reliable, should the dates move.
Common sense. How many numbers are there between 1 and 10 including both? You might say that there are 10 - 1 = 9 which is incorrect. The correct answer is (10 - 1) + 1 = 10.
Likewise, if you have two inclusive dates e.g. 2017-10-01 and 2018-09-30 you add one to DATEDIFF(DAY, '2017-10-01', '2018-09-30') to get 365 instead of 364.
However, as suggested in the other answer, it is much better to the end date exclusive (not counted) which makes date calculations straight forward. In your example, you should add 1 day to the last date so that you have [2017-10-01, 2018-10-01) and DATEDIFF will produce desired results.
I am still learning SQL so please bare that in mind. I have a query that returns me the average days for a specific range, although my range does not account for weekends & holidays. Holidays may be a little tricky but how do I exclude weekends from my range?
For example I have a range 02-01-18 to 02-15-18 where the datediff is 14 days, but how do I get SQL to identifying which days in that range were weekends and if they were to exclude them from my datediff?
My query is
SELECT
AVG(1.00 * DATEDIFF(DAY, xx, yy)) AS DayDiff
FROM
datebase1.dbo.table1
WHERE
MONTH(datecompleted) = MONTH(DATEADD(month, -1, current_timestamp))
AND YEAR(datecompleted) = YEAR(DATEADD(month, -1, current_timestamp))
AND ApprovalRequiredFrom = 'pp'
I do have a calendar I can source which tells me the date and the name of the day, but I want to avoid having to do this. I want to be able to exclude the weekends from my range to get me a more accurate result.
Thanks
To exclude weekends, you need to filter Saturdays and Sundays from both your comparing dates (xx and yy). To do so you use the DATEPART function with the WEEKDAY parameter. The result is a number from 1 to 7 indicating which day of the week it is.
-- 2018-01-01 is a monday
SELECT DATEPART(WEEKDAY, '2018-01-01')
-- Result: 2
The problem is that different database configurations have the date related to the value "1" changed, so maybe the day of week "1" means Sunday for some and Mondays for others. To unify this, you can change the default with SET DATEFIRST.
SET DATEFIRST 1 -- 1: Monday, 7: Sunday
-- 2018-01-01 is a monday
SELECT DATEPART(WEEKDAY, '2018-01-01')
-- Result: 1
Another solution that is datefirst agnostic is to use the ##DATEFIRST session value directly on your expression. The ##DATEFIRST holds the value we set on the SET DATEFIRST statement, or the database default. Please notice that the result is the same, even changing DATEFIRST.
SET DATEFIRST 7 -- 1: Sunday, 2: Monday
-- 2018-01-01 is a monday
SELECT (DATEPART(WEEKDAY, '2018-01-01') + ##DATEFIRST) % 7
-- Result: 2
SET DATEFIRST 1 -- 1: Monday, 7: Sunday
-- 2018-01-01 is a monday
SELECT (DATEPART(WEEKDAY, '2018-01-01') + ##DATEFIRST) % 7
-- Result: 2
For your example, you need to filter xx and yy dates to not be weekends. Add the following to your WHERE clause:
WHERE
--...
AND (DATEPART(WEEKDAY, xx) + ##DATEFIRST) % 7 NOT IN (0, 1)
AND (DATEPART(WEEKDAY, yy) + ##DATEFIRST) % 7 NOT IN (0, 1)
Because need to work with the old MySQL 5.1 server, just got a chance to try a "math" way to calculate no. of SAT / SUN to subtract from:
Note: MySQL's "weekday" function returns 0 for Mon, 5 for SAT and 6 for SUN, thus you see below SQL has some magic no. with 5 and 6.
Sample:
select floor((datediff (ed, st)+1) / 7)*2 /*complete week's weekends*/
+ case when floor((datediff (ed, st) +1) % 7) between 0 and 6 /*additional weekends besides complete weeks*/
then case when weekday(ed) >= weekday(st) then least(floor((datediff (ed, st) +1) % 7), greatest(least(6, weekday(ed)) - greatest(5, weekday(st)) + 1,0))
else least(floor((datediff (ed, st) +1) % 7), greatest(least(6, weekday(ed)+7) - greatest(5, weekday(st)) + 1,0)) end
else 0
end as num_of_sat_and_sun
from (select '2019-01-07' as st, '2019-01-12' as ed) x
Delphi's WeekOfTheYear function uses the ISO 8601 methodology where the week starts on a Monday, and the first week of a year is defined as the first week with four or more days in that year.
To get the week of the year in Microsoft SQL Server, you use the DATEPART(wk, ...) function, but this uses a different mechanism, in that the default for U.S. English starts the week on Sunday, and that the 2nd week of the year is the first Sunday of the year, unless that Sunday is the 1st of January, then the 2nd week starts on the 2nd Sunday of the year, as can be seen if you run this example:
SELECT '2010-1-3', DATEPART(wk, '2010-1-3'), DATENAME(dw, '2010-1-3')
SELECT '2011-1-2', DATEPART(wk, '2011-1-2'), DATENAME(dw, '2011-1-2')
SELECT '2012-1-1', DATEPART(wk, '2012-1-1'), DATENAME(dw, '2012-1-1')
SELECT '2013-1-6', DATEPART(wk, '2013-1-6'), DATENAME(dw, '2013-1-6')
SELECT '2014-1-5', DATEPART(wk, '2014-1-5'), DATENAME(dw, '2014-1-5')
SELECT '2015-1-4', DATEPART(wk, '2015-1-4'), DATENAME(dw, '2015-1-4')
SELECT '2016-1-3', DATEPART(wk, '2016-1-3'), DATENAME(dw, '2016-1-3')
SELECT '2017-1-1', DATEPART(wk, '2017-1-1'), DATENAME(dw, '2017-1-1')
SELECT '2018-1-7', DATEPART(wk, '2018-1-7'), DATENAME(dw, '2018-1-7')
SELECT '2019-1-6', DATEPART(wk, '2019-1-6'), DATENAME(dw, '2019-1-6')
SELECT '2020-1-5', DATEPART(wk, '2020-1-5'), DATENAME(dw, '2020-1-5')
The results from above show every Sunday being the 2nd week of the year, except for the 2 that are the 1st January, which show the 1st week of the year.
I looked at the answer for the question below, that seemed to indicate it would return the week of the year depending on what day the week starts,
Delphi week number function based on system start of week
but that is also based on using the Delphi functions, and does not return the same results as SQL Server.
What SQL Server currently shows for the dates 1st January 2018 to 8th January 2018 is:
1/1/2018 = 1
2/1/2018 = 1
3/1/2018 = 1
4/1/2018 = 1
5/1/2018 = 1
6/1/2018 = 1
7/1/2018 = 2
8/1/2018 = 2
Delphi does have a DayOfWeek function that uses Sunday as the first day of the week, compared to the DayofTheWeek function which uses Monday, but I just can't seem to work out the logic that is needed to get the same results as SQL Server. There has to at least be a condition in there for dealing with Sunday 1st January being week 1, but for the first other Sundays being week 2.
Does anyone have any Delphi source code that returns the week of the year exactly as SQL Server does when using U.S. English default of DATEFIRST 7?
The code is deceptively simple, but the logic of it is a little difficult. The rule about the first Sunday means that, in all cases the first Saturday is in week 1. So the idea is that we go to the next Saturday and count the number of Saturdays from the start of the year.
function SQLWeekOfYear(const pDate: TDate): integer;
var
iYear, iMonth, iDay : word;
iDOW : integer;
iDays : integer;
begin
// this is based on First Sunday in year being in week 2
// unless it is the first of Jan, which is equivalent of saying that the
// first saturday is always in week 1.
//
// This means that we count the number of saturdays prior to this date and add one,
// which is equivalent to finding the next saturday
DecodeDate( pDate, iYear, iMonth, iDay );
iDOW := DayOfWeek( pDate );
// Find how many days so far since 1st jan
iDays := Trunc(pDate - EncodeDate( iYear, 1, 1 )) + 1;
// now adjust for day of week to get to next saturday
iDays := iDays + (7-iDOW);
// add 6 and divide by 7 to find actual number saturdays
Result := (iDays + 6) div 7;
end;
For any given date I need to determine what day of the week it is.
I already know how to get the day of the week with this DATENAME(dw,MyDate.Field)and the number of the day with this DATEPART(dw,MyDate.Field).
Once I've got the day of the week if it is before Wednesday I want to return that Wednesday's date.
If the day of the week is Wednesday or after then I want to return next Wednesdays date.
Monday is day 1 in my system.
Use DATEPART() to determine day of the week.
Use CASE() for the different cases.
Calculate the day delta. Hint: It's either 3-dw or 7+3-dw.
Use DATEADD() to get from the current day (returned by DATEPART) to Wednesday or Wednesday of next week.
the easiest way, without any calculating:
set datefirst 1;
with dates as (
select CAST('20170906' as datetime) d
union all
select DATEADD(day, 1, dates.d)
from dates
where DATEADD(day, 1, dates.d) <= '20170930'
)
select dates.d, DATEADD(day, v.valueToAdd, dates.d) nextWed
from dates
join (values(1,2),(2,1),(3,7),(4,6),(5,5),(6,4),(7,3))v(dayOfWeek,valueToAdd) on v.dayOfWeek = DATEPART(weekday, dates.d)
order by 1