根据出生日期和getDate()函数,如何计算年龄(以年为单位)

224

我有一个列出人名和出生日期(当前为nvarchar(25))的表格。

如何将其转换为日期,并计算他们的年龄?

我的数据如下:

ID    Name   DOB
1     John   1992-01-09 00:00:00
2     Sally  1959-05-20 00:00:00

我希望看到:

ID    Name   AGE  DOB
1     John   17   1992-01-09 00:00:00
2     Sally  50   1959-05-20 00:00:00

17
为什么你要使用nvarchar(25)作为字符串来存储日期值,而不是使用数据库原生的date或datetime类型? - Jesper
这个问题标记为2005而不是2008,因此原生的“日期”类型不可用,但肯定是一个datetime,并且可以认为是SmallDateTime,因为您不需要精度。 - Andrew
嗨,将日期保存为varchar的原因是因为我从非SQL服务器架构中导入了它们,在将它们作为datetime(和其他日期格式)导入时出现了一些问题,而varchar转换得很好。 - Jimmy
7
@James.Elsey,所以您在导入过程中遇到了问题,结果所有日期都有效吗?除非使用datetime或smalldatetime,否则永远无法确定,如果使用varchar,则可能会使您的导入工作正常,但随后可能会出现其他问题。此外,我不会存储年龄,因为它每天都在变化,而是使用视图。 - KM.
@KM 是的,导入日期数据时出现了问题,当时唯一可行的解决方案是将它们作为 nvarchar 导入。这个 select 语句将成为夜间作业的一部分,因此存储年龄不应该是一个问题。 - Jimmy
42个回答

313

以下方法存在闰年/日的问题,请参见下面的更新:

尝试这样做:

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

输出:

AgeYearsDecimal                         AgeYearsIntRound AgeYearsIntTrunc
--------------------------------------- ---------------- ----------------
17.767054                               18               17

(1 row(s) affected)

更新,以下是一些更准确的方法:

求整年份最佳方法

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

你可以将上述的10000更改为10000.0以获得小数,但它不会像下面的方法那样准确。

年份转换为小数的最佳方法

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

25
这也不是一个精确的解决方案。如果我将自己的 @dob 设为“1986-07-05 00:00:00”,并在“2013-07-04 23:59:59”执行以下代码(使用另一个变量代替 GETDATE()),它会显示我已经 27 岁,而那时候我还没有满 27 岁。示例代码:declare @startDate nvarchar(100) = '1986-07-05 00:00:00' declare @endDate nvarchar(100) = '2013-07-04 23:59:59' SELECT DATEDIFF(hour,@startDate,@endDate)/8766.0 AS AgeYearsDecimal ,CONVERT(int,ROUND(DATEDIFF(hour,@startDate,@endDate)/8766.0,0)) AS AgeYearsIntRound ,DATEDIFF(hour,@startDate,@endDate)/8766 AS AgeYearsIntTrunc - bartlaarhoven
20
这个做法并不准确,因为它假设每年有8766小时,相当于365.25天。由于没有一整年是365.25天,所以在这个人的出生日期附近,这种做法比正确率更低。这种方法会更加准确。 - Bacon Bits
1
第二个 @Bacon Bits 的评论 - 当当前日期接近一个人的生日时,这通常是错误的。 - flash
4
我认为第一段文字会让这个回答变得混乱。如果你的更新方法没有闰年问题,我建议(如果你真的想保留它)把它移到你回答的底部。 - ajbeaven
1
如果你想要一个精确的计算,DATEDIFF是不够的@ShailendraMishra,因为它只是近似计算天数等。例如,select datediff(year, '2000-01-05', '2018-01-04')返回18,而不是应该的17。我在“最佳方法计算整年”标题下使用了上面的例子,它完美地工作了。谢谢! - openwonk
显示剩余9条评论

178

我来翻译一下。如果你使用112格式(yyyymmdd)将日期转换为数字,你可以使用以下计算方法...

(yyyyMMdd - yyyyMMdd) / 10000 = 完整年份的差异

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

输出

20091015    19800420    290595  29

22
这几乎像魔法一样,解决了所有闰年问题。值得注意的是,112是将日期格式化为yyyymmdd的CONVERT函数的特殊数字。对于每个人来说,当你看到它时,可能并不显而易见。 - Derek Tomes
我的团队在处理日期时遇到了一个问题,当我们用来计算年龄的日期与比较日期相同时,我们会发现如果这个日期是奇数岁,则年龄会少算一岁。但是现在这个问题已经完美解决了! - The Sheek Geek
13
在SQL Server 2012+中,执行相同计算方法的最简/最短代码为:SELECT [年龄] = (0 + FORMAT(@as_of, 'yyyyMMdd') - FORMAT(@bday, 'yyyyMMdd')) / 10000 -- "0+" 部分告诉SQL将 char(8) 转换为数字 - ukgav
请注意,这仅适用于年龄的整数部分。不要尝试除以10000.00(得到小数而不是整数),并期望小数部分准确反映年龄。 - Tyson

