sql - 如果按条件延长时间,Oracle 查询不返回任何结果

标签 sql database oracle

如果我这样做:

SELECT count(*) FROM XX where "date" >= '8-APR-2015' and "date" <= '8-APR-2016'

它会返回很多行,但如果我这样做:

SELECT count(*) FROM XX where "date" >= '8-APR-2010' and "date" <= '8-APR-2016'

它返回 0。这怎么可能?如果有的话,我会得到更多的行,因为我正在增加对检索有效的范围。有什么想法吗?

编辑:

NLS_TIMESTAMP_FORMAT 'DD-MON-RR HH.MI.SSXFF 
NLS_DATE_FORMAT DD-MON-RR

最佳答案

如果您查看这两个查询的执行计划,尤其是谓词信息,您会发现第一个查询执行的是:

---------------------------------------------------------------------------     
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |     
---------------------------------------------------------------------------     
|   0 | SELECT STATEMENT   |      |     1 |    13 |     3   (0)| 00:00:01 |     
|   1 |  SORT AGGREGATE    |      |     1 |    13 |            |          |     
|*  2 |   TABLE ACCESS FULL| XX   |     1 |    13 |     3   (0)| 00:00:01 |     
---------------------------------------------------------------------------     

Predicate Information (identified by operation id):                             

   2 - filter("date">=TO_TIMESTAMP('8-APR-2015') AND                            
              "date"<=TO_TIMESTAMP('8-APR-2016'))                               

而第二个是:

----------------------------------------------------------------------------    
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |    
----------------------------------------------------------------------------    
|   0 | SELECT STATEMENT    |      |     1 |    13 |     0   (0)|          |    
|   1 |  SORT AGGREGATE     |      |     1 |    13 |            |          |    
|*  2 |   FILTER            |      |       |       |            |          |    
|*  3 |    TABLE ACCESS FULL| XX   |     1 |    13 |     3   (0)| 00:00:01 |    
----------------------------------------------------------------------------    

Predicate Information (identified by operation id):                             
---------------------------------------------------                             

   2 - filter(NULL IS NOT NULL)                                                 
   3 - filter("date">=TO_TIMESTAMP('8-APR-2010') AND                            
              "date"<=TO_TIMESTAMP('8-APR-2016'))                               

并且由于 NULL IS NOT NULL 永远不会为真,因此得到零行。但这取决于您的 NLS 设置。对于其他格式掩码,它没有该过滤步骤。

如果您查看这些 to_timestamp() 调用是如何使用您的格式 NLS 设置进行评估的,您就可以了解发生了什么:

alter session set nls_timestamp_format = 'DD-MON-RR HH.MI.SSXFF';

select to_char(to_timestamp('8-APR-2015'), 'YYYY-MM-DD') as from_1,
  to_char(to_timestamp('8-APR-2016'), 'YYYY-MM-DD') as to_1,
  to_char(to_timestamp('8-APR-2010'), 'YYYY-MM-DD') as from_2,
  to_char(to_timestamp('8-APR-2016'), 'YYYY-MM-DD') as to_2
from dual;

FROM_1     TO_1       FROM_2     TO_2     
---------- ---------- ---------- ----------
2015-04-08 2016-04-08 2020-04-08 2016-04-08

第一对日期看起来不错 - 2015 年早于 2016 年。但是第二个“来自”日期是 2020 年,而不是 2010 年;并且由于 Oracle 足够聪明,意识到 2020 年晚于 2016 年,它知道不可能有匹配的数据,并添加不可能的条件来短路,避免冗余数据访问。

将其与正确处理四位数年份的掩码进行比较:

alter session set nls_timestamp_format = 'DD-MON-RRRR HH.MI.SSXFF';

select to_char(to_timestamp('8-APR-2015'), 'YYYY-MM-DD') as from_1,
  to_char(to_timestamp('8-APR-2016'), 'YYYY-MM-DD') as to_1,
  to_char(to_timestamp('8-APR-2010'), 'YYYY-MM-DD') as from_2,
  to_char(to_timestamp('8-APR-2016'), 'YYYY-MM-DD') as to_2
from dual;

