SQL 言語リファレンスに記載されているとおり:
- SELECT FROM 操作中に、Oracle はデータを列からターゲット変数の型に変換します。
- ...
- 文字値を数値と比較する場合、Oracle は文字データを数値に変換します。
型が一致しない場合、テーブルの列に対して暗黙的な変換が実行されます。これは、いくつかのダミー データを使用して SQL*Plus でトレースすることで確認できます。
create table t42 (foo varchar2(3 byte));
insert into t42 (foo) values ('10');
insert into t42 (foo) values ('2A');
set autotrace on explain
これは機能します:
select * from t42 where foo = '10';
FOO
---
10
Execution Plan
----------------------------------------------------------
Plan hash value: 3843907281
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 3 | 3 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| T42 | 1 | 3 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("FOO"='10')
Note
-----
- dynamic sampling used for this statement (level=2)
しかし、このエラー:
select * from t42 where foo = 10;
ERROR:
ORA-01722: invalid number
Execution Plan
----------------------------------------------------------
Plan hash value: 3843907281
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 3 | 3 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| T42 | 1 | 3 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(TO_NUMBER("FOO")=10)
フィルターの違いに注意してください。filter("FOO"='10')
対filter(TO_NUMBER("FOO")=10)
。後者の場合、数値と比較するとto_number()
、テーブル内のすべての行に対して実行され、その変換の結果が固定値と比較されます。そのため、変換できない文字値があると、ORA-01722 が返されます。適用される関数は、その列にインデックスが存在する場合、使用中のインデックスも停止します。
興味深いのは、複数のフィルターがある場合です。Oracle は異なるタイミングで異なる順序でそれらを評価する場合があるため、常に ORA-01722 が表示されるとは限らず、時々ポップアップすることがあります。あなたが持っていたとしましょうwhere foo = 10 and bar = 'X'
。オラクルが非値を最初に除外できると考えた場合、残ったものX
にのみ を適用しto_number()
、その小さなサンプルには に非数値が含まれていない可能性がありますfoo
。ただし、 がある場合、値がどれだけ選択的であるかに応じてand bar = 'Y'
、非Y
値に非数値が含まれるか、 Oracle がfoo
最初に をフィルタリングする可能性があります。
教訓は、数値情報を文字型として保存しないことです。
モラルをバックアップするためにAskTomのリファレンスを探していました.最初に見たものは、「述語の順序の変更」の効果と「varchar2に数値を保存しないでください」と言っている.