【问题标题】:Difference in TO_CHAR formatTO_CHAR 格式的差异
【发布时间】:2023-03-27 16:53:01
【问题描述】:

下面的查询用于获取几天之间的记录计数。 实际上,该特定日期没有记录,因此查询应返回 0。 但是由于一些错误,它返回了错误的值。

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';

但在尝试使用以下查询时,它返回 0 作为例外。

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

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

【问题讨论】:

  • 这不是检查日期是否在特定范围内的方法。
  • 这就是为什么您不通过将日期转换为字符串来比较日期的原因。 '01-01-2099' < '31-12-2000' 因为 '0' < '3',例如,即使将其解释为日期也没有意义。 Oracle 实际上有一个日期文字的语法 (DATE 'YYYY-MM-DD'),所以使用它。
  • @JeroenMostert 嗨,那么根据某个日期范围获取记录的正确方法是什么?
  • 为什么还要将日期转换为字符串?为什么不只比较日期?如果在检查 '9' 是否在 '1''10' 之间之前将数字转换为字符串,您将遇到同样的问题。

标签: sql oracle date to-char


【解决方案1】:

如果你比较两个日期,你会得到日期比较语义。如果比较两个字符串,则会得到按字母顺序排列的字符串比较语义。字符串“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' ) 

【讨论】:

    【解决方案2】:

    其他答案中的一切都很好,而且非常正确(始终使用日期与日期进行比较,不要转换为字符串)。

    另外,当你只关心 YMD 时(时间并不重要),你还应该合并TRUNC,它只会删除 HMS,这样时间就不会成为一个问题:

    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' ) )
    

    【讨论】:

    • 如果您在日期列上调用TRUNC(),在许多情况下,您还会使任何可能使用该列进行查询的索引失效。
    • 是的,确实如此。
    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

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

      考虑到这一点,查看第一个示例:

      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'
      

      【讨论】:

      • 完全同意不将日期转换为字符串。但是,如果严格的 YYYYMMDD(甚至是 YYYYMMDDHHMMSS)格式在所有个位数上保持零填充,这将始终正确排序 - 不知道为什么它会在 10 月至 12 月或 1 日/11 日/21 日/31 日失败-- 虽然我只将它用于 LESS-THAN、GREATER-THAN 之类的比较,而不是用于 BETWEEN 之类的函数。
      • 正确排序和在一个范围内比较是不同的事情。
      • 啊,是的。这是有道理的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-01
      • 2014-04-09
      • 2016-01-27
      相关资源
      最近更新 更多