2

我在这里发生了一种奇怪的行为,我希望有人可以向我解释。

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

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

4

3 回答 3

9

一般的问题是,由于 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 选择评估谓词的顺序。

于 2012-04-26T18:42:57.157 回答
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
于 2012-04-26T18:25:47.437 回答
3

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

于 2012-04-26T18:27:11.360 回答