比较Oracle中的时间戳

时间:2017-02-07 10:35:24

标签: sql oracle timestamp

我有一个列,用于在Oracle 11g Express的数据库中的VARCHAR2列中存储日期和时间数据,格式为:

9/30/2016 14:00:00

我正在尝试在时间范围之间获取数据的方法。我发现了以下两种方式:

select * 
from dummy
WHERE starttime > '9/30/2016 14:00:00' 
AND starttime < '9/30/2016 17:00:00' 
order by starttime;

select * 
from dummy 
WHERE to_timestamp(starttime, 'mm/dd/yyyy hh24:mi:ss') > TO_TIMESTAMP('9/30/2016 14:00:00', 'mm/dd/yyyy hh24:mi:ss') 
AND TO_TIMESTAMP(starttime, 'mm/dd/yyyy hh24:mi:ss') < TO_TIMESTAMP('9/30/2016 17:00:00', 'mm/dd/yyyy hh24:mi:ss');

我想知道第一种方法是如何工作的,因为列启动时间以VARCHAR格式存储,并且没有转换为时间戳,比较仍然有效。有人可以向我解释如何/为什么会发生这种情况,或者是否存在一些不起作用的角落情况?感谢。

5 个答案:

答案 0 :(得分:3)

这是有效的,因为您没有月份和年份或一位数和两位数天的问题。想想“9/30/2016 14:00:00”到“9/30/2016 17:00:00”范围内的任何字符串。他们都必须从'9/30/2016 1'开始。

如果在'9/30/2016 14:00:00'到'10 / 30/2016 17:00:00'的范围内,您根本找不到任何记录,因为该字符串会有以somthing&gt; ='9'和&lt; ='1'开头,这是不可能的。

因此,特定日期内的狭窄范围将您拯救到此: - )

答案 1 :(得分:1)

将varchar存储的日期设置为聪明...

如果您不需要跨越一年的边界,您的第一种方法就可以了。数字从左到右比较(因为文本)。除非你存储为“YYYY-MM-DD HH24:MI:SS”,否则你会遇到问题。

2个选项,将该存储更改为DATE,或在WHERE子句中使用to_dateto_timestamp(我建议to_date

答案 2 :(得分:1)

将日期/时间存储为字符串或数字是个坏主意。优化器不知道域,因此在尝试估计基数时,您没有给优化器最好的机会。例如,请考虑以下两个日期

Dec 31st 2016
Jan 1st 2017

如果您将这些存储为数字,则可以使用

20170101 and 20161231

那么它们之间的天数是多少?使用数字,你得到

20170101 - 20161231
= 8870

然而,真实的(基于日期的)答案是一个。

虽然您的列可以TO_DATE()CAST,但您现在面临无法使用某些优化的风险,例如索引,分区修剪,布隆过滤等。

所以我强烈建议使用正确的数据类型。

答案 3 :(得分:1)

将值存储在VARCHAR列中意味着您将进行字符串比较:

SELECT * 
FROM   dummy
WHERE  starttime > '9/30/2016 14:00:00' 
AND    starttime < '10/30/2016 17:00:00' 
ORDER BY starttime;

这将查看开始时间并逐个字符地考虑它,如果第一个字符大于'9'且小于'1'那么它将返回一行(因为这将是永远不会是真的它不会返回一行)。此外,它不会考虑910代表月份以及进行字符串比较时'09/30/2016' < '09/31/1900' < '10/30/2016'

即使您将值存储在TIMESTAMP列中,使用字符串文字也是一个坏主意:

SELECT * 
FROM   dummy
WHERE  starttime > '9/30/2016 14:00:00' 
AND    starttime < '9/30/2016 17:00:00' 
ORDER BY starttime;

这可行,因为Oracle将使用会话参数TO_TIMESTAMP( time, format_mask )作为格式掩码执行隐式转换(使用NLS_TIMESTAMP_FORMAT)。

因此,您的查询将(假设TIMESTAMP数据类型)有效(尽管Oracle将以更有效的方式实现它):

SELECT * 
FROM   dummy
       CROSS JOIN
       ( SELECT value AS format_mask
         FROM NLS_SESSION_PARAMETERS
         WHERE PARAMETER = 'NLS_TIMESTAMP_FORMAT' ) nls
WHERE  starttime > TO_TIMESTAMP( '9/30/2016 14:00:00', nls.format_mask )
AND    starttime < TO_TIMESTAMP( '9/30/2016 17:00:00', nls.format_mask )
ORDER BY starttime;

NLS_TIMESTAMP_FORMAT是会话参数 - 这意味着每个用户可以在自己的会话中为此参数设置自己的值,如果一个用户将其更改为YYYY-MM-DD"T"HH24:MI:SS.ZZZ"Z"(即ISO8601格式),则如果没有对您的查询进行任何更改,您的查询将会针对该用户(而不是其他未更改过的用户)中断。

不是使用字符串文字和隐式转换,最好是显式设置您期望的格式掩码或使用ANSI TIMESTAMP字面值:

SELECT * 
FROM   dummy
WHERE  TO_TIMESTAMP( starttime, 'MM-DD-YYYY HH24:MI:SS' ) > TIMESTAMP '2016-09-30 14:00:00' 
AND    TO_TIMESTAMP( starttime, 'MM-DD-YYYY HH24:MI:SS' ) < TIMESTAMP '2016-09-30 17:00:00'
ORDER BY TO_TIMESTAMP( starttime, 'MM-DD-YYYY HH24:MI:SS' );

然后,您将从TO_TIMESTAMP( starttime, 'MM-DD-YYYY HH24:MI:SS' )上的基于函数的索引中受益。

更好的是,将您的列转换为正确的TIMESTAMP格式,然后您不需要基于函数的索引,只需使用TIMESTAMP文字作为边界,而无需转换函数

答案 4 :(得分:0)

在 Oracle 上,您也可以使用:

SELECT * FROM table 
WHERE field BETWEEN TRUNC(SYSDATE - 6) AND SYSDATE