比较两个日期字段时,“Literal与格式字符串不匹配”

时间:2012-04-26 18:12:32

标签: oracle plsql

我有一种奇怪的行为,我希望有人可以向我解释。

我在查询中有两个字段。一个是数字字段,通过to_date('01/01/1960', 'mm/dd/yyyy') + somethingorother转换为日期。另一个是文本字段,其中包含至少一个非日期值,转换为to_date(textField, 'mm/dd/rrrr')的日期。如果我运行查询,它运行正常。但是,如果我将查询括在select * from ( ) where field1 > field2中,则会出现“ORA-01861:文字与格式字符串不匹配”错误。如果我尝试在回退文本字段的子查询的where子句中排除已知的非日期值,则无效。

我知道在没有代码的情况下几乎不可能搞清楚,但我想知道是否有人可以向我解释为什么没有过滤器它可以工作,但是当我添加它时会爆炸。感谢。

3 个答案:

答案 0 :(得分:8)

一般问题是,由于SQL是基于集合的语言,因此Oracle可以按其选择的任何顺序自由评估谓词。如果您有一个VARCAHR2列存储一些日期值和一些非日期值,这意味着Oracle可以自由地评估首先过滤掉所有非日期值的谓词,或者评估谓词检查转换的DATE值之一是否大于另一个值。如果在过滤掉非日期值之前恰好评估DATE不等式谓词(field1 > field2),则会出错。

SQL是基于集合的事实是使用错误数据类型的主要原因之一是如此成问题 - 您无法确定查询总是会过滤掉不可转换数据之前调用转换函数。即使你设置了像过滤掉无效数据的视图这样的抽象障碍,优化器也可以自由地重新排序谓词,这样你就可以很容易地发现你的查询最终会破坏你的抽象障碍,或者你有大多数时间都在运行的查询,除非优化器恰好选择不同的执行计划。 Jonathan Gennick有一篇非常有趣的文章Subquery Madness,讨论了这个具体问题。

您可以编写自己的转换函数,忽略异常并在查询中使用该异常。例如,您可以创建一个函数

CREATE OR REPLACE FUNCTION my_to_date( p_date_str    IN VARCHAR2,
                                       p_format_mask IN VARCHAR2 )
  RETURN DATE
IS
  l_date DATE;
BEGIN
  l_date := to_date( p_date_Str, p_format_mask ); 
  RETURN l_date;
EXCEPTION
  WHEN OTHERS THEN
    RETURN null;
END;

然后在查询中使用该功能

SELECT *
  FROM (SELECT to_date('01/01/1960', 'mm/dd/yyyy') + somethingorother field1,
               my_to_date( textField, 'mm/dd/rrrr' ) field2
          FROM your_table
         WHERE some_condition)
 WHERE field1 > field2

这将有效,因为无论是否评估为有效my_to_date,都可以在任何字符串上调用DATE,因此您的查询不再取决于Oracle选择评估的顺序谓词。

答案 1 :(得分:4)

我猜你的varchar2列中的某个日期 - 注意到不一致 - 不符合您指定的格式。请不要在字符列中存储日期。使用日期列,它可以阻止这种情况发生。

创建以下功能:

create or replace function is_date
        ( Pdate varchar2
        , Pformatstring varchar2 ) return number is

   l_date date;

begin

   l_date := to_date(Pdate, Pformatstring);

   return 1;
-- raise an exception when you can-t convert to a date.
exception when others then
   return 0;

end;

然后跑进你的桌子:

select <columns>
  from my_table
 where is_date(textfield,  'mm/dd/rrrr') = 0

这会显示数据错误的位置。

如评论中所述,由于您未选择所有数据,因此最初似乎运行正常。


还有另一种选择,但仍与将日期存储为角色有关。如果您正在进行复杂的连接,请执行以下操作:

select a.a, a.b
  from my_table a
  join another_table b
    on to_date(replace(a.textfield,'?'),'mm/dd/rr') = b.a_date

然后,Oracle不一定按照您想要的顺序评估所有内容。您必须将其更改为:

select x.*
  from ( select a, b, to_date(replace(a.textfield,'?'),'mm/dd/rr') as another_date
           from my_table ) x
  join another_table b
    on another_date = b.a_date

答案 2 :(得分:3)

我将使用DBMS_Xplan.display查看执行计划,以查看您提供的过滤器是否以某种微妙的方式进行修改,以解释此行为。