【问题标题】:Order of Dates in Oracle DatabaseOracle 数据库中的日期顺序
【发布时间】:2017-01-04 00:27:40
【问题描述】:

我有下表记录了员工在公司的出勤情况。

    CREATE TABLE EMP_ATNDNC
    (
    WORK_DAY    DATE                         NOT NULL,
    EMP_ID      VARCHAR2(20)                 NOT NULL,
    STATUS      VARCHAR2(1)   DEFAULT '0'    NOT NULL,
    TMSTMP      TIMESTAMP,
    RCRD_VER    TIMESTAMP                    NOT NULL,

   CONSTRAINT  EMP_ATNDNC_PK PRIMARY KEY(WORK_DAY,EMP_ID),
   CONSTRAINT  EMP_ATNDNC_FK_DAY FOREIGN KEY(WORK_DAY) REFERENCES SCHOOL_OPEN_RCRD(WORK_DAY),
   CONSTRAINT  EMP_ATNDNC_FK_ID FOREIGN KEY(EMP_ID) REFERENCES EMP_RCRD(EMP_ID)
   );

截至目前,已有两条记录:

    WORK_DAY    EMP_ID     STATUS           TMSTMP                             RCRD_VER
    29-SEP-16     1234       0      27-AUG-16 05.38.46.022000000 PM       27-AUG-16 05.38.46.022000000 PM
    26-AUG-16     9999       0      26-AUG-16 04.50.04.628000000 PM       26-AUG-16 04.50.04.628000000 PM

如果我运行这个查询:

    SELECT * FROM EMP_ATNDNC WHERE WORK_DAY BETWEEN TO_DATE('24-AUG-16') AND TO_DATE('30-SEP-16');

我只得到这条记录:

    WORK_DAY    EMP_ID     STATUS           TMSTMP                          RCRD_VER
    29-SEP-16     1234       0      27-AUG-16 05.38.46.022000000 PM     27-AUG-16 05.38.46.022000000 PM

如果我运行这个查询:

    SELECT * FROM EMP_ATNDNC ORDER BY WORK_DAY ASC;

我得到这个结果:

    WORK_DAY    EMP_ID     STATUS           TMSTMP                             RCRD_VER
    29-SEP-16     1234       0      27-AUG-16 05.38.46.022000000 PM       27-AUG-16 05.38.46.022000000 PM
    26-AUG-16     9999       0      26-AUG-16 04.50.04.628000000 PM       26-AUG-16 04.50.04.628000000 PM

代替:

    WORK_DAY    EMP_ID     STATUS           TMSTMP                             RCRD_VER
    26-AUG-16     9999       0      26-AUG-16 04.50.04.628000000 PM       26-AUG-16 04.50.04.628000000 PM
    29-SEP-16     1234       0      27-AUG-16 05.38.46.022000000 PM       27-AUG-16 05.38.46.022000000 PM

为什么会这样?

【问题讨论】:

  • 发生这种情况没有任何意义。但是,由于您在评论中写道,您的问题存在拼写错误,这意味着您向我们展示的内容不是您的内容的直接复制粘贴,而是在发布前经过了一些编辑。错误可能在于编辑。魔鬼在细节中。
  • 同意上面的@MikeNakis。你这两个问题都没有意义。我能想象的唯一另一种可能性是work_day上的某种损坏索引
  • @MikeNakis 实际上在发布这个问题之前,我尝试使用 '30-SEP-2016' 和 '30-SEP-16' 来做。
  • 你能用select to_char(work_day, 'SYYYY-MM-DD') from emp_atndnc检查你现有的值吗?看起来世纪是错误的 - 可能是用 YY 面具完成了 0016 年的插入?
  • @Gerrat,是的,你是对的。问题是由于损坏的索引。我删除了表并重新创建了它,它适用于我的查询。

标签: sql date oracle11g sql-order-by


【解决方案1】:

您的日期似乎不在当前世纪,可能是因为插入的日期模型不正确。 (实际上没有任何理由继续使用两位数的年份......)

为了演示,用你的表定义:

insert into EMP_ATNDNC
values (to_date('26-AUG-16', 'DD-MON-RRRR'), 9999, 0,
  to_timestamp('26-AUG-16 04.50.04.628000000 PM', 'DD-MON-RR HH.MI.SS.FF AM'),
  to_timestamp('26-AUG-16 04.50.04.628000000 PM', 'DD-MON-RR HH.MI.SS.FF AM'));

insert into EMP_ATNDNC
values (to_date('29-SEP-16', 'DD-MON-YYYY'), 1234, 0,
  to_timestamp('27-AUG-16 05.38.46.022000000 PM', 'DD-MON-RR HH.MI.SS.FF AM'),
  to_timestamp('27-AUG-16 05.38.46.022000000 PM', 'DD-MON-RR HH.MI.SS.FF AM'));

