2

私はここで奇妙な振る舞いをしています。誰かが私にそれを説明してくれることを願っています.

クエリに 2 つのフィールドがあります。1 つは、 を使用して日付に変換される数値フィールドですto_date('01/01/1960', 'mm/dd/yyyy') + somethingorother。もう 1 つは、少なくとも 1 つの日付以外の値を含むテキスト フィールドで、 で日付に変換されますto_date(textField, 'mm/dd/rrrr')。クエリを実行すると、正常に実行されます。ただし、クエリを で囲むselect * from ( ) where field1 > field2と、「ORA-01861: リテラルがフォーマット文字列と一致しません」というエラーが発生します。テキスト フィールドを引き戻すサブクエリの where 句で既知の日付以外の値を除外しようとしても、役に立ちません。

コードなしでこれを理解することはほとんど不可能であることはわかっていますが、フィルターなしで機能するのに、フィルターを追加すると爆発する理由を誰かが説明できるかどうか疑問に思っています。ありがとう。

4

3 に答える 3

9

一般的な問題は、SQLはセットベースの言語であるため、Oracleは任意の順序で述語を自由に評価できることです。一部の日付値と一部の非日付値を格納する列がある場合VARCAHR2、Oracleは、すべての非日付値を最初に除外する述語を評価するか、いずれか1つかどうかをチェックする述語を評価することができます。変換されDATEた値は、最初に他の値よりも大きくなります。DATE日付以外の値を除外する前に不等式述語( )を評価するとfield1 > field2、エラーが発生します。

SQLがセットベースであるという事実は、間違ったデータ型を使用することが非常に問題となる主な理由の1つです。変換を呼び出す前に、クエリが変換不可能なデータを常に除外することを確信することはできません。働き。無効なデータを除外するビューなどの抽象化バリアを設定した場合でも、オプティマイザは述語を自由に並べ替えることができるため、クエリが抽象化バリアを破ってしまうことや、クエリがほとんどの場合機能するクエリがあることを簡単に見つけることができます。オプティマイザはたまたま別の実行プランを選択します。Jonathan Gennickは、この特定の問題について説明している非常に楽しい記事SubqueryMadnessを持っています。

例外を無視する独自の変換関数を作成し、それをクエリで使用できます。たとえば、関数を作成できます

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列の日付の 1 つが矛盾していることに注意してください - あなたが示した形式ではありません。文字列に日付を格納しないでください。日付列を使用すると、これがまったく発生しなくなります。

次の関数を作成します。

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

その場合、オラクルは必ずしも意図した順序ですべてを評価するとは限りません。次のように変更する必要があります。

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 に答える