【问题标题】:Equals(=) vs. LIKE for date data type日期数据类型的 Equals(=) 与 LIKE
【发布时间】:2013-09-01 13:15:21
【问题描述】:

首先,我知道这个问题已经普遍发布Equals(=) vs. LIKE。 在这里,我查询了ORACLE数据库上的日期类型数据,发现如下,当我这样写select语句时:

SELECT ACCOUNT.ACCOUNT_ID, ACCOUNT.LAST_TRANSACTION_DATE
FROM ACCOUNT
WHERE ACCOUNT.LAST_TRANSACTION_DATE LIKE '30-JUL-07';

我得到了我正在寻找的所有行。但是当我使用符号等于= 时:

SELECT ACCOUNT.ACCOUNT_ID, ACCOUNT.LAST_TRANSACTION_DATE
FROM ACCOUNT
WHERE ACCOUNT.LAST_TRANSACTION_DATE = '30-JUL-07';

即使除了等号之外没有什么不同,我什么也得不到。请问我能找到任何解释吗?

【问题讨论】:

  • 我猜第一个只匹配30-Jul-07 00:00:00,而第二个匹配30-Jul-07 **:**:**

标签: sql oracle


【解决方案1】:

假设LAST_TRANSACTION_DATEDATE 列(或TIMESTAMP),那么这两个版本都是非常糟糕的做法。

在这两种情况下,DATE 列将隐式转换为基于当前 NLS 设置的字符文字。这意味着对于不同的客户,您将获得不同的结果。

使用日期文字时总是使用to_date() 和(!)格式掩码或使用 ANSI 日期文字。这样,您将日期与日期而不是字符串与字符串进行比较。因此,对于相等的比较,您应该使用:

LAST_TRANSACTION_DATE = to_date('30-JUL-07', 'dd-mon-yy')

请注意,使用“MON”仍然会导致不同 NLS 设置的错误('DEC''DEZ''MAR''MRZ')。使用月份数字(和四位数年份)更不容易出错:

LAST_TRANSACTION_DATE = to_date('30-07-2007', 'dd-mm-yyyy')

或使用 ANSI 日期文字

LAST_TRANSACTION_DATE = DATE '2007-07-30'

现在上述查询很可能什么也不返回的原因是在 Oracle 中 DATE 列也包含时间。上述日期文字隐含地包含时间00:00。如果表中的时间不同(例如19:54),那么日期当然不相等。

要解决此问题,您有不同的选择:

  1. 在表格列上使用trunc() 将时间“标准化”为00:00 trunc(LAST_TRANSACTION_DATE) = DATE '2007-07-30 但是,这将阻止使用在 LAST_TRANSACTION_DATE 上定义的索引
  2. 使用between
    LAST_TRANSACTION_DATE between to_date('2007-07-30 00:00:00', 'yyyy-mm-dd hh24:mi:ss') and to_date('2007-07-30 23:59:59', 'yyyy-mm-dd hh24:mi:ss')

第一个解决方案的性能问题可以通过在trunc(LAST_TRANSACTION_DATE) 上创建一个索引来解决,该索引可以由该表达式使用。但是表达式LAST_TRANSACTION_DATE = '30-JUL-07' 也阻止了索引的使用,因为在内部它被处理为to_char(LAST_TRANSACTION_DATE) = '30-JUL-07'

要记住的重要事项:

  1. 永远不要依赖隐式数据类型转换。它在某些时候给你带来问题。始终比较正确的数据类型
  2. Oracle DATE 列始终包含作为比较规则一部分的时间。

【讨论】:

  • LAST_TRANSACTION_DATE = to_date('30-07-2007', 'dd-mm-yyyyy') 上多了一个 'y',请编辑。我不能自己编辑,因为我需要编辑至少 6 个字符 :) 谢谢
  • 我认为between 也不会从索引中受益。
  • 我需要使用LIKE=,即使使用to-date,如UPDATED_AT LIKE to_date('19-DEC-21', 'dd-mon-yy')
【解决方案2】:

您不应该直接将日期与字符串进行比较。你依赖implicit conversions,它的规则很难记住。

此外,您选择的日期格式不是最佳的:年份有四位数字(Y2K 错误?),并且并非所有语言都有名为 JUL 的一年中的第七个月。你应该使用YYYY/MM/DD之类的东西。

最后,Oracle 中的日期是精确到秒的时间点。 所有日期都有一个时间部分,即使是00:00:00。当您使用= 运算符时,Oracle 将比较日期和时间。

这是一个重现您描述的行为的测试用例:

SQL> create table test_date (d date);

Table created

SQL> alter session set nls_date_format = 'DD-MON-RR';

Session altered

SQL> insert into test_date values
  2     (to_date ('2007/07/30 11:50:00', 'yyyy/mm/dd hh24:mi:ss'));

1 row inserted

SQL> select * from test_date where d = '30-JUL-07';

D
-----------

SQL> select * from test_date where d like '30-JUL-07';

D
-----------
30/07/2007

当您使用= 运算符时,Oracle 会将常量字符串30-JUL-07 转换为日期并将值与列进行比较,如下所示:

SQL> select * from test_date where d = to_date('30-JUL-07', 'DD-MON-RR');

D
-----------

当你使用LIKE运算符时,Oracle会将列转换为字符串,并与右侧进行比较,相当于:

SQL> select * from test_date where to_char(d, 'DD-MON-RR') like '30-JUL-07';

D
-----------
30/07/2007

始终将日期与日期进行比较,将字符串与字符串进行比较。相关问题:

【讨论】:

  • 假设这些数据格式不在我的控制之下,我需要与日期 30-JUL-07 进行比较,我想我必须使用像这样的显式转换 to_date('30-JUL-07 ')。对吗?
  • 您必须使用带有两个参数的to_date(常量格式:)。此外,LIKE 严格来说是一个字符串运算符,因此不适用于日期。
【解决方案3】:

日期字段不是字符串。当您使用 = 时,会在内部对字符串进行隐式转换,这与任何内容都不匹配,因为您的字符串没有所需的精度。

我猜LIKE 语句与日期字段的行为有些不同,导致在比较中使用隐式通配符,从而消除了对任何精度的要求。本质上,您的 LIKE 是这样工作的:

SELECT ACCOUNT.ACCOUNT_ID, ACCOUNT.LAST_TRANSACTION_DATE
FROM ACCOUNT
WHERE ACCOUNT.LAST_TRANSACTION_DATE BETWEEN DATE('30-JUL-07 00:00:00.00000+00:00') AND DATE('30-JUL-07 23:59:59.99999+00:00');

【讨论】:

    猜你喜欢
    • 2015-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-26
    • 2014-03-01
    相关资源
    最近更新 更多