当结果已被过滤以匹配我的日期格式(Oracle SQL)时,为啥这个 to_date 不起作用
Posted
技术标签:
【中文标题】当结果已被过滤以匹配我的日期格式(Oracle SQL)时,为啥这个 to_date 不起作用【英文标题】:Why doesn't this to_date work, when the results have been filtered to match my date format (Oracle SQL)当结果已被过滤以匹配我的日期格式(Oracle SQL)时,为什么这个 to_date 不起作用 【发布时间】:2021-01-12 17:39:00 【问题描述】:我有一个包含一列 (VARCHAR2) 的表“A”。该表包含一行包含文本“01/01/2021”和另一行包含文本“A”。
当我尝试过滤掉“A”然后 to_date 剩余值时,我得到“ORA-01858:在需要数字的地方发现了一个非数字字符”。我尝试了两种方法。
select *
from tbl
where col <> 'A'
and to_Date(col,'DD/MM/YYYY') = to_date('01/01/2020','DD/MM/YYYY');
select *
from ( select *
from tbl
where col <> 'A')
where to_Date(col,'DD/MM/YYYY') = to_date('01/01/2020','DD/MM/YYYY');
我可以理解为什么第一个可能不起作用,但在第二个示例中,to_date 应该只看到过滤后的数据(即“01/01/2020”)。
当我删除“A”的值时,该语句运行并且我得到了我的结果,因此似乎可以确定它没有运行的原因是因为它试图对“A”的值进行to_date,即使那到那时应该已经被过滤掉了。
我已经能够使用实际的 Oracle 表来复制它,但不幸的是,当我尝试使用 WITH AS 复制这些表时,查询可以正常工作并且没有遇到错误 - 另一个谜!
为什么这个查询不起作用?操作顺序似乎得到了满足(如果我使用 WITH AS,它就可以工作)。
【问题讨论】:
Oracle 不保证where
条件的评估顺序。也就是说,我认为除非在结果集中返回该行,否则 Oracle 不会生成错误。我发现很难重现错误:dbfiddle.uk/….
为什么将日期存储在varchar
列中?
@GordonLinoff - 除非最近发生了一些变化(ish),否则即使该行随后被过滤掉,Oracle 也会生成错误。前段时间有一个非常好的讨论,Chris Date 和一群非常聪明的人讨论了这个问题。不幸的是,大多数原始文章不再在线,但至少有一个电子邮件线程已存档freelists.org/post/oracle-l/Re2-A-Cure-for-Madness,3
@a_horse_with_no_name 这是一个用于快速数据比较的“临时”表。我倾向于从电子表格中导入数据以进行快速数据比较,在这种情况下,我知道所有不是日期的值,所以我想我可以在这里快速过滤掉它们,而不是在电子表格本身中。没有像这样存储用于业务用途的数据。仅在这些临时“比较”表中。
@JustinCave 。 . .有趣的。在 SQL Server 中似乎经常发生的事情在 Oracle 中很少发生(根据我的经验)。我认为 Oracle 有更多有意识的优化来避免它。我想它根本不会将谓词推送到扫描节点(相当于),这是 SQL Server 中此问题的最常见原因。顺便说一句,我认为这是一个错误。如果您正在考虑基于集合,则未返回给用户的行上的错误不应返回错误。
【参考方案1】:
Oracle(和其他数据库)没有义务在评估外部谓词之前评估应用于内联视图的谓词。事实上,从性能优化的角度来看,您经常希望优化器将选择性谓词从外部查询推送到视图、内联视图或子查询中。在这种情况下,查询是否抛出错误将取决于优化器选择的查询计划以及它实际首先计算的谓词。
作为一种快速技巧,您可以更改内联视图以防止推入谓词。在这种情况下,rownum
的存在会阻止优化器推送谓词。您还可以使用 no_push_pred
之类的提示来尝试强制优化器使用您想要的计划
select *
from ( select t.*, rownum rn
from tbl t
where col <> 'A')
where to_Date(col,'DD/MM/YYYY') = to_date('01/01/2020','DD/MM/YYYY');
不过,这些快速破解中的任何一个的问题是,某些未来版本的优化器可能有比您现在所知道的更多的选项,因此您将来可能会遇到问题。
更好的选择是重写查询,这样您就不必关心谓词的评估顺序。在这种情况下(取决于 Oracle 版本),这很容易,因为 to_date
允许您在出现转换错误时指定一个值
select *
from tbl
where col <> 'A'
and to_Date(col default null on conversion error,'DD/MM/YYYY') =
to_date('01/01/2020','DD/MM/YYYY');
如果您使用的是早期版本的 Oracle 或 to_date
只是实际问题的一个示例,您可以创建一个执行相同操作的自定义函数。
create function safe_to_date( p_str in varchar2, p_fmt in varchar2 )
return date
is
begin
return to_date( p_str, p_fmt );
exception
when value_error
then
return null;
end safe_to_date;
select *
from tbl
where col <> 'A'
and safe_to_date(col,'DD/MM/YYYY') = to_date('01/01/2020','DD/MM/YYYY');
【讨论】:
以上是关于当结果已被过滤以匹配我的日期格式(Oracle SQL)时,为啥这个 to_date 不起作用的主要内容,如果未能解决你的问题,请参考以下文章
Oracle:有效地使用where子句过滤时间戳列以获取特定日期的所有记录