从生日计算年龄 - TSQL,Oracle和其他任何

时间:2018-02-12 22:31:47

标签: sql sql-server oracle date

我经常看到那些认为DATEDIFF(YEAR, date_of_birth, GETDATE())会产生当前年龄的人。

不幸的是这不正确。 DATEDIFF函数(至少在SQL Server中)不计算两个日期之间的完整年数 - 它计算跨越的日历边界的数量。

也就是说,在YEAR参数的情况下,DATEDIFF计算1-Januaries的数量。使用MONTH参数,它将计算月份的1个数。

因此,例如,12月31日出生的婴儿已经是" 1岁"实际上他们不会在1岁之前一直到31岁 - 12月18日,他们是目前0岁。

以这种方式使用DATEDIFF功能相当于写YEAR(GETDATE()) - YEAR(date_of_birth),这当然不会考虑一年中人的生日在哪里。

在这里回顾了其他问题的一个很好的样本,似乎没有明确的答案,并且有很多答案都是错误的。

所以我的问题是,最简单的表达是,根据一个人的出生日期和参考日期,计算一个人在该参考日期的年龄?< / p>

我正在寻找以下条款的答案:

  • 答案应该是内联表达式 - 能够被选中或合并到where子句中。

  • 答案应该是健全的并且没有错误的角落案例 - 不管这些案件是不常见的还是难以置信的。它应该适用于新生婴儿和几百年前的人,应该应对闰年,理想情况下应该能够代表出生前的一段时间作为负数(随着这个人成为 - 在他们出生的前一天1岁。

  • 答案应该是数字上精确的 - 它不应该依赖于使用每年的平均天数或除以幻数,并将错误四舍五入。我相信所有使用四舍五入的琐碎方法都有极端情况,因此不稳健,但我有待纠正。

  • 对于那些生日落在2月29日的人,如果参考年份为闰年,则应将其年龄视为2月29日的增加,或者如果参考年份为闰年,则应将其年龄视为增加参考年份不是闰年。

  • 答案应该忽略出生日期或参考日期的任何时间部分 - 也就是说,应假定该人在午夜出生,并且他们的年龄应在随后的生日午夜时间增加。

我对SQL Server的答案特别感兴趣,但也有兴趣查看对任何其他数据库平台的答案

编辑:我很感谢已经给出的答案以及之前问题的链接。此问题现已标记为重复。

令我印象深刻的是,所给出的链接的接受答案是混乱的,并且从错误的实现开始,对它的评论指的是它的实现是不正确的 - 只有在我仔细阅读之后我才看到它有已经更新了正确的实现(夹在&#34;十进制年龄&#34的处理之间;这与我的问题无关)。第三个答案,也吸引了大量的选票,也是不正确的。还有无数其他质量不同的答案。

我还要求一个答案,理想情况下,正确处理&#34;负面年龄&#34;以某种方式(参考日期在出生日期之前),而链接的答案则没有。因此,虽然我的问题对待同一主题,但它并不重复,因为我已经施加了一些在该问题中不存在的额外约束,并且会使(以某种方式)所有答案无效。那个老问题。

老实说,鉴于有多少巧妙的错误答案比比皆是(正如我在第一段中提到的那样),我本以为这个问题会得到更多的赞成和更多的兴趣,而不是一个downvote和3票结束。

4 个答案:

答案 0 :(得分:1)

在Oracle中,您可以这样做,

SELECT FLOOR(MONTHS_BETWEEN(:given_date, :birth_date)/12) 
  FROM DUAL;

如果生日是2月29日,如果年份不是闰年,则3月1日是考虑的生日,您可以使用下面的查询,

SELECT FLOOR(
          MONTHS_BETWEEN (
            CASE TO_CHAR(TO_DATE(:birth_date, 'DD-MON-YYYY'), 'fmMON-DD')
                WHEN 'FEB-29' THEN
                    TO_DATE(:given_date, 'DD-MON-YYYY') - 1
                ELSE 
                    TO_DATE(:given_date, 'DD-MON-YYYY')
             END,
             TO_DATE(:birth_date, 'DD-MON-YYYY'))
               /12) years_old
  FROM DUAL;

在SQL Server中,我帮不了你,但我找到了这个链接,http://www.sqlines.com/oracle-to-sql-server/months_between,也许它可以帮助你,

答案 1 :(得分:1)

从Oracle开始,我使用了这样的功能;我已经阅读了你提到的条件(即你应该能够在WHERE子句中使用它;好吧,你可以在那里使用一个函数。如果你不想要它,使用整个代码也没问题,但看起来很难看丑陋)。输出可以修改;我使用了那种格式,你可以根据需要改变它。

你在这里:

if ( srs!=null && srs.next() ) {

斯科特的EMP表:

SQL> create or replace function f_age (par_birthdate in date)
  2     return varchar2
  3  is
  4     retval   varchar2 (30);
  5  begin
  6     retval :=
  7           trunc (months_between (sysdate, par_birthdate) / 12)
  8        || ' years '
  9        || lpad (trunc (mod (months_between (sysdate, par_birthdate), 12)),
 10                 2,
 11                 ' ')
 12        || ' months '
 13        || lpad (
 14              trunc (
 15                   sysdate
 16                 - add_months (
 17                      par_birthdate,
 18                          trunc (months_between (sysdate, par_birthdate) / 12)
 19                        * 12
 20                      + trunc (
 21                           mod (months_between (sysdate, par_birthdate), 12)))),
 22              2,
 23              ' ')
 24        || ' days ';
 25
 26     return (trim (retval));
 27  end f_age;
 28  /

Function created.

答案 2 :(得分:1)

正确答案

从给出的来源拼凑而成,这是SQL Server的正确答案(从2012年开始)

IIF(reference_date < date_of_birth, -1, CAST(CONVERT(CHAR(8), reference_date, 112) AS INT) - CAST(CONVERT(CHAR(8), date_of_birth, 112) AS INT) / 10000)

这会将日期转换为ISO日期字符串(YYYYMMDD),将字符串转换为和int,然后从另一个中减去一个 - 如果出生的月份和月日高于参考日期,则此减法借用一个数字从年份组成部分。然后我们将整数除以10,000以产生全年差异。这在闰年中无缝地工作。时间部分在转换中自动截断。 CHAR(8)用作字符串的固定长度。

对于其他平台,我建议调整此SQL Server解决方案。对于2012年之前的SQL Server,请将IIF替换为CASE

当然还有其他各种正确的方法,但这似乎是使用语法最经济的内联解决方案,而且我还没有看到更好的方法。

答案不正确

我只是想我花一点时间来解决各种来源的错误答案,以便将来的读者受益。

1. DATEDIFF(YEAR, date_of_birth, reference_date)

正如我在问题中所提到的,这似乎是许多尝试的第一种方法。不幸的是,它仅计算当年的部分差异,以便在1月1日至18日出生的婴儿在1月1日至17日期间成为1岁。 这很好但是不对。

2. FLOOR(DATEDIFF(DAY, date_of_birth, reference_date) / 365.25)

这似乎是另一种常见方法 - 也在我的问题评论中给出。

对于一名出生于2月28日的婴儿,经过适当的计算,他们将在2月28日 - 2月18日 - 他们的下一个生日年满1岁。这两个日期之间过了365天。 365/365.25 = 0.99。地板变为零。婴儿在1月1日至18日被视为1岁 - 太晚了。

进一步细化小数点后的“每年天数”除数没有帮助,因为任何除数高于365,在这种情况下将失败。 这很好但是不对。

3. DATEDIFF(HOUR, date_of_birth, reference_date) / 8766

(2)的变体是使用小时而不是天。每天有固定的小时数,因此8766/24 = 365.25。我们看到这个解决方案相当于(2)。 这很好但是不对。

4. CONVERT( INT, ROUND(DATEDIFF(HOUR, date_of_birth, reference_date) / 8766.0, 0) )

另一种变化是在小时使用四舍五入。由于相同的原因,这与(3)完全相同的缺陷,但另外如果涉及时间组件,那么它在之前的最后30分钟上升 >到了生日。 这很好但是不对。

5. DATEPART(DAYOFYEAR, ...)

其他人已经探讨过DAYOFYEAR能否提供解决方案。遗憾的是,由于闰年,标准DAYOFYEAR并未始终如一地映射到一年中的特定日期(月和月)。 这很好但是不对。

答案可能

6. FLOOR( MONTHS_BETWEEN(reference_date, date_of_birth) / 12 )

当出生日期为29-Feb(闰年),参考日期为28-Feb(非闰年)时,这个特定于Oracle的解决方案就会失效,因为它将生日视为生日非闰年28 - 2月。

这实际上可能更接近于为闰年婴儿庆祝生日时的常见惯例,但我在我的问题中已经指出,在这种情况下,生日用于计算一个人的年龄< / em>应视为1-Mar(符合英国法律)。

MONTHS_BETWEEN还有其他怪癖可能不适合年龄计算(例如,如果它用于计算年龄为几个月,例如对于小于1岁的婴儿,其中“生日“在随后几个月的同一个工作日或之后。”

这很好在某些情况下可能会被认为是对的!

摘要

当然有其他方法来实现正确的解决方案而不是这里给出的解决方案,但应注意一连串不正确的答案(以及我在此不考虑的更多过度的例子),以及任何创新或替代方案计算应该经过彻底测试并与已知可行的计算进行比较。

答案 3 :(得分:0)

伪:

const Empty =
  Symbol ()

const Node = (value = null, left = Empty, right = Empty) =>
  ({ Node, value, left, right })
  
const countNodes = (node = Empty) =>
  node === Empty
    ? 0
    : 1 + countNodes (node.left) + countNodes (node.right)
    
console.log
  ( countNodes ()                                       // 0

  , countNodes (Node (0))                               // 1

  , countNodes (Node (0, Node (1), Node (2)))           // 3

  , countNodes (Node (0, Node (1, Node (2), Node (3))
                       , Node (4, Node (5), Node (6)))) // 7
  )