我有一个列,用于在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格式存储,并且没有转换为时间戳,比较仍然有效。有人可以向我解释如何/为什么会发生这种情况,或者是否存在一些不起作用的角落情况?感谢。
答案 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_date
或to_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'
那么它将返回一行(因为这将是永远不会是真的它不会返回一行)。此外,它不会考虑9
和10
代表月份以及进行字符串比较时'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