FROM_1     TO_1       FROM_2     TO_2     
---------- ---------- ---------- ----------
2015-04-08 2016-04-08 2010-04-08 2016-04-08

现在“日期”中的第二个是正确的。

差异缩小到 how the RR format mask behaves ,尽管没有真正记录这种特定行为。

实际发生的事情取决于 Oracle 在尝试灵活解释格式掩码方面的帮助。正如文档中所说,就在日期时间格式元素表的下方,“Oracle 数据库以一定的灵 active 将字符串转换为日期”——但其效果有时有点出乎意料。

实际上是 after RR 将其丢弃。您可以通过这个小演示看到这一点:

with t as (
  select 1998 + level as year from dual connect by level < 16
)
select year, to_char(to_timestamp(to_char(year), 'RR HH'), 'YYYY-MM-DD HH24:MI:SS')
from t;

      YEAR TO_CHAR(TO_TIMESTAM
---------- -------------------
      1999 1999-04-01 00:00:00
      2000 2000-04-01 00:00:00
      2001 2020-04-01 01:00:00
      2002 2020-04-01 02:00:00
      2003 2020-04-01 03:00:00
      2004 2020-04-01 04:00:00
      2005 2020-04-01 05:00:00
      2006 2020-04-01 06:00:00
      2007 2020-04-01 07:00:00
      2008 2020-04-01 08:00:00
      2009 2020-04-01 09:00:00
      2010 2020-04-01 10:00:00
      2011 2020-04-01 11:00:00
      2012 2020-04-01 12:00:00
      2013 2013-04-01 00:00:00

RR 模型似乎只查看年份的前两位数字,但在有用时它也会尝试为您处理四位数年份,这适用于 2015 年和 2016 年.如果面具没有时间成分,它会在其他年份有效。但它确实如此,而且它更愿意使用掩码的 HH 部分来解释四位数年份的第三和第四个字符。

因此对于 2010 年,它看到“10”,决定可以将其解释为 HH 值,然后只转换剩余的两位数 ' 20' 使用 RR 掩码 - 它被视为 2020 年。所以你最终在 2020 年 4 月 8 日上午 10 点结束。同样的事情发生在 2000 年(尽管你无法区分)到 2012 年. 当您到达 2013 年时,“13”对于 HH 掩码不再有效,因此它会返回到将所有四位数字视为年份。如果 NLS 格式桅杆有 HH24,那么它也会在 2013-2023 年“中断”。


道德是永远不要依赖 NLS 设置。 (并且永远不要使用 2 位数年份或 2 位数年份掩码)。将字符串显式转换为日期/时间戳:

where "date" >= to_timestamp('8-APR-2015', 'DD-MON-YYYY')
and "date" <= to_timestamp('8-APR-2016', 'DD-MON-YYYY');

...虽然最好不要使用月份名称,因为它们也依赖于 NLS,但你可以指定你想要英文翻译:

where "date" >= to_timestamp('8-APR-2015', 'DD-MON-YYYY', 'NLS_DATE_LANGUAGE=ENGLISH')
and "date" <= to_timestamp('8-APR-2016', 'DD-MON-YYYY', 'NLS_DATE_LANGUAGE=ENGLISH');

或者对于固定值更好,使用 ANSI 日期/时间戳文字:

where "date" >= timestamp '2010-04-08 00:00:00'
and "date" <= timestamp '2016-04-08 00:00:00';

关于sql - 如果按条件延长时间,Oracle 查询不返回任何结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36800231/

相关文章:

mysql - 在 mysql 存储过程中执行保证和失败回滚

mysql - 当计算机意外关闭时,mysql 数据会发生什么情况?

python - cx_Oracle.InterfaceError : Unable to acquire Oracle environment handle in linux

mysql - 当我尝试离线连接 Oracle 时无法对离线数据库执行命令

oracle - "Alter session"通过计划作业上的登录触发器

mysql - MySQL 中可以预定义查询吗?

mysql - 使用 where 条件选择要插入到表中的列

php - 我想向 mysql 日期行添加 30 天

java - 结果集导航是否取决于所使用的驱动程序类型?

mysql - 如何在 substring_index 中传递多个分隔符