57

我在我们的生产代码中使用了这个查询将近10年:

SELECT FLOOR((CAST (GetDate() AS INTEGER) - CAST(Date_of_birth AS INTEGER)) / 365.25) AS Age

9
还不错,但是不是100%。2007/10/16的时候报告的年龄将在2009/10/15时达到2岁。 - Andrew
4
哦,我们忽略了一个显而易见的问题,现在已经过了中午了,getdate返回一个整数值,所以肯定会进行四舍五入。我复制粘贴了你的答案并运行它,因此自动使用了getdate而不是字面量。 - Andrew
1
对于DOB='1969-01-10'和Today='2014-01-10',它不起作用。年龄应该是45岁,而不是44岁。您应该使用“365.23076923074”而不是“365.25”,就像@mediantiba的帖子中所述。 - Granger
12
如果我们讨论人类年龄,你应该按照人们计算年龄的方式来计算。这与地球运动的快慢没有任何关系,而是与日历有关。每当出生日期的同一月和日经过时,您就将年龄增加1岁。以下是最准确的方法,因为它反映了人类所说的“年龄”的含义: DATEDIFF(yy, @BirthDate, GETDATE()) - CASE WHEN (MONTH(@BirthDate) >= MONTH(GETDATE())) AND DAY(@BirthDate) > DAY(GETDATE()) THEN 1 ELSE 0 END - Bacon Bits
8
抱歉,该语法有误。CASE WHEN (MONTH(@date) > MONTH(GETDATE())) OR (MONTH(@date) = MONTH(GETDATE()) AND DAY(@date) > DAY(GETDATE())) THEN 1 ELSE 0 END的意思是:如果@date所在的月份大于当前月份,或者@date所在的月份等于当前月份且@date所在的日子大于当前日子,则返回1,否则返回0。 - Bacon Bits
显示剩余3条评论

40

你需要考虑datediff命令的舍入方式。

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>

这段代码是我从这里调整而来的。

需要注意的是,对于非闰年,这段代码将把2月28日视为闰年生日,例如,2020年2月29日出生的人将在2021年2月28日而不是3月1日被视为1岁。


@Andrew - 已更正 - 我漏了一个替换。 - Ed Harper
4
"""简化版 SELECT DATEDIFF(year, DOB, getdate()) + CASE WHEN (DATEADD(year,DATEDIFF(year, DOB, getdate()) , DOB) > getdate()) THEN - 1 ELSE 0 END)""" - Peter
3
这是正确的方法;我不明白为什么那些 hack 能够获得这么多赞。 - Salman A
1
这就是方法。逻辑非常容易理解。计算两个日期之间的年份差,然后将其加到原始出生日期上。这就是今年的出生日期。如果这个日期在今天之后,生日还没有到来;需要减去1来进行调整。否则,生日就是今天或者已经过去了;返回实际的年份差。这个方法也适用于闰年。 - Patrick Tucci
这个对我也有意义。 - Alain
1
它比使用FORMAT快16倍以上。 - Jeff Moden

35

上述解决方案中很多都是错误的,DateDiff(yy,@Dob, @PassedDate)方法不会考虑日期的月份和天数。而且仅比较日期部分并且只有在正确排序时才起作用。

以下代码可行且非常简单:

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

为什么要乘以100?这对我来说是有效的,因为我正在尝试在数据库中复制我们代码库中存在的内容 - 但我无法解释你的函数。这可能是一个愚蠢的问题 :) - Jen
7
谢谢!这正是我在这里期望的代码。这是该帖中唯一没有进行(丑陋的)字符串转换的完全正确的代码!@ Jen 它获取出生日期的月份和日期(如9月25日),并将其转换为整数值“0925”(或“925”)。它同样处理当前日期(如12月16日变成“1216”),然后检查是否已经超过出生日期整数值。要创建该整数,月份应乘以100。 - bartlaarhoven
感谢 @bartlaarhoven :) - Jen
我只想提一下,虽然这种方法避免了字符串转换,但它会进行大量的类型转换。我的测试显示,它的速度并不比dotjoe的答案快,而且代码更冗长。 - StriplingWarrior
根据被接受的答案的评论,@KM也出现了错误,并将产生错误值。 - jinglesthula
显示剩余2条评论

10

编辑:此回答是不正确的。我将其保留在这里,以警告任何想使用dayofyear的人,并在结尾处进行了进一步编辑。


如果像我一样,您不想除以小数天或冒险出现四舍五入/闰年错误,我为@Bacon Bits在上面的帖子https://dev59.com/NnI_5IYBdhLWcg3wAeA2#1572257中的评论喝彩:

如果我们谈论人的年龄,您应该按照人类计算年龄的方式来计算。它与地球运动的快慢无关,而全部与日历有关。每次生日当天的月份和日期过去时,您就会增加1岁。这意味着以下方式最准确,因为它反映了人们说“年龄”时的含义。

然后他提供了:

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

