【问题标题】:Mysterious change of year from 2050 to 1950从 2050 年到 1950 年的神秘年份变化
【发布时间】:2013-05-27 10:19:50
【问题描述】:

发生了一些不寻常的事情。迁移时,我有一些日期超过 2050 年(例如,20.05.2050、21.11、2051),但由于某种原因,Oracle 将它们更改为 1950、1951 等。如果你问我,这很烦人,而且是客户报告的。这些是退休日期,所以显然不能在 1950 年代和 1960 年代

原始表中的日期类似于 YYYYMMDD 格式,即 20500130。所以这是我的合并语句

MERGE INTO employment_data emp                                                     
   USING   temp_02 src
   ON      (TO_NUMBER(src.id) = emp.id)
   WHEN MATCHED THEN UPDATE SET 
       emp.retirement_day = DECODE(TO_NUMBER(src.retirement), 0, NULL, TO_DATE(src.retirement, 'YYYY.MM.DD'));

其他日期都可以(例如 20301204),但 2050 年之后的任何日期都会出错。知道如何解决这个烦恼吗?

提前谢谢 :-)

【问题讨论】:

  • 格式是什么?你提到YYYYMMDD(20500130),然后使用YYYY.MM.DD,还提到(04122030)的例子,我猜是MMDDYYYY。
  • 是的,有些东西没有加起来。如果您在 2050.05.20 这样的值上调用 TO_NUMBER(),您将得到一个无效数字异常,因此 DECODE 的第一个条件应该抛出非零日期。
  • 现在,有趣的部分来了。如果我从 temp_02 查询中运行 SELECT TO_DATE(retirement, 'YYYY.MM.DD') ,那么我会得到正确的结果。但是,如果我在合并查询中不解码,在数据库中插入数据并运行选择查询进行检查,那么我会看到 1950 年代被插入到数据库中。

标签: oracle plsql oracle11g plsqldeveloper


【解决方案1】:

问题在于DECODE 强制进行隐式转换。 DECODE函数的返回数据类型由rule决定:

DECODE(expr, search, result [, search, result]* [, default])

Oracle 在比较之前自动将expr 和每个search 值转换为第一个search 值的数据类型。 Oracle 自动将返回值转换为与第一个result 相同的数据类型。如果第一个result 的数据类型为CHAR 或如果第一个result 为空,则Oracle 将返回值转换为数据类型VARCHAR2

在 Oracle 中,NULL 的默认数据类型为 VARCHAR2,因此您的 DECODE 表达式相当于:

DECODE(TO_NUMBER(src.retirement), 0, 
       NULL, 
       TO_CHAR(TO_DATE(src.retirement, 'YYYY.MM.DD')));

没有格式,TO_CHAR 使用您的NLS_DATE_FORMAT 会话设置,可能是默认的DD-MON-RR,它会丢失世纪信息。

正如related question 中所述,RR 的规则是 as follow

  • 如果指定的两位数年份是 00 到 49,则
    • 如果当前年份的后两位数字为 00 到 49,则返回的年份与当前年份的前两位数字相同。
    • 如果当前年份的后两位是 50 到 99,则返回年份的前 2 位比当前年份的前 2 位大 1。
  • 如果指定的两位数年份是 50 到 99,则
    • 如果当前年份的后两位为 00 到 49,则返回年份的前 2 位比当前年份的前 2 位小 1。
    • 如果当前年份的后两位数字是 50 到 99,则返回的年份与当前年份的前两位数字相同。

这就是为什么 2050 年之前的日期没有触发神秘行为的原因!

【讨论】:

    【解决方案2】:

    我已经想出了这个方法,并认为它可以帮助其他人。

    我不确定这是 Oracle 设置的问题还是 PL/SQL Developer 的一些顽皮问题。无论如何,我先检查了 NLS_DATE_FORMAT。

    SELECT value FROM v$nls_parameters
    WHERE parameter = 'NLS_DATE_FORMAT'
    

    这表明它被设置为 DD-MON-RR。

    现在不更改配置,我想更改迁移会话的设置,并且只更改该会话的设置。

    Alter SESSION SET nls_date_format = 'dd-mon-yyyy';
    

    然后运行合并语句。

    MERGE INTO employment_data emp                                                     
       USING   temp_02 src
       ON      (TO_NUMBER(src.id) = emp.id)
       WHEN MATCHED THEN UPDATE SET 
           emp.retirement_day = TO_CHAR(TO_DATE(src.retirement,'YYYY.MM.DD'));
    

    这很有效。

    【讨论】:

    • 为您提供的几条规则: 1. 切勿将日期存储或操作为 DATE 数据类型以外的任何内容。 2. 永远不要依赖隐式日期转换——你应该在任何转换中始终指定日期格式。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多