如果我这样做:
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
答案 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';