Oracle to_date,从 MM-DD-YYYY 中减去 DDMMYY

Posted

技术标签:

【中文标题】Oracle to_date,从 MM-DD-YYYY 中减去 DDMMYY【英文标题】:Oracle to_date, subtracting DDMMYY from MM-DD-YYYY 【发布时间】:2018-01-09 13:59:36 【问题描述】:

我试图让 30 岁以下的每个人都参与 SQL 查询。我在我的数据库中的出生日期是 DDMMYY 格式,我试图像这样从 SYSDATE 中减去它(其中 10957 是 30 年的天数):

 ((TO_DATE(SYSDATE, 'MM-DD-YYYY') 
  - TO_DATE(BIRTH_DATE_FROM_DB, 'DDMMYY'), 'MM-DD-YYYY')) < 10957 

用一个有世纪信息的日期减去一个没有世纪信息的日期显然是个问题。这是社会安全号码的出生日期部分,所以我可以根据号码的另一部分来判断是 1900 年代还是 2000 年代。有没有办法在此基础上插入额外的YY?

这是我用来确定世纪的代码(挪威社会安全号码以 500 或以上开头的每个人都是 2000 年以后出生的):

(SUBSTR(PERSON_NR, 1, 3) > 499
AND LENGTH(PERSON_NR) = 5))

我知道这是一个复杂的问题,可能很难给出准确的答案,但如果您能指出正确的方向,我将不胜感激。

【问题讨论】:

为什么要将日期存储在VARCHARC 列中? 在birth_date_from_db 上使用年份格式RR。我的意思是 TO_DATE(BIRTH_DATE_FROM_DB, 'DDMMRR') 对于日期比较,您可以使用:MONTHS_BETWEEN(sysdate, TO_DATE(BIRTH_DATE_FROM_DB, 'DDMMRR')) sysdate 已经是一个日期,因此您不应使用 to_date(sysdate),就像使用 to_number(123)to_char('cheese') 一样。如果要四舍五入,请使用trunc(sysdate) Possibly related,虽然在那个版本中规则似乎有点复杂... 【参考方案1】:

Norwegian identification number 比您建议的要复杂,正如 in this question 所提到的。您可以从中调整答案;使用 CTE 在该答案上生成相同的虚拟数据加上几个 D 数字(并且没有无用的未来日期值):

with t42 (ssn) as (
  select '12104900000' from dual
  union all select '12105099999' from dual
  union all select '01010000001' from dual
  union all select '02029949902' from dual
  union all select '03035450003' from dual
  union all select '04049974904' from dual
  union all select '05050050005' from dual
  union all select '07074090007' from dual
  union all select '08089999908' from dual
  union all select '01121799908' from dual
  union all select '48089999908' from dual
  union all select '52104900000' from dual
)
select ssn
from t42
where months_between(trunc(sysdate), to_date(
      case
        when to_number(substr(ssn, 1, 2)) > 31
          then to_char(to_number(substr(ssn, 1, 2)) - 40, 'FM00')
        else substr(ssn, 1, 2)
      end
    || substr(ssn, 3, 2)
    || case
        when to_number(substr(ssn, 7, 3)) between 0 and 499
          and to_number(substr(ssn, 5, 2)) between 0 and 99 then '19'
        when to_number(substr(ssn, 7, 3)) between 500 and 749
          and to_number(substr(ssn, 5, 2)) between 54 and 99 then '18'
        when to_number(substr(ssn, 7, 3)) between 500 and 999
          and to_number(substr(ssn, 5, 2)) between 0 and 39 then '20'
        when to_number(substr(ssn, 7, 3)) between 900 and 999
          and to_number(substr(ssn, 5, 2)) between 40 and 99 then '19'
      end
    || substr(ssn, 5, 2), 'DDMMYYYY')
  ) < 360;

返回 12 个虚拟值中的 5 个:

SSN        
-----------
02029949902
05050050005
08089999908
01121799908
48089999908

或者使用转换后的日期和年龄:

with ...
select ssn, to_char(dob, 'YYYY-MM-DD') as dob,
  trunc(months_between(trunc(sysdate), dob)) as age_in_months,
  trunc(months_between(trunc(sysdate), dob)/12) as age_in_years
from (
  select ssn,
    to_date(
      case
        when to_number(substr(ssn, 1, 2)) > 31
          then to_char(to_number(substr(ssn, 1, 2)) - 40, 'FM00')
        else substr(ssn, 1, 2)
      end
    || substr(ssn, 3, 2)
    || case
        when to_number(substr(ssn, 7, 3)) between 0 and 499
          and to_number(substr(ssn, 5, 2)) between 0 and 99 then '19'
        when to_number(substr(ssn, 7, 3)) between 500 and 749
          and to_number(substr(ssn, 5, 2)) between 54 and 99 then '18'
        when to_number(substr(ssn, 7, 3)) between 500 and 999
          and to_number(substr(ssn, 5, 2)) between 0 and 39 then '20'
        when to_number(substr(ssn, 7, 3)) between 900 and 999
          and to_number(substr(ssn, 5, 2)) between 40 and 99 then '19'
      end
    || substr(ssn, 5, 2), 'DDMMYYYY') as dob
  from t42
)
where months_between(trunc(sysdate), dob) < 360;

SSN         DOB        AGE_IN_MONTHS AGE_IN_YEARS
----------- ---------- ------------- ------------
02029949902 1999-02-02           227           18
05050050005 2000-05-05           212           17
08089999908 1999-08-08           221           18
01121799908 2017-12-01             1            0
48089999908 1999-08-08           221           18

【讨论】:

谢谢!你完全正确。当我重写您的代码以考虑到 SSN 在我的数据库中的出生日期和编号分为两个不同的列时,它会起作用。感谢您的详尽回答。 Stack Overflow 很棒,人们非常乐于助人! :)【参考方案2】:

使用case when 并连接字符串:

with t (person, person_nr, birth_date_from_db) as (
    select 'Mark', '501ABC', '300403' from dual union all
    select 'Paul', '417PQR', '300403' from dual )
-- end of test data    
select person, person_nr,
       months_between(
         trunc(sysdate),
         to_date(substr(birth_date_from_db, 1, 4)
           || case when substr(person_nr, 1, 3) > 499 then '20' else '19' end
           || substr(birth_date_from_db, 5, 2), 'DDMMYYYY')) / 12 as age
  from t

马克 14 岁,保罗 114 岁:

PERSON PERSON_NR        AGE
------ --------- ----------
Mark   501ABC    14.6935483
Paul   417PQR    114.693548

【讨论】:

谢谢。这似乎是正确的解决方案,但我在将其作为 WHERE 子句运行时遇到了问题。我收到错误 ORA-01847 day of month must be between 1 and last day of month。这似乎是同一个问题:***.com/questions/22439654/… @ThomasTallaksen - 你看到the D-number issue mentioned on this answer了吗?我怀疑您需要完全遵守 SSN 规则,而不仅仅是依赖三位数的个人号码。 这无疑为我指明了正确的方向。但正如@AlexPoole 指出的那样,挪威的社会安全号码比我最初想象的要复杂。

以上是关于Oracle to_date,从 MM-DD-YYYY 中减去 DDMMYY的主要内容,如果未能解决你的问题,请参考以下文章

oracle中的to_date函数

oracle中to_date详细用法示例(oracle日期格式转换)

Impala 是不是有类似 to_date (oracle) 的功能?

关于Oracle中to_date的用法

在android中,是不是可以将日期对象格式化为mm-dd-yy?如果是,那么如何? [复制]

oracle日期格式转换 to_date()