alter session set nls_date_format = 'DD-MON-RR';
alter session set nls_timestamp_format = 'DD-MON-RR HH:MI:SS.FF3 AM';

SELECT * FROM EMP_ATNDNC WHERE WORK_DAY BETWEEN TO_DATE('24-AUG-16') AND TO_DATE('30-SEP-16');

WORK_DAY  EMP_ID               S TMSTMP                    RCRD_VER                
--------- -------------------- - ------------------------- -------------------------
26-AUG-16 9999                 0 26-AUG-16 04:50:04.628 PM 26-AUG-16 04:50:04.628 PM

WORK_DAY  EMP_ID               S TMSTMP                    RCRD_VER                
--------- -------------------- - ------------------------- -------------------------
29-SEP-16 1234                 0 27-AUG-16 05:38:46.022 PM 27-AUG-16 05:38:46.022 PM
26-AUG-16 9999                 0 26-AUG-16 04:50:04.628 PM 26-AUG-16 04:50:04.628 PM

如果您查看两个插入语句,其中一个是使用 RRRR 转换两位数年份,这对世纪做出了一些(通常有帮助的)假设,在这种情况下将“16”视为“2016”;第二个是使用YYYY,比较苛刻。

如果您使用全年格式模型查询实际日期,您会发现差异:

select to_char(work_day, 'SYYYY-MM-DD') as work_day, emp_id from emp_atndnc order by work_day;

WORK_DAY    EMP_ID             
----------- --------------------
 0016-09-29 1234                
 2016-08-26 9999                

问题并不在于您的查询(尽管它使用两位数的年份和隐式转换也依赖于会话的 NLS 设置,@TimBiegeleisen 指出这不是一个好主意)。

真正的问题在于数据。您也许可以纠正它 - 如果您也愿意做出假设,例如为本世纪之前的任何事物增加 2000 年。

但实际上,您还需要找出不良数据的来源,并阻止它发生。很可能您的插入依赖于字符串的隐式日期转换,并且不同的用户或应用程序具有不同的 NLS 设置 - 特别是 NLS_DATE_FORMAT。

您永远不应依赖依赖 NLS 设置的隐式数据转换。而且您不应该再使用两位数的年份,尽管这可能超出您的控制范围。

【讨论】:

  • 当然,我会记住这一点。然而,正如 Gerrat 在 cmets 中指出的那样,问题是由于索引损坏造成的。我删除了表并重新创建了它,它适用于我的查询
  • 您的第二个查询(使用 order by)通常不会使用索引。您如何确定索引已损坏,如果是,您为什么不重新创建它?当您重新创建表时,您是如何重新填充它的?你看过旧表中的完整年份了吗?
【解决方案2】:

BETWEEN 子句中的第二个日期将年份指定为两位数:

BETWEEN TO_DATE('24-AUG-2016') AND TO_DATE('30-SEP-16');
                                                   ^^

假设您的默认日期格式为dd-mon-yyyy,则进行以下更改可能起作用:

BETWEEN TO_DATE('24-AUG-2016') AND TO_DATE('30-SEP-2016');

但是,正如documentationTO_DATE 提到的那样:

最好始终使用 TO_DATE 指定格式掩码 (fmt)

因此,理想情况下,您应该明确提及您使用的日期格式,例如在这个查询中:

SELECT *
FROM EMP_ATNDNC
WHERE WORK_DAY BETWEEN TO_DATE('24-AUG-2016', 'dd-mon-yyyy') AND
                       TO_DATE('30-SEP-2016', 'dd-mon-yyyy')

【讨论】:

  • 不,它没有帮助。仍然得到相同的结果
  • '30-SEP-2016' 实际上是我的问题中的一个错字。我已经更正了。我试过'SELECT * FROM EMP_ATNDNC WHERE WORK_DAY BETWEEN TO_DATE('24-AUG-16', 'dd-mon-yy') AND TO_DATE('30-SEP-16', 'dd-mon-yy');'但是还是不行
  • @Steve 看看here。我的查询应该可以正常工作。
  • 感谢您的洞察力,但是正如 Gerrat 在 cmets 中指出的那样,问题是由于索引损坏造成的。我删除了表并重新创建了它,它适用于我的查询
  • @Steve 很公平,但我认为我关于 TO_DATE 的一般 cmets 是有效的,所以我会留下这个,以防其他人遇到你的问题,但不是由于索引损坏。跨度>
猜你喜欢
  • 1970-01-01
  • 2015-12-19
  • 1970-01-01
  • 2016-10-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-19
  • 1970-01-01
相关资源
最近更新 更多