如果条件延长时间,则Oracle查询不返回结果

时间:2016-04-22 17:45:29

标签: 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

1 个答案:

答案 0 :(得分:5)

如果你查看两个查询的执行计划,特别是谓词信息,你会看到第一个查询的执行计划:

---------------------------------------------------------------------------     
| 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设置。使用其他格式掩码时,它没有该过滤步骤。

如果您了解如何使用您的格式NLS设置评估这些to_timestamp()来电,您就可以了解所发生的事情:

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年之前。但是第二对日期来自&#39;已经到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

现在是第二个来自&#39; date&#39;是对的。

差异低至how the RR format mask behaves,但这种特定行为并未真正记录。

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

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年,它会看到&#39; 10&#39;,决定可以将其解释为HH值,这样做,然后才会转换剩余的两位数&#39; 20&#39;使用RR面具 - 它被视为2020年。所以你最终会在2020年4月8日上午10点结束。2000年也会发生同样的事情(虽然你不能说出差异)直到2012年。当你进入2013年,&#39; 13&#39;对于HH掩码不再有效,因此它会将所有四位数视为年份。如果NLS格式桅杆有HH24那么它就会“破坏”#39}。也是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';