How to calculate the number of months and days between two dates, without returning negative values in SQL Server - sql

I have this SQL block working in most situations but am returning a negative "day" value when the the month/day of the end date is > than the month/day of the start date. Not quite sure how to prevent negative day results from happening other than wrapping this in a case when to catch the exceptions when end month/day is > start month/day.
For example, if the start date is 2017 - 06 -05 and the end date is 2019 - 06 - 04, the output is "24 months and -1 days".
CONVERT ( VARCHAR ( MAX ),
DATEDIFF ( month , [Participant - Birthday] , [Event - Event Date] ) )
+ ' months and ' +
CONVERT ( VARCHAR ( MAX ) ,
DATEDIFF ( day , DATEADD ( month , DATEDIFF ( month , [Participant - Birthday] , [Event - Event Date] ) , [Participant - Birthday] ) , [Event - Event Date] ) )
+ ' days'
Expected out put using the above start and end date should be "23 months and 30 days".

Here is an alternative way of expressing the logic:
select s, e, v.num_months, datediff(day, dateadd(month, num_months, s), e)
from (values (convert(date, '2017-06-05'), convert(date, '2019-06-04')),
(convert(date, '2017-06-05'), convert(date, '2019-06-05')),
(convert(date, '2017-06-05'), convert(date, '2019-06-06'))
) t(s, e) cross apply
(values (case when day(s) <= day(e) then datediff(month, s, e) else datediff(month, s, e) - 1 end)) v(num_months);
This calculates the number of months difference -- up to or before the end date. It then calculates the day difference between the end date and the start date plus that number of months.

Related

Time Difference between Two rows excluding weekend and Time stamp

Date
ID
Value
2022-10-07 17:30:00.000
1
1
2022-10-10 10:00:00.000
2
2
2022-10-12 08:31:42.000
3
1
I want to find date difference from two rows with few conditions in MS SQL
Difference should be from 9am - 6pm (exclude rest of the time)
Also Exclude weekends.
For example, Date Diff from first row will be 1 hr 30 mins.
I am using this query:
DATEDIFF(MINUTE, LAG(date) OVER (ORDER BY date), date)
How can I add more conditions to this?
First things first, let's compute the values we need to handle, namely the current date (that we already have as a field) and the previous date (using the LAG window function, as you did in your attempt).
WITH cte AS (
SELECT [date] AS curDate,
LAG([date]) OVER(ORDER BY date) AS prevDate
FROM tab
)
CASE 1: prevDate's day is equal to curDate's day
In this case we just need to apply the difference in minutes between the two dates as follows:
CASE WHEN CONVERT(DATE, curDate) = CONVERT(DATE, prevDate)
THEN DATEDIFF(MINUTE, prevDate, curDate)
CASE 2: prevDate's day is not equal to curDate's day
Here we need to compute three things:
the difference in time between endtime (18:00:00) and prevDate's time
DATEDIFF(MINUTE, CAST(prevDate AS TIME), '18:00:00')
the difference in time between starttime (9:00:00) and curDate's time
DATEDIFF(MINUTE, '9:00:00', CAST(curDate AS TIME))
the difference in days between curDate and prevDate. For this last one there's the caveat regarding the weekend days though. We can solve this problem by checking if the day of week of curDate is smaller than the day of week of prevDate (if prevDate's working date is Friday and curDate's working date is Monday, we will have dayofweek = 2 for Monday and dayofweek = 6 for Friday, whereas 2-6<0). If the result is smaller than 0, then we need to subtract 3 days of work (multiplied by 9 days of working activity for each day - 18:00:00-9:00:00).
DATEDIFF(MINUTE, '9:00:00', CAST(curDate AS TIME)) +
DATEDIFF(MINUTE, CAST(prevDate AS TIME), '18:00:00') +
9*(DATEDIFF(DAY, prevDate, curDate) -
CASE WHEN DATEPART(WEEKDAY, curDate) - DATEPART(WEEKDAY, prevDate) > 1
THEN 0
ELSE 3 END
)
Hence the Final Query would be the following one:
WITH cte AS (
SELECT [date] AS curDate,
LAG([date]) OVER(ORDER BY date) AS prevDate
FROM tab
)
SELECT curDate,
CASE WHEN CONVERT(DATE, curDate) = CONVERT(DATE, prevDate)
THEN DATEDIFF(MINUTE, prevDate, curDate)
ELSE DATEDIFF(MINUTE, '9:00:00', CAST(curDate AS TIME)) +
DATEDIFF(MINUTE, CAST(prevDate AS TIME), '18:00:00') +
9*(DATEDIFF(DAY, prevDate, curDate) -
CASE WHEN DATEPART(WEEKDAY, curDate) - DATEPART(WEEKDAY, prevDate) > 1
THEN 0
ELSE 3 END)
END
FROM cte
Check the demo here.

