在 where 子句问题中的 Oracle 日期比较

Posted

技术标签:

【中文标题】在 where 子句问题中的 Oracle 日期比较【英文标题】:Oracle date comparison in where clause issue 【发布时间】:2017-11-13 21:42:28 【问题描述】:

每当我重新排列 where 子句的条件时,我都会看到不同的查询结果;我试图弄清楚为什么会这样。我被要求尝试弄清楚发生了什么,因此我可以使用一些帮助来确定可能导致问题的原因。需要明确的是,我们在运行 select 语句时没有收到错误,而是收到了意外的数据结果。这是一个 Oracle 数据库(我相信是 10-g)。对于同一个字段,我们有两个条件。首先是确保结果大于 30 年前(这个数据库不是我自己设置的,但是拥有该应用程序的团队决定将 created_date 更改为过去的 30 是个好主意).... .第二个是更具体的日期范围,即 9 月 1 日至 10 月 1 日。

SELECT created_date
FROM tableName
WHERE 
created_date > sysdate - 11000 -- 30 years in the past
AND short_name like 'FOO%'
AND trunc(created_date) BETWEEN TO_DATE('09-01-2017', 'MM-DD-YYYY')  
AND TO_DATE('10-01-2017', 'MM-DD-YYYY')

在运行上述查询时,我们会在 where 子句末尾收到带有 created_date 的结果。

但是,当我将 created_date 和 sysdate 之间的比较放在两个日期之间的比较之后时,我们会收到正确的结果。

SELECT created_date
FROM tableName
WHERE 
short_name like 'FOO%'
AND trunc(created_date) BETWEEN TO_DATE('09-01-2017', 'MM-DD-YYYY')  
AND TO_DATE('10-01-2017', 'MM-DD-YYYY')
AND created_date > sysdate - 11000 -- 30 years in the past

此外,我将“AND trunc(created_date) BETWEEN TO_DATE('09-01-2017', 'MM-DD-YYYY')”的 trunc 部分删除为“AND created_date BETWEEN TO_DATE('09-01-2017 ', 'MM-DD-YYYY')" 并在之前或之后通过 sysdate 比较收到预期结果。

SELECT created_date
    FROM tableName
    WHERE 
    created_date > sysdate - 11000 -- 30 years in the past
    AND short_name like 'FOO%'
    AND created_date BETWEEN TO_DATE('09-01-2017', 'MM-DD-YYYY')  
    AND TO_DATE('10-01-2017', 'MM-DD-YYYY')

编辑:

    数据库中有许多记录。只有 1 个结果的 created_date 为 22-Sep-17。

    SELECT created_date
    FROM tableName
    WHERE 
    created_date > sysdate - 11000 -- 30 years in the past
    AND short_name like 'FOO%'
    AND trunc(created_date) BETWEEN TO_DATE('09-01-2017', 'MM-DD-YYYY')  
    AND TO_DATE('10-01-2017', 'MM-DD-YYYY')
    

生成 25 个条目,created_date 为 2004 年 2 月 17 日,1 个条目为 2017 年 9 月 22 日。

    我理解 sysdate - 11,000 是 30 年多一点。目前的要求是检查 11000 天而不是 30 年。我在年度估计中偏离了。使用 sysdate 是因为被认为是旧的或更新的记录将被放置在比 sysdate - 11000 更远的 created_date 中。我知道当此查询中已经存在另一个日期范围条件时,这没有任何意义,但解决方案已经构建并且送走了。为了做出改变,我需要弄清楚为什么我会得到我得到的,这就是我在这里发帖的原因。 第二个日期范围(在本例中为 09-01-2017 和 10-01-2017)是传入的参数,因此它们可以是从过去任何一天到今天的任何范围。我知道这是一个糟糕的设计,但我没有构建它。

这些将为之间的值提供两个微妙不同的结果 2017-10-01 00:00:01 和 2017-10-01 23:59:59。使用 TRUNC 的查询 将包括没有 TRUNC 的范围 排除在外。

预期结果是在 17 年 9 月 22 日的日期范围内返回记录;但是,我们收到了 25 条记录,所有记录的日期均为 2004 年 2 月 17 日......如果我删除 created_date > sysdate - 11000,我也会收到唯一的一条记录。

【问题讨论】:

你能举一个不符合你条件的日期例子吗?也许在选择时添加 to_char(created_date,'MM-DD-YYYY') 以便我们知道格式?为什么您需要在两者之间进行 trunc(Created_Date) 呢?好的,我知道为什么如果 create_Date 是 '10-01-2017 13:50' 或其他东西,你会想要它.. 只会伤害索引的使用。 我们看到很多问题,例如“为什么 Oracle 要做这件奇怪的事情?”标准答案是甲骨文没有做那件奇怪的事,只是你不完全了解甲骨文在做什么。因此,请发布一个可重现的测试用例,其中包含演示此行为的示例数据。 知道您的数据库版本:select * from v$version,并请提供示例数据来证明您遇到的问题。 如果你想减去 30 年,那么不要减去天,而是减去年:sysdate - interval '30' year 30 年不是问题,但返回的日期是 2004 年和 2017 年,这表明在 09-01-2017 和 10-1-2017 之间的第二个日期范围正在忽略。 【参考方案1】:

首先要确保结果大于30年前

您不是减去 30 年,而是减去 11,000 天,大约(取决于闰年)30 年零 42 天。

如果您想减去 30 年,请使用:

ADD_MONTHS( date_value, -30 * 12 )

第二个是更具体的日期范围,即 9 月 1 日至 10 月 1 日。

这使得第一个条件无关紧要 - 如果它介于 2017-09-012017-10-01 之间,那么它将在过去 30 年内,您不需要第一个条件。

我将“AND trunc(created_date) BETWEEN TO_DATE('09-01-2017', 'MM-DD-YYYY')”的 trunc 部分删除为“AND created_date BETWEEN TO_DATE('09-01-2017', 'MM-DD-YYYY')”,并在之前或之后通过 sysdate 比较收到了预期的结果。

对于2017-10-01 00:00:012017-10-01 23:59:59 之间的值,这些将给出两个略有不同的结果。使用TRUNC 的查询将包含该范围,如果没有TRUNC,该范围将被排除在外。

另外,使用TRUNC 意味着Oracle 不会在created_date 列上使用索引(并且需要在TRUNC( created_date ) 上使用基于函数的索引)。如果要使用索引,则只需使用列并测试它是否大于或等于范围的开始并小于范围的结束加上一天(最多包括 23:59:59那天)。

TRUNC 查询的简化等效项是:

SELECT created_date
FROM   tableName
WHERE  short_name like 'FOO%'
AND    created_date >= DATE '2017-09-01'                    -- range start
AND    created_date <  DATE '2017-10-01' + INTERVAL '1' DAY -- range end + 1 day

【讨论】:

从 sysdate 开始的第一个条件大于 11,000 天是因为比这更早的记录被认为已删除,我没有构建它,所以我不确定为什么没有存档表.拥有第二个日期条件似乎无关紧要,但这并不意味着它不意味着不会评估该条件。或者是吗?您的 TRUNC 查询的简化等效项是:使用您的查询,我收到日期范围内的唯一一个结果。但是,为什么在我的第一个查询中,我得到了 25 条 created_date 17-Feb-04 记录,而只有 1 条记录是 22-Sep-17。 @MMcCullum 希望优化器会注意到 9 月至 10 月的范围排除了“不到 30 年”的范围,并且它不会评估这种情况 - 但是,如果它确实评估了它,那么你就是做的是给查询更多的工作来检查永远不会是假的东西(假设月份范围是真的),如果你知道它永远不会是假的,那么为什么要强迫数据库做不必要的工作呢? 至于第二部分 - 请提供 minimal reproducible example 包括定义表的 DDL 语句和一些示例数据,这些数据表明它正在获取超出您的条件的行 - 它不应该(而且它可能不是,但相反,我猜,这与日期到字符串的转换有关,但您似乎使用的是完整格式模型,所以不清楚为什么会这样 - 但如果没有可重复的示例,就不可能做任何事而不是猜测)。 有没有办法检查优化器是否在检查 9 月到 10 月的范围?至于结果,第一个查询产生 25 个条目,created_date 为 17-Feb-04,1 个条目为 22-Sep-17。所有其他人都生成了一个数据集,其中只有一个相同的记录,日期为 22-Sep-17。 created_date 字段设置为日期数据类型。 short_name 是 varchar2(30, byte)。 created_date 和 short_name 上有一个索引。

以上是关于在 where 子句问题中的 Oracle 日期比较的主要内容,如果未能解决你的问题,请参考以下文章

Oracle中where子句中的case表达式

Oracle SQL Where 子句查找超过 30 天的日期记录

Oracle:有效地使用where子句过滤时间戳列以获取特定日期的所有记录

where 子句中的日期时间

Oracle_where子句

Oracle:`(+)` 在 WHERE 子句中的作用是啥?