TO_CHAR 格式的差异

Difference in TO_CHAR format

下面的查询用于获取某几天的记录数。 实际上,该特定日期没有记录,因此查询应该 return 0。 但是由于一些错误,它 return 的值不正确。

SELECT COUNT(*)
FROM transaction_tb 
WHERE STATUS NOT IN ('Wrong', 'Dont') 
AND to_char(date, 'DD-MM-YYYY') BETWEEN '01-04-2019' AND '31-03-2020';

但是当尝试使用以下查询时,它 returned 0 作为例外。

SELECT COUNT(*) 
FROM transaction_tb 
WHERE STATUS NOT IN ('Wrong', 'Dont') 
AND to_char(date, 'YYYYMMDD') BETWEEN '20190401' AND '20200331';

两个查询应该return相同的值。
这些查询有什么区别?

如果比较两个日期,就会得到日期比较语义。如果你比较两个字符串,你会得到按字母顺序排列的字符串比较语义。字符串“10-10-1950”按字母顺序位于字符串“01-04-2019”和字符串“31-03-2020”之间。尽管它所代表的日期明显早于其他字符串所代表的任何一个日期。

如果将日期与日期进行比较,您将获得所需的日期比较语义。要么使用日期文字

where date_column between date '2019-01-04' and date '2020-03-31'

或使用 to_date 将字符串转换为日期

where date_column between to_date( '01-04-2019', 'MM-DD-YYYY' ) 
                      AND to_date( '31-03-2020', 'MM-DD-YYYY' ) 

不要使用字符串进行日期比较!

SELECT COUNT(*)
FROM transaction_tb 
WHERE STATUS NOT IN ('Wrong', 'Dont') AND
      date >= DATE '2019-01-04' AND
      date <= DATE '2020-03-31'

当您转换为 character/text 类型时,您正在为 BETWEEN 操作进行文本比较。它根本不再进行日期检查。文本比较按字母顺序逐个字符进行,一旦 任何内容 超出范围就会停止。

考虑到这一点,查看第一个样本:

to_char(date, 'DD-MM-YYYY') BETWEEN '01-04-2019' AND '31-03-2020'

两个边界上的第二个字符是 1,因此第二个字符没有 1 的任何内容都将失败。换句话说,唯一可能成功超过这一点的日期是该月的第 1、11、21 和 31 天。

更进一步,我们到达第 4 个字符,对于两个边界字面量都是 0。带有 1 的任何内容都不能通过此示例,包括从 10 月到 12 月的所有日期。

接下来是第5个字符,依次是43。如果我们忽略排除 一切 的顺序问题,并且只考虑 3-4,则不包括 1 月和 2 月的任何日期,以及 4 月之后的任何日期。


现在我们来看第二个例子。 似乎有效的那个:

to_char(date, 'YYYYMMDD') BETWEEN '20190401' AND '20200331';

应用相同的过程,我们在整个年份部分都得到了有效的结果。但是然后我们到达 0403 中的 0 并且我们遇到了与以前相同的问题,不包括 10 月到 12 月。最后,我们在最后一个字符上也有同样的问题,这将您限制在该月的第 1、11、21 和 31 天。


您应该做的是将比较保留为 date如果您发现自己转换为字符串类型以进行日期检查,那您就犯了大错。

相反,您想将边界表示为 date literals:

"date" BETWEEN DATE '2019-04-01' AND DATE '2020-03-31'

其他答案都很好,很真实(总是用日期和日期比较,不要转成字符串)。

此外,当您只关心 Y-M-D(并且一天中的时间并不重要)时,您还应该合并 TRUNC,它只会删除 H-M-S,以便一天中的时间不会成为问题:

WHERE TRUNC( TO_DATE( '01-04-2019', 'MM-DD-YYYY' ) ) <= TRUNC( date_column )
  AND TRUNC( date_column ) <= TRUNC( TO_DATE( '03-30-2020', 'MM-DD-YYYY' ) )