这里提出了几个建议,涉及比较月份和日期(有些人会犯错误,在此处未正确允许OR)。但是没有人提供dayofyear,它似乎非常简单而且更短。我提供:

DATEDIFF(year, @date, GETDATE()) -
CASE WHEN DATEPART(dayofyear, @date) > DATEPART(dayofyear, GETDATE()) THEN 1 ELSE 0 END

[注:在SQL BOL / MSDN中,DATEPART(dayofyear, ...)返回的内容没有实际记录!我理解它是一个范围为1-366的数字;最重要的是,它不像DATEPART(weekday, ...)SET DATEFIRST那样因区域而异。]


编辑:为什么dayofyear会出错:正如用户@AeroX所评论的那样,如果出生/开始日期在非闰年的2月之后,则在当前/结束日期为闰年时,年龄会提前一天增加,例如'2015-05-26''2016-05-25'将得到1岁,但其实应该仍然是0岁。比较不同年份的dayofyear显然是危险的。因此,使用MONTH()DAY()仍然是必要的。


这个应该被投票赞成,甚至标记为答案。它简短、优雅且逻辑正确。 - z00l
2
对于在二月之后出生的每个人,使用“DayOfYear”方法在每个闰年提前一天增加他们的年龄。 - AeroX
4
感谢 @AeroX 发现这个漏洞。我决定保留我的解决方案,作为对那些可能已经或将要使用 dayofyear 的人的警示,但明显需要进行编辑以显示其错误之处。我希望这样做是合适的。 - JonBrave

8

我相信这与其他发布的内容类似...但是这个解决方案适用于闰年例子02/29/1976到03/01/2011,并且也适用于第一年的情况...例如07/04/2011到07/03/2012,而最后一个关于闰年解决方案的发布并不适用于那个第一年的情况。

SELECT FLOOR(DATEDIFF(DAY, @date1 , @date2) / 365.25)

这里发现了相关的IT技术内容。


这对于一年后的确切日期不起作用,小数部分为.999,而在一年后应该是一个整数。 - Sanjeev Singh

6

由于没有一个简单的答案总是能给出正确的年龄,这是我想到的。

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 

这会得到出生日期和当前日期之间的年份差异。如果生日还未过去,则减去一年。

始终准确-无论闰年或离生日有多近。

最棒的是-没有函数。


使用SQL Server 2012+的FORMAT函数有一个简单的答案:SELECT [Age] = (0+ FORMAT(GETDATE(),'yyyyMMdd') - FORMAT(DateOfBirth,'yyyyMMdd') ) /10000其中的0+部分告诉SQL将char(8)计算为数字。 - ukgav
FORMAT 给出了正确的答案,但并不是正确的答案,因为它比正确答案慢16倍。 - Jeff Moden

5

我经过了很多思考和搜索,得出了三个解决方案:

  • 正确计算年龄
  • 简短易懂(大部分)
  • 非常易懂(大部分)。

以下是测试数值:

DECLARE @NOW DATETIME = '2013-07-04 23:59:59' 
DECLARE @DOB DATETIME = '1986-07-05' 

解决方法1:我在一个JavaScript库中找到了这个方法,它是我最喜欢的。

DATEDIFF(YY, @DOB, @NOW) - 
  CASE WHEN DATEADD(YY, DATEDIFF(YY, @DOB, @NOW), @DOB) > @NOW THEN 1 ELSE 0 END

实际上,它是将出生日期与当前日期之间的差异相加,如果大于当前日期,则减去一年。很简单吧?唯一需要注意的是这里重复了年份的差异。

但是,如果您不需要在行内使用它,可以这样写:

DECLARE @AGE INT = DATEDIFF(YY, @DOB, @NOW)
IF DATEADD(YY, @AGE, @DOB) > @NOW
SET @AGE = @AGE - 1

解决方案2:这个方案最初是从@bacon-bits那里复制的。它最容易理解,但有点长。
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

这基本上是像我们人类一样计算年龄。


解决方案3:我的朋友将其重构为以下代码:

DATEDIFF(YY, @DOB, @NOW) - 
  CEILING(0.5 * SIGN((MONTH(@DOB) - MONTH(@NOW)) * 50 + DAY(@DOB) - DAY(@NOW)))

这是最短的代码,但也是最难理解的。其中的50只是一个权重,所以当月份相同时才会考虑天数差异的影响。而SIGN函数则将任意值转换为-1、0或1。CEILING(0.5 *Math.max(0, value)相同,但SQL中并没有类似的函数。


4
select floor((datediff(day,0,@today) - datediff(day,0,@birthdate)) / 365.2425) as age

这里有很多365.25的答案。记住闰年是如何定义的:

  • 每四年
    • 除了每100年
      • 每400年

1
非常好的答案。对于那些好奇为什么365.2425是正确的值,这里有一个解释:https://www.grc.nasa.gov/WWW/k-12/Numbers/Math/Mathematical_Thinking/calendar_calculations.htm - vvvv4d

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接