SQL query to find dates of last year equivalent to today's date

I am writing a SQL query to find business working dates of last year equivalent to today's date.
In this query it should fetch :-
For e.g. if today is 5th January, 2021 and it is the second day of second week of the year. So I need to find the exact equivalent date of the second day of second week of the previous year. So it would be 7th January, 2020.
And with this, I need the business working dates of that week of 7th January 2020 (i.e. excluding Saturday & Sunday)
Which will come up as 2020-Jan-06 to 2020-Jan-10 according to the example.
So I will need the report between 6th Jan - 10th Jan, 2020.
I am trying to use this code to find date of last year equivalent to today's date (5th Jan, 2021 viz. second day of second week)
select Convert(date, (DATEADD(year, -1, getdate()+2))) ;
2021-01-05 is the 2nd day of the first week of 2021 according to ISO standards.
If you want the 2nd day of the first week of 2021, then it is either today's date minus 52 weeks or 53 weeks. Based on the Wikipedia page for ISO dates:
[53 week years are those] years in which 1 January or 31 December are Thursdays
So, we want that for the previous year. Hence, I think the following should work:
select dateadd(week,
(case when 'Thursday' in (datename(weekday, datefromparts(year(getdate()) - 1, 1, 1)),
datename(weekday, datefromparts(year(getdate()) - 1, 12, 31))
)
then -53 else -52
end),
convert(date, getdate())
)
Note that this returns 2019-12-31, which is the correct value based on ISO standards.
I have use multiple CTE to show you the step by step calculation. It should be pretty easy to follow.
Basically it find the week_no and day_no_of_week for 2021-01-05 and then use that to find the same date for 2020
declare #input_date date = '2021-01-05',
#year_offset int = -1; -- previous year
with
cte1 as
(
select input_date = #input_date,
week_no = DATEPART(WEEK, #input_date),
first_day_of_week = DATEADD(WEEK, DATEDIFF(WEEK, 0, #input_date), 0)
),
cte2 as
(
select *,
day_no_of_week = DATEDIFF(DAY, first_day_of_week, #input_date) + 1
from cte1
),
cte3 as
(
select *,
first_day_of_the_prev_year = DATEADD(YEAR, DATEDIFF(YEAR, 0, #input_date) + #year_offset, 0)
from cte2
),
cte4 as
(
select *,
first_day_of_week_prev_year = DATEADD(WEEK, DATEDIFF(WEEK, 0, DATEADD(WEEK, week_no - 1, first_day_of_the_prev_year)), 0)
from cte3
)
select *,
DATEADD(DAY, day_no_of_week - 1, first_day_of_week_prev_year) as the_required_date
from cte4

How can I select the past seven days and its corresponding week in the past year

How can I select
The past week
Its corresponding days in the year before
This is needed for a dashboard, I would like to show a chart with results from the past seven days. It displays green if our call-center handles 98% of their phone calls within a certain time-span, red if we go over 98%. As a reference I would like to create a chart below with the corresponding seven days in the year before. This is challenging, because weekdays really influence the workload. That means I can't compare a Tuesday with a Sunday or Monday.
For instance, today is Saturday 21st Dec 2019, I would like to report the following timespans:
2019-12-13 00:00:00 -> 2019-12-20 23:59:59
and
2018-12-14 00:00:00 -> 2018-12-21 23:59:59
I made the following code (used within a select statement):
case when cs.ReachedAt between (getdate() - 7) and getdate() then 1 else 0 end as Is_PastWeek
case when cs.ReachedAt between (convert(datetime, convert(varchar(50), convert(date, dateadd(d, -1, dateadd(wk, -52, getdate())))) + ' 23:59:59')) and (convert(datetime, convert(varchar(50), convert(date, dateadd(d, -8, dateadd(wk, -52, getdate())))) + ' 00:00:00')) then 1 else 0 end as Is_SameWeekLastYear
It works, but isn't perfect. I just select the corresponding weekday in the same week as 52 weeks ago. Which means I sometimes end up selecting a matching weekday, but not the nearest. How can I do this better?
EDIT
To clarify what I mean by "picking the nearest corresponding weekday in the year before", i made the following example:
with cte1 as (
select row_number() over (order by (select 1)) - 1 as incrementor
from master.sys.columns sc1
cross join master.sys.columns sc2
), cte2 as (
select dateadd(day, cte1.incrementor, '2000-01-01') as generated_date
from cte1
where dateadd(day, cte1.incrementor, '2000-01-01') < getdate()
), cte3 as (
select convert(date, generated_date) as generated_date
, convert(date, getdate()) as now_date
from cte2
), cte4 as (
select *
, convert(date, dateadd(YEAR, -1, now_date)) as year_back
from cte3
)
select now_date
, generated_date
from cte4
where 1=1
and datepart(week, year_back) = datepart(week, generated_date)
and datepart(DW, year_back) = datepart(DW, generated_date)
This will result in:
For the grey values, I would rather take the weekday of one week later. That way I pick "the nearest corresponding weekday in the year before".
Please note that the above is an example to show what I mean, my ultimate goal is to start with this date, select the whole week before... And all (if possible) neatly within a where clause.
The expression datepart(week, getdate()) will deliver you the calendar week. With this, you can go further.
This is too long for a comment.
What difference does it make? If you are looking for the past week, just look at the same 7 days from the previous year. In one case the week might start on a Tuesday and in the other on a Wednesday. But in both cases, each weekday occurs once.
The logic would be:
where cs.ReachedAt >= datefromparts(year(getdate() - 7) - 1, month(getdate() - 7), day(getdate() - 7) and
cs.ReachedAt < datefromparts(year(getdate()), month(getdate()), day(getdate()))
The logic for the current year:
where cs.ReachedAt >= convert(date, getdate() - 7) and
cs.ReachedAt < convert(date, getdate())

Select time difference in minutes for this months portion only

Cant figure out what im doing wrong:
I have a table that has a start time and end time column and im trying to figure out the sum of time in minutes for this months portion only.
Eg.
Start date: 27-02-13
End Date: 03-03-13
Over all minutes= 5760
Minutes for March only: 4320
This is my query but its not working properly, i tried to add a where clause to look back the last two months for pervious time entrys. What am I doing wrong?
SELECT
isnull((Sum(DATEDIFF(minute,
CASE when datetime5 < dateadd(mm,datediff(mm,0,GetDate()), 0)
THEN dateadd(mm,datediff(mm,0,GetDate()), 0)
ELSE datetime5 END,
CASE when datetime6 > dateadd(mm,datediff(mm,0,GetDate())+1, 0)
THEN dateadd(mm,datediff(mm,0,GetDate())+1, 0)
ELSE datetime6 END))/60.0),0 )
as Minutes , 0
FROM AllUserData
WHERE tp_ListID in (select tp_ID from Lists where tp_Title = 'D1 Downtime')
and datetime5 >=dateadd(mm,datediff(mm,0,GetDate())-2, 0);
See this SQL Fiddle.
with month_start_finish (month_start, month_finish) as
(
-- here you get the beginning of the current month and the next month
-- needed for filters and calculations
select
dateadd(month, datediff(month, 0, getdate()), 0),
dateadd(month, datediff(month, 0, getdate()) + 1, 0)
)
select start_date, finish_date,
-- here you calculate your minutes
datediff(minute,
case when start_date > month_start then start_date else month_start end,
case when finish_date < month_finish then finish_date else month_finish end
) as minutes_in_this_month
from test, month_start_finish
where start_date < month_finish -- here you remove any periods that
and finish_date > month_start -- are not related to the current month
Note how it works correctly even if the the period is longer than a month.
Select all records that end after the beginning of this month.
Then, the portion of each record that is in this month can be obtained with something like this (pseudo-code):
end - max(start,start_of_this_month)
Take the start of the period or the start of this month, whichever is later.
This should help you simplify your query, I think. Here is the basic idea (pseudo-code again, as I don't know the nuances of SQL Server date operations).
select sum(elapsed_time) from (
select end - max(start,start_of_this_month) as elapsed_time
from your table
where end > start_of_this_month
) time_periods

How to calculate if workdays are more than 6 days from todays date

I have the following in my select statement:
CASE WHEN DATEADD(DAY, - 5, GETDATE()) > [Date] THEN '6+' ELSE NULL END AS Test
This works fine, but it also includes weekends. How would I go about not counting weekends in the part that is calculating if todays date - 5 is > date? I want to use only 5 working days, not weekends.
This will exclude Saturday and Sunday from your CASE:
CASE WHEN
(
(DATEADD(DAY, - 5, GETDATE()) > [Date])
AND
(DATEPART(WEEKDAY, DATEADD(DAY, - 5, GETDATE())) NOT IN (7,1))
)
THEN '6+' END AS Test
I suggest to calculate the workdays from your [Date] to GETDATE() and apply whatever criteria to that number in your CASE expression.
To calculate the workdays, for simplicity of expressions, you can add these 2 cross-applies to your FROM clause:
CROSS APPLY ( VALUES (
DATEDIFF(Day, 0, [Date]),
DATEDIFF(Day, 0, GETDATE())
)
) tmp (d1, d2)
CROSS APPLY ( VALUES (
tmp.d2 - tmp.d1 - (tmp.d2/7 - tmp.d1/7) - ((tmp.d2+1)/7 - (tmp.d1+1)/7)
)
) diff (workdays)
Having done so, your case expression would look like this:
CASE WHEN diff.workdays > 5 THEN '6+' ELSE NULL END AS Test