【问题标题】:Calculating Age from Birthday - TSQL, Oracle, and any others从生日计算年龄 - TSQL、Oracle 等
【发布时间】:2018-07-23 05:20:06
【问题描述】:

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

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

也就是说,在 YEAR 参数的情况下,DATEDIFF 计算 1-Januaries 越过的次数。使用MONTH 参数,它将计算当月的第一天数。

因此,例如,17 年 12 月 31 日出生的婴儿已经“1 岁”,而实际上他们要到 18 年 12 月 31 日才满 1 岁,而他们目前是 0 岁。

这样使用DATEDIFF函数就相当于写YEAR(GETDATE()) - YEAR(date_of_birth),当然没有考虑人的生日在一年中的哪个位置。

在这里查看了其他问题的一个很好的样本,似乎没有一个确定的答案,而且有很多答案是完全错误的。

所以我的问题是,给定一个人的出生日期和参考日期,计算一个人截至该参考日期的年龄的最简单表达式是什么?

我正在寻找以下术语的答案:

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

  • 答案应该是可靠的,并且没有错误的极端情况 - 但这些情况并不常见或不可信。它应该适用于新生婴儿和数百岁的人,应该应对闰年,并且理想情况下应该能够将出生前的时期表示为负数(这个人成为 -出生前一天满 1 岁)。

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

  • 对于那些生日在 2 月 29 日的人,如果参考年是闰年,则应将其年龄视为 2 月 29 日递增,如果参考年是闰年,则应将其年龄视为 3 月 1 日递增参考年不是闰年。

  • 答案应忽略出生日期或参考日期的任何时间组成部分 - 即,应假定此人出生于午夜,并且其年龄应在随后生日的午夜递增。

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

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

令我震惊的是,对给定链接的接受答案是混乱的,并且以不正确的实现开头,并且上面的 cmets 指的是它的实现不正确 - 只有在我仔细阅读之后我才看到它已更新为正确的实现(夹在与我的问题无关的“十进制年龄”处理之间)。第三个答案也吸引了大量赞成票,但也是不正确的。还有无数其他质量参差不齐的答案。

我还要求一个答案,理想情况下,以某种方式正确处理“负年龄”(参考日期在出生日期之前),而链接的答案没有。因此,虽然我的问题处理的是同一个主题,但它不是重复的,因为我已经施加了该问题中不存在的几个额外限制,这将使(以一种或另一种方式)对该旧问题给出的所有答案无效问题。

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

【问题讨论】:

  • 完全依赖于数据库。我怀疑你想要一个“sql-server”标签。在 Postgres 中,您可以使用 age()
  • @GordonLinoff,我确实想要“sql-server”标签,但正如我所说,我还想要任何其他平台的答案。我注意到您对 Postgres 的回答!理想情况下,这应该为所有平台生成一些明确的答案 - 是其他人的问题促使我问这个问题。
  • @AaronDietz,是的,对于 2017 年 2 月 28 日出生的婴儿,他们要到 18 年 3 月 1 日才满 1 岁。从 2017 年 2 月 28 日到 2018 年 2 月 28 日之间有 365 天。 365/365.25=0.99...
  • 这似乎可能会吸引到与this existing question 类似的一轮答案,无论您如何谨慎地表达您的要求......

标签: sql sql-server oracle date


【解决方案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,也许它可以帮助你,

【讨论】:

  • 我对此表示赞同,但请查看我自己的答案,我现在起草了该答案以将所有内容整合在一起,并处理此功能的微妙之处。
  • 感谢您的支持,我修改了答案以处理 2 月 29 日。
【解决方案2】:

在 Oracle 中,我使用了这样的功能;我已经阅读了您提到的条件(即您应该能够在 WHERE 子句中使用它;好吧,您可以在那里使用一个函数。如果您不想要它,使用没有问题整个代码也是如此,但那会看起来)。输出可以修改;我用的是那个格式,你可以随意更改。

你在这里:

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.

Scott 的 EMP 表:

SQL> alter session set nls_date_format = 'dd.mm.yyyy';

Session altered.

SQL> select ename, hiredate, f_age(hiredate) result
  2  from emp;

ENAME      HIREDATE   RESULT
---------- ---------- ------------------------------
KING       17.11.1981 36 years  2 months 27 days
BLAKE      01.05.1981 36 years  9 months 12 days
CLARK      09.06.1981 36 years  8 months  4 days
JONES      02.04.1981 36 years 10 months 11 days
SCOTT      09.12.1982 35 years  2 months  4 days
FORD       03.12.1981 36 years  2 months 10 days
SMITH      17.12.1980 37 years  1 months 27 days
ALLEN      20.02.1981 36 years 11 months 24 days
WARD       22.02.1981 36 years 11 months 22 days
MARTIN     28.09.1981 36 years  4 months 16 days
TURNER     08.09.1981 36 years  5 months  5 days
ADAMS      12.01.1983 35 years  1 months  1 days
JAMES      03.12.1981 36 years  2 months 10 days
MILLER     23.01.1982 36 years  0 months 21 days

14 rows selected.

SQL>

【讨论】:

    【解决方案3】:

    正确答案

    根据给出的来源拼凑而成,这是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 解决方案。对于 SQL Server 2012 之前的版本,请将 IIF 替换为 CASE

    当然还有很多其他的正确方式,不过这似乎是语法上最经济的内联解决方案,我没见过更好的。


    错误答案

    我只是想花一点时间来解决各种来源中的错误答案,以造福未来的读者。

    1. DATEDIFF(YEAR, date_of_birth, reference_date)
    

    正如我在问题中提到的,这似乎是许多人尝试的第一种方法。不幸的是,它只计算日期中年份部分的差异,因此 2017 年 12 月 31 日出生的婴儿在 2018 年 1 月 1 日满 1 岁。 很好,但不正确。

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

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

    对于 17 年 2 月 28 日出生的婴儿,正确计算他们在 2018 年 2 月 28 日(他们的下一个生日)满 1 岁。两个日期之间相隔 365 天。 365 / 365.25 = 0.99。地板它变为零。婴儿在 2018 年 3 月 1 日被视为 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 )
    

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

    这实际上可能更接近于闰年婴儿庆祝生日的共同惯例,但我在我的问题中指定了在这种情况下,生日是为了计算一个人的年龄 应被视为 1-Mar(这与英国法律一致)。

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

    很好在某些情况下可能被认为是正确的!


    总结

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

    【讨论】:

    • 我认为这是我见过的最美丽的消息之一。
    【解决方案4】:

    伪:

    if get month - date of birth month>=0 && get day - birthday >=0
            Age = get year - date of birth year
    else
            Age = get year - date of birth year - 1
    

    【讨论】:

      猜你喜欢
      • 2012-02-06
      • 2011-03-02
      • 2012-09-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多