I have a problem getting difference between two dates in years, months, days without using function, just the Select statement.
So far i have this messy code, but it doesnt work so well, as sometimes the month/day is - . tblProject.BillingDate is StartDate, tblServiceTicket.ServiceTicketDate is EndDate.
CONVERT(varchar(12),datediff(YEAR,tblProject.BillingDate,tblServiceTicket.ServiceTicketDate)) + ' year, '
+ CONVERT(varchar(12),DATEDIFF(MM, DATEADD(YY, datediff(YEAR,tblServiceTicket.ServiceTicketDate,tblProject.BillingDate), tblServiceTicket.ServiceTicketDate), tblProject.BillingDate)) + ' months, '
+ CONVERT(varchar(12),DATEDIFF(DD, DATEADD(MM, DATEDIFF(MM, DATEADD(YY, (datediff(YEAR,tblProject.BillingDate,tblServiceTicket.ServiceTicketDate)), tblProject.BillingDate), tblServiceTicket.ServiceTicketDate), DATEADD(YEAR, datediff(YEAR,tblProject.BillingDate,tblServiceTicket.ServiceTicketDate) , tblProject.BillingDate)), tblServiceTicket.ServiceTicketDate)) + ' days '
The only way that I know to do this is semi-iteratively, like this. This version can be improved with coding around DATEDIFF, but it shows the logic needed more clearly than the DATEDIFF version.
DECLARE #dstart datetime,
#dend datetime,
#dwork datetime
DECLARE #yy int,
#mm int,
#dd int
SET #dstart = '19570125'
SET #dend = '20161214'
DECLARE #ix int
-- Get Year interval
SET #ix = 0
WHILE #ix >= 0
BEGIN
Set #ix = #ix + 1
IF Dateadd(year, #ix, #dstart) > #dend
BEGIN
SET #yy = #ix - 1
SET #ix = -1
END
END
Set #dwork = Dateadd(year, #yy, #dstart)
-- Get month interval
SET #ix = 0
WHILE #ix >= 0
BEGIN
Set #ix = #ix + 1
IF Dateadd(MONTH, #ix, #dwork) > #dend
BEGIN
SET #mm = #ix - 1
SET #ix = -1
END
END
Set #dd = DATEDIFF(day, dateadd(month, #mm, #dwork), #dend)
SELECT 'The difference is ' + Cast(#yy as varchar) + ' years, ' + Cast(#mm as varchar) + ' Months, and ' + Cast(#dd as varchar) + ' Days'
Here are some sample outputs, showing how it handles a couple of problem cases.
-- One day at new years
SET #dstart = '20161231'
SET #dend = '20170101'
-- The difference is 0 years, 0 Months, and 1 Days
-- One month to a shorter month
SET #dstart = '20160131'
SET #dend = '20170228'
-- The difference is 1 years, 1 Months, and 0 Days
-- My age
SET #dstart = '19570125'
SET #dend = '20161214'
-- The difference is 59 years, 10 Months, and 19 Days
Related
I have this query for expiry date calculation and its work fine but result show in - eg:-9 year 4 month and 5 day.
I want to show that in normal way like "Expire in 9 years 4 months and 5 day":
DECLARE #TempDate Datetime ,
#ExpiryDate Datetime,
#year int,
#month int,
#day int
SET #ExpiryDate = (SELECT TOP (1) [ExpiryDate] FROM [dbo].[Purchases] WHERE [ProductId] = 1)
SELECT #TempDate = #ExpiryDate
SELECT
#year = DATEDIFF(YEAR, #TempDate, GETDATE()) -
CASE
WHEN (MONTH(#ExpiryDate) > MONTH(GETDATE())) OR
(MONTH(#ExpiryDate) = MONTH(GETDATE()) AND DAY(#ExpiryDate) > DAY(GETDATE()))
THEN 1 ELSE 0
END
SELECT #TempDate = DATEADD(YEAR, #year, #TempDate)
SELECT #month = DATEDIFF(MONTH, #TempDate, GETDATE()) -
CASE
WHEN DAY(#ExpiryDate) > DAY(GETDATE())
THEN 1 ELSE 0
END
SELECT #TempDate = DATEADD(MONTH, #month, #TempDate)
SELECT #day = DATEDIFF(DAY, #TempDate, GETDATE())
SELECT #year AS Years, #month AS Months, #day AS [Days]
If I understand your question correctly, the calculation is working as you expect but you want the Years value to be returned as a positive rather than negative number. If this is the case, you should change the final SELECT to:
SELECT (#year * -1) AS Years, #month AS Months, #day AS [Days];
Alternatively if you want to return the output as a string (i.e. Expire in 9 years 4 months and 5 day), change the final SELECT to:
SELECT 'Expire in ' + CAST((#year * -1) AS VARCHAR(2)) + ' years '
+ CAST(#month AS VARCHAR(2)) + ' months and '
+ CAST(#day AS VARCHAR(2)) + ' day';
Casting as VARCHAR(2) assumes that you expect no more than 99 years, but you may want to increase that number.
After reading on this topic and being advised to use DateDiff. I wrote a function that doesn't provide the answer I want. The client wants to now how long it took to complete a checklist. I have a CreationDate and CompletionDate. I need to know how many years, months, weeks and days it took. If it is 2 days then '2 days' without the years. The function deducts the number of years and then attempt to check the number of months, then the number of weeks and then the number of days. Only results are given if available. It seems DateDiff is the problem... or I am the problem not understanding DateDiff. It even returns a week for a 4 day difference in dates which doesn't make sense. It should return the number of weeks within the two dates, not caring when it starts.
This is the code
ALTER FUNCTION [dbo].[DateRangeText]
(#FromDate DATETIME, #ToDate DATETIME)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #Result AS VARCHAR(MAX);
SET #Result = '';
DECLARE #TmpS AS VARCHAR(MAX);
SET #TmpS = '';
DECLARE #Years AS INT;
SET #Years = DATEDIFF(year, #FromDate, #ToDate);
IF (#Years > 0)
BEGIN
IF (#Years = 1)
SET #TmpS = ' Year ';
ELSE
SET #TmpS = ' Years ';
SET #Result = #Result + CAST(#Years AS VARCHAR) + #TmpS;
SET #ToDate = DATEADD(YEAR, -1 * #Years, #ToDate);
END;
DECLARE #Months AS INT;
SET #Months = DATEDIFF(month, #FromDate, #ToDate);
IF (#Months > 0)
BEGIN
IF (#Months = 1)
SET #TmpS = ' Month ';
ELSE
SET #TmpS = ' Months ';
SET #Result = #Result + CAST(#Months AS VARCHAR) + #TmpS;
SET #ToDate = DATEADD(MONTH, -1 * #Months, #ToDate);
END;
DECLARE #Weeks AS INT;
SET #Weeks = DATEDIFF(week, #FromDate, #ToDate);
IF (#Weeks > 0)
BEGIN
IF (#Weeks = 1)
SET #TmpS = ' Week ';
ELSE
SET #TmpS = ' Weeks ';
SET #Result = #Result + CAST(#Weeks AS VARCHAR) + #TmpS;
SET #ToDate = DATEADD(WEEK, -1 * #Weeks, #ToDate);
END;
DECLARE #Days AS INT;
SET #Days = DATEDIFF(day, #FromDate, #ToDate);
IF (#Days > 0)
BEGIN
IF (#Days = 1)
SET #TmpS = ' Day ';
ELSE
SET #TmpS = ' Days ';
SET #Result = #Result + CAST(#Days AS VARCHAR) + #TmpS;
SET #ToDate = DATEADD(WEEK, -1 * #Days, #ToDate);
END;
IF (#Result = '')
SET #Result = 'Same day';
RETURN Rtrim(COALESCE(#Result,''));
END;
Since you are using a function, consider the following Table-Valued Function. Easy to use a stand-alone or included as a CROSS APPLY.
Performant and Accurate without having to worry about all the misc date calculations.
Example
Select * from [dbo].[tvf-Date-Elapsed] ('1991-09-12 21:00:00.000',GetDate())
Returns
Years Months Days Hours Minutes Seconds
26 7 5 13 47 11
The TVF if interested
CREATE FUNCTION [dbo].[tvf-Date-Elapsed] (#D1 DateTime,#D2 DateTime)
Returns Table
Return (
with cteBN(N) as (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cteRN(R) as (Select Row_Number() Over (Order By (Select NULL))-1 From cteBN a,cteBN b,cteBN c),
cteYY(N,D) as (Select Max(R),Max(DateAdd(YY,R,#D1))From cteRN R Where DateAdd(YY,R,#D1)<=#D2),
cteMM(N,D) as (Select Max(R),Max(DateAdd(MM,R,D)) From (Select Top 12 R From cteRN Order By 1) R, cteYY P Where DateAdd(MM,R,D)<=#D2),
cteDD(N,D) as (Select Max(R),Max(DateAdd(DD,R,D)) From (Select Top 31 R From cteRN Order By 1) R, cteMM P Where DateAdd(DD,R,D)<=#D2),
cteHH(N,D) as (Select Max(R),Max(DateAdd(HH,R,D)) From (Select Top 24 R From cteRN Order By 1) R, cteDD P Where DateAdd(HH,R,D)<=#D2),
cteMI(N,D) as (Select Max(R),Max(DateAdd(MI,R,D)) From (Select Top 60 R From cteRN Order By 1) R, cteHH P Where DateAdd(MI,R,D)<=#D2),
cteSS(N,D) as (Select Max(R),Max(DateAdd(SS,R,D)) From (Select Top 60 R From cteRN Order By 1) R, cteMI P Where DateAdd(SS,R,D)<=#D2)
Select [Years] = cteYY.N
,[Months] = cteMM.N
,[Days] = cteDD.N
,[Hours] = cteHH.N
,[Minutes] = cteMI.N
,[Seconds] = cteSS.N
--,[Elapsed] = Format(cteYY.N,'0000')+':'+Format(cteMM.N,'00')+':'+Format(cteDD.N,'00')+' '+Format(cteHH.N,'00')+':'+Format(cteMI.N,'00')+':'+Format(cteSS.N,'00')
From cteYY,cteMM,cteDD,cteHH,cteMI,cteSS
)
--Max 1000 years
--Select * from [dbo].[tvf-Date-Elapsed] ('1991-09-12 21:00:00.000',GetDate())
--Select * from [dbo].[tvf-Date-Elapsed] ('2017-01-01 20:30:15','2018-02-05 22:58:35')
I'll leave weeks and leap years to you to figure out unless I have time to come back to this later. However this will get you the years, months, and days you are looking for:
DECLARE #FromDate DateTime
DECLARE #ToDate DateTime
DECLARE #years INT
DECLARE #months INT
DECLARE #days INT
-- just some sample dates for testing
Set #FromDate = '01-01-2014'
Set #ToDate = GetDate()
SET #years = DATEDIFF(mm, #FromDate, #ToDate)/12
SET #months = DATEDIFF(mm, #FromDate, #ToDate)%12 - 1
SET #days = ABS(DATEDIFF(dd, DATEADD(mm,#months , DATEADD(yy, #years, #FromDate)), #ToDate))
DECLARE #YearsStr VarChar(20)
DECLARE #MonthsStr VarChar(20)
DECLARE #DaysStr VarChar(20)
SET #YearsStr = Case When #years > 0 Then Convert(varchar(10),#years) + ' Years, ' Else '' End
SET #MonthsStr = Case When #months > 0 Then Convert(varchar(10),#months) + ' Months, ' Else '' End
SET #DaysStr = Convert(varchar(10),#days) + ' Days '
SELECT #YearsStr + #MonthsStr + #DaysStr
I need an SQL function to calculate age. It has to be accurate and cover all corner cases.
It is for hospital ward for babies, so age of 30 minuets is a common case.
I have a looked on other answers but could not find one that deals with all cases.
For example:
Baby born in 2014-04-29 12:59:00.000.
And now is 2014-04-29 13:10:23.000,
Age should be 0 Years, 0 Months, 0 Days, 0 Hours, 11 minutes
It would be great if someone can provide the ultimate and definitive version of that function.
(I am afraid that simple solutions with DateDiff are not good enough. As stated in a more popular question : "The Datediff function doesn't handle year boundaries well ..."
This query will give you date diff in minutes,
select datediff(mi, '2014-04-23 05:23:59.660',getdate())
Then you can simply calc the minutes/60 for hours and minutes mod 60 for minutes
select datediff(mi, '2014-04-23 05:23:59.660',getdate())/60 as [Hours], select datediff(mi, '2014-04-23 05:23:59.660',getdate()) % 60 as [Minutes]
We can use DATEDIFF to get the Year, Month, and Day differences, and then simple division for the Seconds, Minutes, and Hours differences.
I've used #CurrentDate to recreate the original request, but #CurrentDate = GETDATE() will return the age at time of execution.
DECLARE #BirthDate DATETIME
DECLARE #CurrentDate DATETIME
SET #BirthDate = '2014-04-29 12:59:00.000'
SET #CurrentDate = '2014-04-29 13:10:23.000'
DECLARE #DiffInYears INT
DECLARE #DiffInMonths INT
DECLARE #DiffInDays INT
DECLARE #DiffInHours INT
DECLARE #DiffInMinutes INT
DECLARE #DiffInSeconds INT
DECLARE #TotalSeconds BIGINT
-- Determine Year, Month, and Day differences
SET #DiffInYears = DATEDIFF(year, #BirthDate, #CurrentDate)
IF #DiffInYears > 0
SET #BirthDate = DATEADD(year, #DiffInYears, #BirthDate)
IF #BirthDate > #CurrentDate
BEGIN
-- Adjust for pushing #BirthDate into future
SET #DiffInYears = #DiffInYears - 1
SET #BirthDate = DATEADD(year, -1, #BirthDate)
END
SET #DiffInMonths = DATEDIFF(month, #BirthDate, #CurrentDate)
IF #DiffInMonths > 0
SET #BirthDate = DATEADD(month, #DiffInMonths, #BirthDate)
IF #BirthDate > #CurrentDate
BEGIN
-- Adjust for pushing #BirthDate into future
SET #DiffInMonths = #DiffInMonths - 1
SET #BirthDate = DATEADD(month, -1, #BirthDate)
END
SET #DiffInDays = DATEDIFF(day, #BirthDate, #CurrentDate)
IF #DiffInDays > 0
SET #BirthDate = DATEADD(day, #DiffInDays, #BirthDate)
IF #BirthDate > #CurrentDate
BEGIN
-- Adjust for pushing #BirthDate into future
SET #DiffInDays = #DiffInDays - 1
SET #BirthDate = DATEADD(day, -1, #BirthDate)
END
-- Get number of seconds difference for Hour, Minute, Second differences
SET #TotalSeconds = DATEDIFF(second, #BirthDate, #CurrentDate)
-- Determine Seconds, Minutes, Hours differences
SET #DiffInSeconds = #TotalSeconds % 60
SET #TotalSeconds = #TotalSeconds / 60
SET #DiffInMinutes = #TotalSeconds % 60
SET #TotalSeconds = #TotalSeconds / 60
SET #DiffInHours = #TotalSeconds
-- Display results
SELECT #DiffInYears AS YearsDiff,
#DiffInMonths AS MonthsDiff,
#DiffInDays AS DaysDiff,
#DiffInHours AS HoursDiff,
#DiffInMinutes AS MinutesDiff,
#DiffInSeconds AS SecondsDiff
It will give you date diff in seconds select datediff(s, '2014-04-23 05:23:59.660',getdate())
I have a table that has the following column (with some sample entries provided):
COLUMN NAME: pt_age_old
20 Years 8 Months 3 Weeks
1 Year 3 Months 2 Weeks
58 Years 7 Months
1 Year
7 Years 11 Months 2 Weeks
26 Years 6 Months
56 Years 6 Months
48 Years 6 Months 4 Weeks
29 Years 11 Months
4 Years 3 Months
61 Years 8 Months 4 Weeks
I have tried to cast it to datetime, of course this did not work. Same with convert.
Keep getting the following message:
Msg 241, Level 16, State 1, Line 2
Conversion failed when converting date and/or time from character string.
Main 2 questions are:
Is this type of conversion even possible with this existing string format?
If so, can you steer me in the right direction so I can make this happen?
Thanks!
This could be done using custom code such as below - I've assumed the values you're using are people's ages, and you're trying to work out their date of birth given their age today.
You can see the below code in action here: http://sqlfiddle.com/#!3/c757c/2
create function dbo.AgeToDOB(#age nvarchar(32))
returns datetime
as
begin
declare #pointer int = 0
, #pointerPrev int = 1
, #y nvarchar(6)
, #m nvarchar(6)
, #w nvarchar(6)
, #d nvarchar(6)
, #result datetime = getutcdate() --set this to the date we're working to/from
--convert various ways of expressing units to a standard
set #age = REPLACE(#age,'Years','Y')
set #age = REPLACE(#age,'Year','Y')
set #age = REPLACE(#age,'Months','M')
set #age = REPLACE(#age,'Month','M')
set #age = REPLACE(#age,'Weeks','W')
set #age = REPLACE(#age,'Week','W')
set #age = REPLACE(#age,'Days','D')
set #age = REPLACE(#age,'Day','D')
set #pointer = isnull(nullif(CHARINDEX('Y',#age),0),#pointer)
set #y = case when #pointer > #pointerprev then SUBSTRING(#age,#pointerprev,#pointer - #pointerprev) else null end
set #pointerPrev = #pointer + 1
set #pointer = isnull(nullif(CHARINDEX('M',#age),0),#pointer)
set #m = case when #pointer > #pointerprev then SUBSTRING(#age,#pointerprev,#pointer - #pointerprev) else null end
set #pointerPrev = #pointer + 1
set #pointer = isnull(nullif(CHARINDEX('W',#age),0),#pointer)
set #w = case when #pointer > #pointerprev then SUBSTRING(#age,#pointerprev,#pointer - #pointerprev) else null end
set #pointerPrev = #pointer + 1
set #pointer = isnull(nullif(CHARINDEX('D',#age),0),#pointer)
set #d = case when #pointer > #pointerprev then SUBSTRING(#age,#pointerprev,#pointer - #pointerprev) else null end
set #result = dateadd(YEAR, 0 - isnull(cast(#y as int),0), #result)
set #result = dateadd(MONTH, 0 - isnull(cast(#m as int),0), #result)
set #result = dateadd(Week, 0 - isnull(cast(#w as int),0), #result)
set #result = dateadd(Day, 0 - isnull(cast(#d as int),0), #result)
return #result
end
go
select dbo.AgeToDOB( '20 Years 8 Months 3 Weeks')
NB: there's a lot of scope for optimisation here; I've left it simple above to help keep it clear what's going on.
Technically it is possible to convert the relative time label into a datetime, but you will need a reference date though (20 Years 8 Months 3 Weeks as of '2013-10-16'). The reference date is most likely today (using GETDATE() or CURRENT_TIMESTAMP), but there is a chance it is a different date. You will have to parse the label, convert it to a duration, and then apply the duration to the reference time. This will be inherently slow.
There are at least two possible ways of doing this, write a FUNCTION to parse and convert the relative time label or use a .NET extended function to do the conversion. You would need to identify all of the possible labels otherwise the conversion will fail. Keep in mind that the reference date is important since 2 months is not a constant number of days (Jan-Feb = either 58 or 59 days).
Here is a sample of what the function might look like:
-- Test data
DECLARE #test varchar(50)
, #ref_date datetime
SET #test = '20 Years 8 Months 3 Weeks'
SET #ref_date = '2013-10-16' -- or use GETDATE() / CURRENT_TIMESTAMP
-- Logic in function
DECLARE #pos int
, #ii int
, #val int
, #label varchar(50)
, #result datetime
SET #pos = 0
SET #result = #ref_date
WHILE (#pos <= LEN(#test))
BEGIN
-- Parse the value first
SET #ii = NULLIF(CHARINDEX(' ', #test, #pos), 0)
SET #label = RTRIM(LTRIM(SUBSTRING(#test, #pos, #ii - #pos)))
SET #val = CAST(#label AS int)
SET #pos = #ii + 1
-- Parse the label next
SET #ii = NULLIF(CHARINDEX(' ', #test, #pos), 0)
IF (#ii IS NULL) SET #ii = LEN(#test) + 1
SET #label = RTRIM(LTRIM(SUBSTRING(#test, #pos, #ii - #pos)))
SET #pos = #ii + 1
-- Apply the value and offset
IF (#label = 'Days') OR (#label = 'Day')
SET #result = DATEADD(dd, -#val, #result)
ELSE IF (#label = 'Weeks') OR (#label = 'Week')
SET #result = DATEADD(ww, -#val, #result)
ELSE IF (#label = 'Months') OR (#label = 'Month')
SET #result = DATEADD(mm, -#val, #result)
ELSE IF (#label = 'Years') OR (#label = 'Year')
SET #result = DATEADD(yy, -#val, #result)
END
SELECT #result
-- So if this works correctly,
-- 20 Years 8 Months 3 Weeks from 2013-10-16 == 1993-01-26
Make a function to split it up:
create function f_tst
(
#t varchar(200)
) returns date
begin
declare #a date = current_timestamp
;with split1 as
(
select 1 start, charindex(' ', #t + ' ', 4)+1 stop
where #t like '% %'
union all
select stop, charindex(' ', #t + ' ', stop + 4)+1
from split1
where charindex(' ', #t, stop) > 0
), split2 as
(
select cast(left(x.sub, charindex(' ', x.sub)) as int) number,
substring(x.sub, charindex(' ', x.sub) + 1, 1) unit
from split1
cross apply (select substring(#t, start, stop - start) sub) x
)
select #a = case unit when 'W' then dateadd(ww, -number, #a)
when 'Y' then dateadd(yy, -number, #a)
when 'M' then dateadd(mm, -number, #a)
end
from split2
return #a
end
Test function:
select dbo.f_tst(age)
from (values('20 Years 8 Months 3 Weeks'),('1 Month') ) x(age)
Result:
1993-01-27
2013-09-17
No, date time type presents actual date, like YYYY-MM-DD HH:MM, your strings are not DATE fields, they are age, to have date time you need date of birth and them somehow add this age to it
I am using this query to get time taken.
SELECT DATEDIFF(dd, ActualStartDate, ActualCompletionDate) AS TimeTaken
FROM TableName
Now I want to exclude weekends and only include Mon-Fri as days counted.
Example query below, here are some details on how I solved it.
Using DATEDIFF(WK, ...) will give us the number of weeks between the 2 dates. SQL Server evaluates this as a difference between week numbers rather than based on the number of days. This is perfect, since we can use this to determine how many weekends passed between the dates.
So we can multiple that value by 2 to get the number of weekend days that occurred and subtract that from the DATEDIFF(dd, ...) to get the number of weekdays.
This doesn't behave 100% correctly when the start or end date falls on Sunday, though. So I added in some case logic at the end of the calculation to handle those instances.
You may also want to consider whether or not the DATEDIFF should be fully inclusive. e.g. Is the difference between 9/10 and 9/11 1 day or 2 days? If the latter, you'll want to add 1 to the final product.
declare #d1 datetime, #d2 datetime
select #d1 = '9/9/2011', #d2 = '9/18/2011'
select datediff(dd, #d1, #d2) - (datediff(wk, #d1, #d2) * 2) -
case when datepart(dw, #d1) = 1 then 1 else 0 end +
case when datepart(dw, #d2) = 1 then 1 else 0 end
I found when i used this there was a problem when d1 fell on saturday. Below is what i used to correct this.
declare #d1 datetime, #d2 datetime
select #d1 = '11/19/2011' , #d2 = '11/28/2011'
select datediff(dd, #d1, #d2) +case when datepart(dw, #d1) = 7 then 1 else 0 end - (datediff(wk, #d1, #d2) * 2) -
case when datepart(dw, #d1) = 1 then 1 else 0 end +
case when datepart(dw, #d2) = 1 then 1 else 0 end
BEGIN
DECLARE #totaldays INT;
DECLARE #weekenddays INT;
SET #totaldays = DATEDIFF(DAY, #startDate, #endDate)
SET #weekenddays = ((DATEDIFF(WEEK, #startDate, #endDate) * 2) + -- get the number of weekend days in between
CASE WHEN DATEPART(WEEKDAY, #startDate) = 1 THEN 1 ELSE 0 END + -- if selection was Sunday, won't add to weekends
CASE WHEN DATEPART(WEEKDAY, #endDate) = 6 THEN 1 ELSE 0 END) -- if selection was Saturday, won't add to weekends
Return (#totaldays - #weekenddays)
END
This is on SQL Server 2014
declare #d1 datetime, #d2 datetime
select #d1 = '4/19/2017', #d2 = '5/7/2017'
DECLARE #Counter int = datediff(DAY,#d1 ,#d2 )
DECLARE #C int = 0
DECLARE #SUM int = 0
WHILE #Counter > 0
begin
SET #SUM = #SUM + IIF(DATENAME(dw,
DATEADD(day,#c,#d1))IN('Sunday','Monday','Tuesday','Wednesday','Thursday')
,1,0)
SET #Counter = #Counter - 1
set #c = #c +1
end
select #Sum
If you hate CASE statements as much as I do, and want to be able to use the solution inline in your queries, just get the difference of days and subtract the count of weekend days and you'll have the desired result:
declare #d1 datetime, #d2 datetime, #days int
select #d1 = '2018/10/01', #d2 = '2018/11/01'
SET #days = DateDiff(dd, #d1, #d2) - DateDiff(ww, #d1, #d2)*2
print #days
(The only caveat--or at least point to keep in mind--is that this calculation is not inclusive of the last date, so you might need to add one day to the end date to achieve an inclusive result)
I just want to share the code I created that might help you.
DECLARE #MyCounter int = 0, #TempDate datetime, #EndDate datetime;
SET #TempDate = DATEADD(d,1,'2017-5-27')
SET #EndDate = '2017-6-3'
WHILE #TempDate <= #EndDate
BEGIN
IF DATENAME(DW,#TempDate) = 'Sunday' OR DATENAME(DW,#TempDate) = 'Saturday'
SET #MyCounter = #MyCounter
ELSE IF #TempDate not in ('2017-1-1', '2017-1-16', '2017-2-20', '2017-5-29', '2017-7-4', '2017-9-4', '2017-10-9', '2017-11-11', '2017-12-25')
SET #MyCounter = #MyCounter + 1
SET #TempDate = DATEADD(d,1,#TempDate)
CONTINUE
END
PRINT #MyCounter
PRINT #TempDate
If you do have a holiday table, you can also use that so that you don't have to list all the holidays in the ELSE IF section of the code. You can also create a function for this code and use the function whenever you need it in your query.
I hope this might help too.
Using https://stackoverflow.com/a/1804095 and JeffFisher30's answer above (https://stackoverflow.com/a/14572370/6147425) and my own need to have fractional days, I wrote this:
DateDiff(second,Start_Time,End_Time)/86400.0
-2*DateDiff(week, Start_Time, End_Time)
-Case When (DatePart(weekday, Start_Time)+##DateFirst)%7 = 1 Then 1 Else 0 End
+Case When (DatePart(weekday, End_Time)+##DateFirst)%7 = 1 Then 1 Else 0 End
Use this function to calculate the number of business days excluding Saturday and Sunday. Also it will exclude start date and it will include end date.
-- Select [dbo].[GetBussinessDays] ('02/18/2021', '03/06/2021') -- 11 days
CREATE or ALTER FUNCTION [dbo].[GetBussinessDays] (
#StartDate DATETIME,
#EndDate DATETIME
)
returns INT AS
BEGIN
DECLARE #tempStartDate DATETIME= #StartDate;
DECLARE #tempEndDate DATETIME = #EndDate;
IF(#tempStartDate IS NULL
OR
#tempEndDate IS NULL)
BEGIN
RETURN NULL;
END
--To avoid negative values reverse the date if StartDate is grater than EndDate
IF(#StartDate > #EndDate)
BEGIN
SET #StartDate = #tempEndDate;
SET #EndDate = #tempStartDate;
END
DECLARE #Counter INT = Datediff(day,#StartDate ,#EndDate);
DECLARE #TempCounter INT = 0;
DECLARE #TotalBusinessDays INT = 0;
WHILE #Counter >= 0
BEGIN
IF(#TempCounter > 0 OR #Counter = 1) -- To ignore first day's calculation
Begin
SET #TotalBusinessDays = #TotalBusinessDays + Iif(Datename(dw, Dateadd(day,#TempCounter,#StartDate)) IN('Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday'),1,0)
END
SET #Counter = #Counter - 1
SET #TempCounter = #TempCounter +1
END
RETURN #TotalBusinessDays;
END
Using #Derek Kromm answer (Current Marked Answer)
I have modified so it IS tolerant of any localisations that may be on the target server.
DECLARE #d1 DATETIME, #d2 DATETIME
SELECT #d1 = '10/01/2022', #d2 = '10/28/2022'
SELECT (datediff(dd, #d1, #EndQuery)+1) - (datediff(wk, #d1, dateadd(dd,1,#d2)) * 2)
- CASE WHEN DateName(WEEKDAY, #d1) = 'Sunday' THEN 1 ELSE 0 END -- This includes for start date edge case
+ CASE WHEN DateName(WEEKDAY, #d2) = 'Saturday' THEN 1 ELSE 0 END -- This includes for end date edge case.
This is with the end date being innclusive.
/*
EXAMPLE:
/MONDAY/
SET DATEFIRST 1
SELECT dbo.FUNC_GETDATEDIFFERENCE_WO_WEEKEND('2019-02-01','2019-02-12')
*/
CREATE FUNCTION FUNC_GETDATEDIFFERENCE_WO_WEEKEND
(
#pdtmaLastLoanPayDate DATETIME,
#pdtmaDisbursedDate DATETIME
)
RETURNS BIGINT
BEGIN
DECLARE
#mintDaysDifference BIGINT
SET #mintDaysDifference = 0
WHILE CONVERT(NCHAR(10),#pdtmaLastLoanPayDate,121) <= CONVERT(NCHAR(10),#pdtmaDisbursedDate,121)
BEGIN
IF DATEPART(WEEKDAY,#pdtmaLastLoanPayDate) NOT IN (6,7)
BEGIN
SET #mintDaysDifference = #mintDaysDifference + 1
END
SET #pdtmaLastLoanPayDate = DATEADD(DAY,1,#pdtmaLastLoanPayDate)
END
RETURN ISNULL(#mintDaysDifference,0)
END