Realy year datediff (no year less) - sql

I need to calculate the year diferences between two dates.
Tried using DATEDIFF but that only do the less of the dates, for example:
Date 1: 07/03/2011
Date 2: 07/02/2012
(Date format MM/DD/YYYY)
Then: DATEDIFF([yyyy], '07/03/2011', '07/02/2012') = 1
But the real diferences are 0 year.

This should work
declare #date1 datetime
declare #date2 datetime
select #date1 = '20110703', #date2 = '20120703'
select case
when dateadd(yy, DATEDIFF(yy, #date1, #date2), #date1) > #date2
then DATEDIFF(yy, #date1, #date2) -1
else DATEDIFF(yy, #date1, #date2) end
A fuller test case showing many edge conditions
create table dates(id int identity, date1 datetime, date2 datetime)
insert dates select '20110703', '20120703'
insert dates select '20110703', '20120702'
insert dates select '20110702', '20120703'
insert dates select '20110228', '20120228'
insert dates select '20120229', '20130228'
insert dates select '20120229', '20130301'
insert dates select '20110301', '20120229'
insert dates select '20120229', '20160301'
insert dates select '20120229', '20160229'
insert dates select '20101231', '20110101'
insert dates select '20100101', '20111231'
select date1, date2,
case
when dateadd(yy, DATEDIFF(yy, date1, date2), date1) > date2
then DATEDIFF(yy, date1, date2) -1
else DATEDIFF(yy, date1, date2) end
from dates
order by id

Since noone had a correct solution i post mine even though noone will ever notice.
declare #d1 datetime
declare #d2 datetime
set #d1 = '1968-02-29'
set #d2 = '2011-02-28'
select datediff(year, #d1, #d2)-
case when month(#d1)*32 + day(#d1) > month(#d2) * 32 + day(#d2) then 1 else 0 end
--case when month(#d2)*32 + day(#d1) > month(#d2) * 32 + day(#d2) then 1 else 0 end
This method is basically the same as the next method, difference is that it is done with numbers to avoid the casting, which i was told was slower.
select datediff(year, #d1, #d2)-
case when convert(char(5),#d1, 1) > convert(char(5),#d2, 1) then 1 else 0 end

SQL Server just does a year diff if you specify [yyyy].
For DATEDIFF([yyyy], '07/03/2011', '12/31/2011') it will return zero.
For DATEDIFF([yyyy], '07/03/2011', '01/01/2012') it will return 1.
In your case you should count the days if you are looking for 365 or 366 days:
DATEDIFF([dd], '07/03/2011', '07/02/2012') / 366

Yeah, DATEDIFF works that way with all dateparts, so I guess you should calculate the difference in days and then divide by 365 (this, if you don't care about the time of day). Si, try this:
DECLARE #StartDate DATETIME, #EndDate DATETIME
SET #StarDate= '20110307'
SET #EndDate = '20120207'
SELECT DATEDIFF(DAY,#StartDate, #EndDate)/365

After reviewing the question and answers a bit more, all I've got to offer is some linkage: http://msdn.microsoft.com/en-us/library/ms189794.aspx.

Related

Datetime2 Overflow Issue [duplicate]

I have a table listing people along with their date of birth (currently a nvarchar(25))
How can I convert that to a date, and then calculate their age in years?
My data looks as follows
ID Name DOB
1 John 1992-01-09 00:00:00
2 Sally 1959-05-20 00:00:00
I would like to see:
ID Name AGE DOB
1 John 17 1992-01-09 00:00:00
2 Sally 50 1959-05-20 00:00:00
There are issues with leap year/days and the following method, see the update below:
try this:
DECLARE #dob datetime
SET #dob='1992-01-09 00:00:00'
SELECT DATEDIFF(hour,#dob,GETDATE())/8766.0 AS AgeYearsDecimal
,CONVERT(int,ROUND(DATEDIFF(hour,#dob,GETDATE())/8766.0,0)) AS AgeYearsIntRound
,DATEDIFF(hour,#dob,GETDATE())/8766 AS AgeYearsIntTrunc
OUTPUT:
AgeYearsDecimal AgeYearsIntRound AgeYearsIntTrunc
--------------------------------------- ---------------- ----------------
17.767054 18 17
(1 row(s) affected)
UPDATE here are some more accurate methods:
BEST METHOD FOR YEARS IN INT
DECLARE #Now datetime, #Dob datetime
SELECT #Now='1990-05-05', #Dob='1980-05-05' --results in 10
--SELECT #Now='1990-05-04', #Dob='1980-05-05' --results in 9
--SELECT #Now='1989-05-06', #Dob='1980-05-05' --results in 9
--SELECT #Now='1990-05-06', #Dob='1980-05-05' --results in 10
--SELECT #Now='1990-12-06', #Dob='1980-05-05' --results in 10
--SELECT #Now='1991-05-04', #Dob='1980-05-05' --results in 10
SELECT
(CONVERT(int,CONVERT(char(8),#Now,112))-CONVERT(char(8),#Dob,112))/10000 AS AgeIntYears
you can change the above 10000 to 10000.0 and get decimals, but it will not be as accurate as the method below.
BEST METHOD FOR YEARS IN DECIMAL
DECLARE #Now datetime, #Dob datetime
SELECT #Now='1990-05-05', #Dob='1980-05-05' --results in 10.000000000000
--SELECT #Now='1990-05-04', #Dob='1980-05-05' --results in 9.997260273973
--SELECT #Now='1989-05-06', #Dob='1980-05-05' --results in 9.002739726027
--SELECT #Now='1990-05-06', #Dob='1980-05-05' --results in 10.002739726027
--SELECT #Now='1990-12-06', #Dob='1980-05-05' --results in 10.589041095890
--SELECT #Now='1991-05-04', #Dob='1980-05-05' --results in 10.997260273973
SELECT 1.0* DateDiff(yy,#Dob,#Now)
+CASE
WHEN #Now >= DATEFROMPARTS(DATEPART(yyyy,#Now),DATEPART(m,#Dob),DATEPART(d,#Dob)) THEN --birthday has happened for the #now year, so add some portion onto the year difference
( 1.0 --force automatic conversions from int to decimal
* DATEDIFF(day,DATEFROMPARTS(DATEPART(yyyy,#Now),DATEPART(m,#Dob),DATEPART(d,#Dob)),#Now) --number of days difference between the #Now year birthday and the #Now day
/ DATEDIFF(day,DATEFROMPARTS(DATEPART(yyyy,#Now),1,1),DATEFROMPARTS(DATEPART(yyyy,#Now)+1,1,1)) --number of days in the #Now year
)
ELSE --birthday has not been reached for the last year, so remove some portion of the year difference
-1 --remove this fractional difference onto the age
* ( -1.0 --force automatic conversions from int to decimal
* DATEDIFF(day,DATEFROMPARTS(DATEPART(yyyy,#Now),DATEPART(m,#Dob),DATEPART(d,#Dob)),#Now) --number of days difference between the #Now year birthday and the #Now day
/ DATEDIFF(day,DATEFROMPARTS(DATEPART(yyyy,#Now),1,1),DATEFROMPARTS(DATEPART(yyyy,#Now)+1,1,1)) --number of days in the #Now year
)
END AS AgeYearsDecimal
Gotta throw this one out there. If you convert the date using the 112 style (yyyymmdd) to a number you can use a calculation like this...
(yyyyMMdd - yyyyMMdd) / 10000 = difference in full years
declare #as_of datetime, #bday datetime;
select #as_of = '2009/10/15', #bday = '1980/4/20'
select
Convert(Char(8),#as_of,112),
Convert(Char(8),#bday,112),
0 + Convert(Char(8),#as_of,112) - Convert(Char(8),#bday,112),
(0 + Convert(Char(8),#as_of,112) - Convert(Char(8),#bday,112)) / 10000
output
20091015 19800420 290595 29
I have used this query in our production code for nearly 10 years:
SELECT FLOOR((CAST (GetDate() AS INTEGER) - CAST(Date_of_birth AS INTEGER)) / 365.25) AS Age
You need to consider the way the datediff command rounds.
SELECT CASE WHEN dateadd(year, datediff (year, DOB, getdate()), DOB) > getdate()
THEN datediff(year, DOB, getdate()) - 1
ELSE datediff(year, DOB, getdate())
END as Age
FROM <table>
Which I adapted from here.
Note that it will consider 28th February as the birthday of a leapling for non-leap years e.g. a person born on 29 Feb 2020 will be considered 1 year old on 28 Feb 2021 instead of 01 Mar 2021.
So many of the above solutions are wrong DateDiff(yy,#Dob, #PassedDate) will not consider the month and day of both dates. Also taking the dart parts and comparing only works if they're properly ordered.
THE FOLLOWING CODE WORKS AND IS VERY SIMPLE:
create function [dbo].[AgeAtDate](
#DOB datetime,
#PassedDate datetime
)
returns int
with SCHEMABINDING
as
begin
declare #iMonthDayDob int
declare #iMonthDayPassedDate int
select #iMonthDayDob = CAST(datepart (mm,#DOB) * 100 + datepart (dd,#DOB) AS int)
select #iMonthDayPassedDate = CAST(datepart (mm,#PassedDate) * 100 + datepart (dd,#PassedDate) AS int)
return DateDiff(yy,#DOB, #PassedDate)
- CASE WHEN #iMonthDayDob <= #iMonthDayPassedDate
THEN 0
ELSE 1
END
End
EDIT: THIS ANSWER IS INCORRECT. I leave it in here as a warning to anyone tempted to use dayofyear, with a further edit at the end.
If, like me, you do not want to divide by fractional days or risk rounding/leap year errors, I applaud #Bacon Bits comment in a post above https://stackoverflow.com/a/1572257/489865 where he says:
If we're talking about human ages, you should calculate it the way
humans calculate age. It has nothing to do with how fast the earth
moves and everything to do with the calendar. Every time the same
month and day elapses as the date of birth, you increment age by 1.
This means the following is the most accurate because it mirrors what
humans mean when they say "age".
He then offers:
DATEDIFF(yy, #date, GETDATE()) -
CASE WHEN (MONTH(#date) > MONTH(GETDATE())) OR (MONTH(#date) = MONTH(GETDATE()) AND DAY(#date) > DAY(GETDATE()))
THEN 1 ELSE 0 END
There are several suggestions here involving comparing the month & day (and some get it wrong, failing to allow for the OR as correctly here!). But nobody has offered dayofyear, which seems so simple and much shorter. I offer:
DATEDIFF(year, #date, GETDATE()) -
CASE WHEN DATEPART(dayofyear, #date) > DATEPART(dayofyear, GETDATE()) THEN 1 ELSE 0 END
[Note: Nowhere in SQL BOL/MSDN is what DATEPART(dayofyear, ...) returns actually documented! I understand it to be a number in the range 1--366; most importantly, it does not change by locale as per DATEPART(weekday, ...) & SET DATEFIRST.]
EDIT: Why dayofyear goes wrong: As user #AeroX has commented, if the birth/start date is after February in a non leap year, the age is incremented one day early when the current/end date is a leap year, e.g. '2015-05-26', '2016-05-25' gives an age of 1 when it should still be 0. Comparing the dayofyear in different years is clearly dangerous. So using MONTH() and DAY() is necessary after all.
I believe this is similar to other ones posted here.... but this solution worked for the leap year examples 02/29/1976 to 03/01/2011 and also worked for the case for the first year.. like 07/04/2011 to 07/03/2012 which the last one posted about leap year solution did not work for that first year use case.
SELECT FLOOR(DATEDIFF(DAY, #date1 , #date2) / 365.25)
Found here.
Since there isn't one simple answer that always gives the correct age, here's what I came up with.
SELECT DATEDIFF(YY, DateOfBirth, GETDATE()) -
CASE WHEN RIGHT(CONVERT(VARCHAR(6), GETDATE(), 12), 4) >=
RIGHT(CONVERT(VARCHAR(6), DateOfBirth, 12), 4)
THEN 0 ELSE 1 END AS AGE
This gets the year difference between the birth date and the current date. Then it subtracts a year if the birthdate hasn't passed yet.
Accurate all the time - regardless of leap years or how close to the birthdate.
Best of all - no function.
I've done a lot of thinking and searching about this and I have 3 solutions that
calculate age correctly
are short (mostly)
are (mostly) very understandable.
Here are testing values:
DECLARE #NOW DATETIME = '2013-07-04 23:59:59'
DECLARE #DOB DATETIME = '1986-07-05'
Solution 1: I found this approach in one js library. It's my favourite.
DATEDIFF(YY, #DOB, #NOW) -
CASE WHEN DATEADD(YY, DATEDIFF(YY, #DOB, #NOW), #DOB) > #NOW THEN 1 ELSE 0 END
It's actually adding difference in years to DOB and if it is bigger than current date then subtracts one year. Simple right? The only thing is that difference in years is duplicated here.
But if you don't need to use it inline you can write it like this:
DECLARE #AGE INT = DATEDIFF(YY, #DOB, #NOW)
IF DATEADD(YY, #AGE, #DOB) > #NOW
SET #AGE = #AGE - 1
Solution 2: This one I originally copied from #bacon-bits. It's the easiest to understand but a bit long.
DATEDIFF(YY, #DOB, #NOW) -
CASE WHEN MONTH(#DOB) > MONTH(#NOW)
OR MONTH(#DOB) = MONTH(#NOW) AND DAY(#DOB) > DAY(#NOW)
THEN 1 ELSE 0 END
It's basically calculating age as we humans do.
Solution 3: My friend refactored it into this:
DATEDIFF(YY, #DOB, #NOW) -
CEILING(0.5 * SIGN((MONTH(#DOB) - MONTH(#NOW)) * 50 + DAY(#DOB) - DAY(#NOW)))
This one is the shortest but it's most difficult to understand. 50 is just a weight so the day difference is only important when months are the same. SIGN function is for transforming whatever value it gets to -1, 0 or 1. CEILING(0.5 * is the same as Math.max(0, value) but there is no such thing in SQL.
What about:
DECLARE #DOB datetime
SET #DOB='19851125'
SELECT Datepart(yy,convert(date,GETDATE())-#DOB)-1900
Wouldn't that avoid all those rounding, truncating and ofsetting issues?
Just check whether the below answer is feasible.
DECLARE #BirthDate DATE = '09/06/1979'
SELECT
(
YEAR(GETDATE()) - YEAR(#BirthDate) -
CASE WHEN (MONTH(GETDATE()) * 100) + DATEPART(dd, GETDATE()) >
(MONTH(#BirthDate) * 100) + DATEPART(dd, #BirthDate)
THEN 1
ELSE 0
END
)
select floor((datediff(day,0,#today) - datediff(day,0,#birthdate)) / 365.2425) as age
There are a lot of 365.25 answers here. Remember how leap years are defined:
Every four years
except every 100 years
except every 400 years
There are many answers to this question, but I think this one is close to the truth.
The datediff(year,…,…) function, as we all know, only counts the boundaries crossed by the date part, in this case the year. As a result it ignores the rest of the year.
This will only give the age in completed years if the year were to start on the birthday. It probably doesn’t, but we can fake it by adjusting the asking date back by the same amount.
In pseudopseudo code, it’s something like this:
adjusted_today = today - month(dob) + 1 - day(dob) + 1
age = year(adjusted_today - dob)
The + 1 is to allow for the fact that the month and day numbers start from 1 and not 0.
The reason we subtract the month and the day separately rather than the day of the year is because February has the annoying tendency to change its length.
The calculation in SQL is:
datediff(year,dob,dateadd(month,-month(dob)+1,dateadd(day,-day(dob)+1,today)))
where dob and today are presumed to be the date of birth and the asking date.
You can test this as follows:
WITH dates AS (
SELECT
cast('2022-03-01' as date) AS today,
cast('1943-02-25' as date) AS dob
)
select
datediff(year,dob,dateadd(month,-month(dob)+1,dateadd(day,-day(dob)+1,today))) AS age
from dates;
which gives you George Harrison’s age in completed years.
This is much cleaner than fiddling about with quarter days which will generally give you misleading values on the edges.
If you have the luxury of creating a scalar function, you can use something like this:
DROP FUNCTION IF EXISTS age;
GO
CREATE FUNCTION age(#dob date, #today date) RETURNS INT AS
BEGIN
SET #today = dateadd(month,-month(#dob)+1,#today);
SET #today = dateadd(day,-day(#dob)+1,#today);
RETURN datediff(year,#dob,#today);
END;
GO
Remember, you need to call dbo.age() because, well, Microsoft.
DECLARE #DOB datetime
set #DOB ='11/25/1985'
select floor(
( cast(convert(varchar(8),getdate(),112) as int)-
cast(convert(varchar(8),#DOB,112) as int) ) / 10000
)
source: http://beginsql.wordpress.com/2012/04/26/how-to-calculate-age-in-sql-server/
Try This
DECLARE #date datetime, #tmpdate datetime, #years int, #months int, #days int
SELECT #date = '08/16/84'
SELECT #tmpdate = #date
SELECT #years = DATEDIFF(yy, #tmpdate, GETDATE()) - CASE WHEN (MONTH(#date) > MONTH(GETDATE())) OR (MONTH(#date) = MONTH(GETDATE()) AND DAY(#date) > DAY(GETDATE())) THEN 1 ELSE 0 END
SELECT #tmpdate = DATEADD(yy, #years, #tmpdate)
SELECT #months = DATEDIFF(m, #tmpdate, GETDATE()) - CASE WHEN DAY(#date) > DAY(GETDATE()) THEN 1 ELSE 0 END
SELECT #tmpdate = DATEADD(m, #months, #tmpdate)
SELECT #days = DATEDIFF(d, #tmpdate, GETDATE())
SELECT Convert(Varchar(Max),#years)+' Years '+ Convert(Varchar(max),#months) + ' Months '+Convert(Varchar(Max), #days)+'days'
After trying MANY methods, this works 100% of the time using the modern MS SQL FORMAT function instead of convert to style 112. Either would work but this is the least code.
Can anyone find a date combination which does not work? I don't think there is one :)
--Set parameters, or choose from table.column instead:
DECLARE #DOB DATE = '2000/02/29' -- If #DOB is a leap day...
,#ToDate DATE = '2018/03/01' --...there birthday in this calculation will be
--0+ part tells SQL to calc the char(8) as numbers:
SELECT [Age] = (0+ FORMAT(#ToDate,'yyyyMMdd') - FORMAT(#DOB,'yyyyMMdd') ) /10000
CASE WHEN datepart(MM, getdate()) < datepart(MM, BIRTHDATE) THEN ((datepart(YYYY, getdate()) - datepart(YYYY, BIRTH_DATE)) -1 )
ELSE
CASE WHEN datepart(MM, getdate()) = datepart(MM, BIRTHDATE)
THEN
CASE WHEN datepart(DD, getdate()) < datepart(DD, BIRTHDATE) THEN ((datepart(YYYY, getdate()) - datepart(YYYY, BIRTHDATE)) -1 )
ELSE (datepart(YYYY, getdate()) - datepart(YYYY, BIRTHDATE))
END
ELSE (datepart(YYYY, getdate()) - datepart(YYYY, BIRTHDATE)) END
END
SELECT ID,
Name,
DATEDIFF(yy,CONVERT(DATETIME, DOB),GETDATE()) AS AGE,
DOB
FROM MyTable
How about this:
SET #Age = CAST(DATEDIFF(Year, #DOB, #Stamp) as int)
IF (CAST(DATEDIFF(DAY, DATEADD(Year, #Age, #DOB), #Stamp) as int) < 0)
SET #Age = #Age - 1
Try this solution:
declare #BirthDate datetime
declare #ToDate datetime
set #BirthDate = '1/3/1990'
set #ToDate = '1/2/2008'
select #BirthDate [Date of Birth], #ToDate [ToDate],(case when (DatePart(mm,#ToDate) < Datepart(mm,#BirthDate))
OR (DatePart(m,#ToDate) = Datepart(m,#BirthDate) AND DatePart(dd,#ToDate) < Datepart(dd,#BirthDate))
then (Datepart(yy, #ToDate) - Datepart(yy, #BirthDate) - 1)
else (Datepart(yy, #ToDate) - Datepart(yy, #BirthDate))end) Age
This will correctly handle the issues with the birthday and rounding:
DECLARE #dob datetime
SET #dob='1992-01-09 00:00:00'
SELECT DATEDIFF(YEAR, '0:0', getdate()-#dob)
Ed Harper's solution is the simplest I have found which never returns the wrong answer when the month and day of the two dates are 1 or less days apart. I made a slight modification to handle negative ages.
DECLARE #D1 AS DATETIME, #D2 AS DATETIME
SET #D2 = '2012-03-01 10:00:02'
SET #D1 = '2013-03-01 10:00:01'
SELECT
DATEDIFF(YEAR, #D1,#D2)
+
CASE
WHEN #D1<#D2 AND DATEADD(YEAR, DATEDIFF(YEAR,#D1, #D2), #D1) > #D2
THEN - 1
WHEN #D1>#D2 AND DATEADD(YEAR, DATEDIFF(YEAR,#D1, #D2), #D1) < #D2
THEN 1
ELSE 0
END AS AGE
The answer marked as correct is nearer to accuracy but, it fails in following scenario - where Year of birth is Leap year and day are after February month
declare #ReportStartDate datetime = CONVERT(datetime, '1/1/2014'),
#DateofBirth datetime = CONVERT(datetime, '2/29/1948')
FLOOR(DATEDIFF(HOUR,#DateofBirth,#ReportStartDate )/8766)
OR
FLOOR(DATEDIFF(HOUR,#DateofBirth,#ReportStartDate )/8765.82) -- Divisor is more accurate than 8766
-- Following solution is giving me more accurate results.
FLOOR(DATEDIFF(YEAR,#DateofBirth,#ReportStartDate) - (CASE WHEN DATEADD(YY,DATEDIFF(YEAR,#DateofBirth,#ReportStartDate),#DateofBirth) > #ReportStartDate THEN 1 ELSE 0 END ))
It worked in almost all scenarios, considering leap year, date as 29 feb, etc.
Please correct me if this formula have any loophole.
Declare #dob datetime
Declare #today datetime
Set #dob = '05/20/2000'
set #today = getdate()
select CASE
WHEN dateadd(year, datediff (year, #dob, #today), #dob) > #today
THEN datediff (year, #dob, #today) - 1
ELSE datediff (year, #dob, #today)
END as Age
Here is how i calculate age given a birth date and current date.
select case
when cast(getdate() as date) = cast(dateadd(year, (datediff(year, '1996-09-09', getdate())), '1996-09-09') as date)
then dateDiff(yyyy,'1996-09-09',dateadd(year, 0, getdate()))
else dateDiff(yyyy,'1996-09-09',dateadd(year, -1, getdate()))
end as MemberAge
go
CREATE function dbo.AgeAtDate(
#DOB datetime,
#CompareDate datetime
)
returns INT
as
begin
return CASE WHEN #DOB is null
THEN
null
ELSE
DateDiff(yy,#DOB, #CompareDate)
- CASE WHEN datepart(mm,#CompareDate) > datepart(mm,#DOB) OR (datepart(mm,#CompareDate) = datepart(mm,#DOB) AND datepart(dd,#CompareDate) >= datepart(dd,#DOB))
THEN 0
ELSE 1
END
END
End
GO
DECLARE #FromDate DATETIME = '1992-01-2623:59:59.000',
#ToDate DATETIME = '2016-08-10 00:00:00.000',
#Years INT, #Months INT, #Days INT, #tmpFromDate DATETIME
SET #Years = DATEDIFF(YEAR, #FromDate, #ToDate)
- (CASE WHEN DATEADD(YEAR, DATEDIFF(YEAR, #FromDate, #ToDate),
#FromDate) > #ToDate THEN 1 ELSE 0 END)
SET #tmpFromDate = DATEADD(YEAR, #Years , #FromDate)
SET #Months = DATEDIFF(MONTH, #tmpFromDate, #ToDate)
- (CASE WHEN DATEADD(MONTH,DATEDIFF(MONTH, #tmpFromDate, #ToDate),
#tmpFromDate) > #ToDate THEN 1 ELSE 0 END)
SET #tmpFromDate = DATEADD(MONTH, #Months , #tmpFromDate)
SET #Days = DATEDIFF(DAY, #tmpFromDate, #ToDate)
- (CASE WHEN DATEADD(DAY, DATEDIFF(DAY, #tmpFromDate, #ToDate),
#tmpFromDate) > #ToDate THEN 1 ELSE 0 END)
SELECT #FromDate FromDate, #ToDate ToDate,
#Years Years, #Months Months, #Days Days
What about a solution with only date functions, not math, not worries about leap year
CREATE FUNCTION dbo.getAge(#dt datetime)
RETURNS int
AS
BEGIN
RETURN
DATEDIFF(yy, #dt, getdate())
- CASE
WHEN
MONTH(#dt) > MONTH(GETDATE()) OR
(MONTH(#dt) = MONTH(GETDATE()) AND DAY(#dt) > DAY(GETDATE()))
THEN 1
ELSE 0
END
END
declare #birthday as datetime
set #birthday = '2000-01-01'
declare #today as datetime
set #today = GetDate()
select
case when ( substring(convert(varchar, #today, 112), 5,4) >= substring(convert(varchar, #birthday, 112), 5,4) ) then
(datepart(year,#today) - datepart(year,#birthday))
else
(datepart(year,#today) - datepart(year,#birthday)) - 1
end
The following script checks the difference in years between now and the given date of birth; the second part checks whether the birthday is already past in the current year; if not, it subtracts it:
SELECT year(NOW()) - year(date_of_birth) - (CONCAT(year(NOW()), '-', month(date_of_birth), '-', day(date_of_birth)) > NOW()) AS Age
FROM tableName;

SQL Function using WHILE takes a long time to run

I've created a SQL function that takes two dates, then finds the number of days between them, excluding weekends and holidays. It works, but the issue is that it takes 1.5 seconds per row to run. This is a bummer, because we're running this for reports that are potentially thousands of rows long.
For a little more background, the cteTally table is just a generic tally table. The HLD1 table is a table that looks like the following:
Calendar StrDate EndDate
2016Holidays 1/1/16 1/1/16
2016Holidays 5/30/16 5/30/16
2016Holidays 7/4/16 7/4/16
2016Holidays 9/5/16 9/5/16
2016Holidays 11/24/16 11/25/16
2016Holidays 12/26/16 12/26/16
Essentially what it's intent is is that when a customer calls in on a repair request for a washing machine, we want to track the response time, but only counting business days in our count. So, for example, if a customer called in on June 30 at 4:30 PM, and a tech went out to repair on July 5 at 8:30 AM, we would count Friday 7/1 (a non-holiday, non-weekend date), not count Saturday 7/2 or Sunday 7/3 (weekend days), and not count Monday 7/4 (a holiday), and count Tuesday 7/5 to get an accurate count of the overnights.
The answer that it would then return is 2.
In order to do this, it looks at the days in the function and says "when these days meet these criteria, add or subtract a day so the start or end date is not a weekend or holiday."
I'm about 98.36% sure that there has to be a better/easier way to do this, and I'm way overthinking what's going on, but I can't for the life of me determine what I could do differently, and I've been working on it on and off for about 3 days now.
EDIT (before I even posted): I've now gotten it down to 19 seconds for the 14560 rows returning that I'm using the function in. However, before I modified the function to take holidays into account, it was able to return all of the rows in less than a second. The change that I made was to limit the "holidays" that were getting returned to holidays in between the start and end date. However, I think the "WHILE" loop is still causing me some issues.
I've also tried creating a temp table or a cte table to store the holidays in so it only has to calculate them once per line instead of 4 times per line, but it doesn't appear that that is possible within a function.
I'm going to keep working on this for now, but would love any help that y'all can provide.
EDIT 2: I put all of the "SELECT #variables" that were in a row in the same select statement instead of having separate ones, and that cut the time down by a further 9 seconds for 14000 rows. However, I'd still like to get it under the 2 second barrier, if possible, or at least the 5 second barrier.
Here is the text of the function:
CREATE FUNCTION [dbo].[dateDiffHolidays] (
#startdaytime DATETIME,
#enddaytime DATETIME
)
RETURNS INT
AS
BEGIN
DECLARE #answer INT;
DECLARE #START Date;
DECLARE #END Date;
DECLARE #AddDays int;
SET #answer = 0
-- Strip Times
SELECT #START = dateadd(dd,0, datediff(dd,0,#StartDayTime))
SELECT #END = dateadd(dd,0, datediff(dd,0,#EndDayTime))
SELECT #AddDays = count(*) from (SELECT
dateadd(dd,ctetally.n-1,#START) date1
from
cteTally
where dateadd(dd,ctetally.n-1,#START) <= #END) s1 where s1.date1 in (select cast( DATEADD(day, t.N - 1, StrDate) as date) as ResultDate
from HLD1 s join cteTally t on t.N <= DATEDIFF(day, StrDate, EndDate) + 1)
or datepart(dw,s1.date1) in (1,7)
-- handle end conditions
DECLARE #firstWeekDayInRange datetime, #lastWeekDayInRange datetime;
SET #firstWeekDayInRange = #START
set #lastWeekDayInRange = #END
WHILE #firstWeekDayInRange in (select cast( DATEADD(day, t.N - 1, StrDate) as date) as ResultDate
from HLD1 s join cteTally t on t.N <= DATEDIFF(day, StrDate, EndDate) + 1)
or datepart(dw,#firstWeekDayInRange) in (1,7)
BEGIN
SELECT #firstWeekDayInRange =
CASE
WHEN #firstWeekDayInRange in (select cast( DATEADD(day, t.N - 1, StrDate) as date) from HLD1 s join cteTally t on t.N <= DATEDIFF(day, StrDate, EndDate) + 1)
or datepart(dw,#firstWeekDayInRange) in (1,7)
THEN dateadd(DAY,1,#firstWeekDayInRange)
ELSE #firstWeekDayInRange
END
END
WHILE #lastWeekDayInRange in (select cast( DATEADD(day, t.N - 1, StrDate) as date) as ResultDate
from HLD1 s join cteTally t on t.N <= DATEDIFF(day, StrDate, EndDate) + 1)
or datepart(dw,#lastWeekDayInRange) in (1,7)
BEGIN
SELECT #lastWeekDayInRange =
CASE
WHEN #lastWeekDayInRange in (select cast( DATEADD(day, t.N - 1, StrDate) as date) from HLD1 s join cteTally t on t.N <= DATEDIFF(day, StrDate, EndDate) + 1)
or datepart(dw,#lastWeekDayInRange) in (1,7)
THEN dateadd(DAY,-1,#lastWeekDayInRange)
ELSE #lastWeekDayInRange
END
END
-- add one day to answer (to count Friday) if enddate was on a weekend
SELECT #answer = #answer +
CASE
-- triggered if start and end date are on same weekend
WHEN dateDiff(DAY,#firstWeekDayInRange,#lastWeekDayInRange) < 0 THEN (#answer * -1)
-- otherwise count the days and substract 2 days per weekend in between dates
ELSE (DateDiff(DAY, #firstWeekDayInRange, #lastWeekDayInRange) - #AddDays)
END
RETURN #answer
END
GO
Perhaps this can help.
Declare #HLD1 table (Calendar varchar(50),StrDate Date,EndDate Date)
Insert Into #HLD1 values
('2016Holidays','1/1/16','1/1/16'),
('2016Holidays','5/30/16','5/30/16'),
('2016Holidays','7/4/16','7/4/16'),
('2016Holidays','9/5/16','9/5/16'),
('2016Holidays','11/24/16','11/25/16'),
('2016Holidays','12/26/16','12/26/16')
Declare #Date1 Date = '2016-06-30 16:30:00'
Declare #Date2 Date = '2016-07-05 08:30:00'
Select DateDiff(DD,#Date1,#Date2)-sum(Excl)
From (
Select RetVal,Excl=max(Excl)
From (
Select *,Excl=IIF(DatePart(WEEKDAY,RetVal) in (7,1),1,0) From [dbo].[udf-Create-Range-Date](#Date1,#Date2,'DD',1)
Union All
Select RetVal=StrDate,Excl=1 From #HLD1 Where StrDate Between #Date1 and #Date2
) A
Group By RetVal
) A
Returns
2
You can still use your tally table, but I prefer my UDF to create
Dynamic Date Ranges
CREATE FUNCTION [dbo].[udf-Create-Range-Date] (#DateFrom datetime,#DateTo datetime,#DatePart varchar(10),#Incr int)
Returns
#ReturnVal Table (RetVal datetime)
As
Begin
With DateTable As (
Select DateFrom = #DateFrom
Union All
Select Case #DatePart
When 'YY' then DateAdd(YY, #Incr, df.dateFrom)
When 'QQ' then DateAdd(QQ, #Incr, df.dateFrom)
When 'MM' then DateAdd(MM, #Incr, df.dateFrom)
When 'WK' then DateAdd(WK, #Incr, df.dateFrom)
When 'DD' then DateAdd(DD, #Incr, df.dateFrom)
When 'HH' then DateAdd(HH, #Incr, df.dateFrom)
When 'MI' then DateAdd(MI, #Incr, df.dateFrom)
When 'SS' then DateAdd(SS, #Incr, df.dateFrom)
End
From DateTable DF
Where DF.DateFrom < #DateTo
)
Insert into #ReturnVal(RetVal) Select DateFrom From DateTable option (maxrecursion 32767)
Return
End
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2020-10-01','YY',1)
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2020-10-01','DD',1)
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2016-10-31','MI',15)
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2016-10-02','SS',1)
Scalar UDFs can be slow: http://dataeducation.com/scalar-functions-inlining-and-performance-an-entertaining-title-for-a-boring-post/
If you can turn it into an inline table valued UDF, your query will probably be much faster. Although having looked at your UDF code, it might not be possible/easy to do.

how view result date time datediff dateadd , but out-of-range value

i try compare date now and start date working from master_employee.
but i failed...
if at line i write
select #date = date_start
from Master_Employee
where id = '2'
its succes.
but i hope, can view all result in table Master_Employee.
can you help me ?
thank's very much..
DECLARE #date DATETIME
,#tmpdate DATETIME
,#years INT
,#months INT
,#days INT
SELECT #date = date_Start
FROM Master_Employee
SELECT #tmpdate = #date
SELECT #years = DATEDIFF(yyyy, #tmpdate, GETDATE()) - CASE
WHEN (MONTH(#date) > MONTH(GETDATE()))
OR (
MONTH(#date) = MONTH(GETDATE())
AND DAY(#date) > DAY(GETDATE())
)
THEN 1
ELSE 0
END
SELECT #tmpdate = DATEADD(yyyy, #years, #tmpdate)
SELECT #months = DATEDIFF(mm, #tmpdate, GETDATE()) - CASE
WHEN DAY(#date) > DAY(GETDATE())
THEN 1
ELSE 0
END
SELECT #tmpdate = DATEADD(mm, #months, #tmpdate)
SELECT #days = DATEDIFF(dd, #tmpdate, GETDATE())
SELECT #years AS Years
,#months AS Months
,#days AS Dayss
,GETDATE() AS Date_Now
This will give you how many days, months, years have passed in aggregate for all employees, As far as I can tell this is what you are tying to do.
DECLARE #Today as datetime = CONVERT(Date,GETDATE())
SELECT SUM(DATEDIFF(day,ISNULL(convert(datetime,#date),Today),#Today)) [Days]
,SUM(DATEDIFF(MONTH,ISNULL(convert(datetime,#date),Today),#Today)) [Months]
,SUM(DATEDIFF(Year,ISNULL(convert(datetime,#date),Today(,#Today)) [Years]
FROM Master_Employee
The reason that
SELECT #date = date_Start
FROM Master_Employee
is failing is because you are trying to assign all the start dates to the same variable.
If you want separate lines for each employee try:
DECLARE #Today as datetime = CONVERT(Date,GETDATE())
SELECT Id
,SUM(DATEDIFF(day,ISNULL(convert(datetime,#date),Today),#Today)) [Days]
,SUM(DATEDIFF(MONTH,ISNULL(convert(datetime,#date),Today),#Today)) [Months]
,SUM(DATEDIFF(Year,ISNULL(convert(datetime,#date),Today),#Today)) [Years]
FROM Master_Employee
GROUP BY ID
Be careful, month and year can be misleading, if the person started 12/31/14 and you ran this on 1/1/15 you will see 1 day, 1 month, 1 year. You might be better off using only days and figuring your own math for how long that is...

Number of working days between two dates

I want number of working days in between to dates. For example if we have 01-01-2012 and 20-01-2012, i want to get the number of working days in between that two dates using T-SQL.
Since SQL Server has no idea what your company considers working days, the best answer to this problem is likely going to be to use a calendar table. Once you have a table with past and future dates, with a column like IsWorkDay correctly updated, the query is simple:
SELECT [Date] FROM dbo.Calendar
WHERE [Date] >= #start
AND [Date] <= #end
AND IsWorkDay = 1;
DECLARE #fromDate datetime, #toDate datetime
SELECT #fromDate = ' 01-01-2012', #toDate = '20-01-2012'
SELECT (DATEDIFF(day, #fromDate, #toDate) + 1)
- (DATEDIFF(week, #fromDate, #toDate) * 2)
- (CASE WHEN DATENAME(weekday, #fromDate) = 'Sunday' THEN 1 ELSE 0 END)
- (CASE WHEN DATENAME(weekday, #toDate) = 'Saturday' THEN 1 ELSE 0 END)
I liked Aaron Bertrand's suggestion so I wrote this code that can be added to your queries. It creates a table variable between 2 dates that you can then use in your query by joining on the CalendarDate column (just remember to strip out any time information before joining). This is based on the typical American work week of Monday through Friday.
DECLARE #StartDate DATE
DECLARE #EndDate DATE
SET #StartDate = '2013-08-19'
SET #EndDate = '2013-08-26'
DECLARE #BusinessDay TABLE
(
CalendarDate DATETIME,
IsBusinessDay INT
)
DECLARE #Counter DATETIME = #StartDate
WHILE(#Counter <= #EndDate)
BEGIN
INSERT INTO #WorkDays
SELECT #Counter, CASE WHEN DATENAME(WEEKDAY, #Counter) NOT IN ('Saturday', 'Sunday') THEN 1 ELSE 0 END
SET #Counter = DATEADD(DAY, 1, #Counter)
END
SELECT * FROM #BusinessDay
The downside is this has to be recreated for each query that needs it, so if you're doing this often, a fixed table might be a better way to go.
It can be used like this....
SELECT
BusinessDays = SUM(IsBusinessDay)
FROM
#BusinessDay
WHERE
CalendarDate BETWEEN #StartDate AND #EndDate
That will give you the count of business days between the two dates. Like many others have said, this obviously does not take into account any holidays or my birthday.
Based on previous code, I adapted it to exclude the last day (not asked but I needed it).
select (DATEDIFF(dd,#fromDate, #toDate))
- (DATEDIFF(ww,#fromDate, DATEADD(dd,-1,#toDate)) * 2)
- (CASE WHEN DATENAME(dw, #fromDate) = 'Sunday' THEN 1 else 0 end)
- (CASE WHEN DATENAME(dw, #toDate) = 'Sunday' THEN 1 else 0 end)
I removed the holydays by using a table containing those dates
- ( select count(distinct dcsdte)
from calendar_table
where dcsdte between #fromDate
and #toDate )

Compare current date with stored datetime using month an year only

Using SQL Server 2005 I have a field that contains a datetime value.
What I am trying to do is create 2 queries:
Compare to see if stored datetime is of the same month+year as current date
Compare to see if stored datetime is of the same year as current date
There is probably a simple solution but I keep hitting brick walls using various samples I can find, any thoughts?
Thanks in advance.
Compare the parts of the date:
WHERE YEAR( columnName ) = YEAR( getDate() )
While the other answers will work, they all suffer from the same problem: they apply a transformation to the column and therefore will never utilize an index on that column.
To search the date without a transformation, you need a couple built-in functions and some math. Example below:
--create a table to hold our example values
create table #DateSearch
(
TheDate datetime not null
)
insert into #DateSearch (TheDate)
--today
select getdate()
union all
--a month in advance
select dateadd(month, 1, getdate())
union all
--a year in advance
select dateadd(year, 1, getdate())
go
--declare variables to make things a little easier to see
declare #StartDate datetime, #EndDate datetime
--search for "same month+year as current date"
select #StartDate = dateadd(month, datediff(month, 0, getdate()), 0), #EndDate = dateadd(month, datediff(month, 0, getdate()) + 1, 0)
select #StartDate [StartDate], #EndDate [EndDate], TheDate from #DateSearch
where TheDate >= #StartDate and TheDate < #EndDate
--search for "same year as current date"
select #StartDate = dateadd(year, datediff(year, 0, getdate()), 0), #EndDate = dateadd(year, datediff(year, 0, getdate()) + 1, 0)
select #StartDate [StartDate], #EndDate [EndDate], TheDate from #DateSearch
where TheDate >= #StartDate and TheDate < #EndDate
What the statement does to avoid the transformations, is find all values greater-than or equal-to the beginning of the current time period (month or year) AND all values less-than the beginning of the next (invalid) time period. This solves our index problem and also mitigates any issues related to 3ms rounding in the DATETIME type.
SELECT * FROM atable
WHERE
YEAR( adate ) = YEAR( GETDATE() )
AND
MONTH( adate ) = MONTH( GETDATE() )
It sounds to me like DATEDIFF is exactly what you need:
-- #1 same month and year
SELECT *
FROM your_table
WHERE DATEDIFF(month, your_column, GETDATE()) = 0
-- #2 same year
SELECT *
FROM your_table
WHERE DATEDIFF(year, your_column, GETDATE()) = 0
The datepart function lets you pull the bits you need:
declare #d1 as datetime
declare #d2 as datetime
if datepart(yy, #d1) = datepart(yy, #d2) and datepart(mm, #d1) = datepart(mm, #d2) begin
print 'same'
end
You can use something like this
a)
select *
from table
where MONTH(field) = MONTH(GetDATE())
and YEAR(field) = YEAR(GetDATE())
b)
select *
from table
where YEAR(field) = YEAR(GetDATE())