Oracle VARCHAR列上的数字比较如何工作?

时间:2013-01-24 07:07:09

标签: oracle plsql oracle11g

我有一个表,其中两列的类型为VARCHAR2(3BYTE)和VARCHAR2(32BYTE)。当我执行选择查询(where col1=10where col1='10')或(where col2=70001col2='70001')时,每个where子句集中提取的记录数相同。这是怎么发生的? Oracle如何处理字符串文字和数字常量,并与列数据类型的数据进行比较?

但这对VARCHAR2(128BYTE)类型的列不起作用。查询需要where col3='55555555001'才能正常工作,而where col3=55555555001会导致ORA-01722错误。

2 个答案:

答案 0 :(得分:12)

SQL Language Reference中所述:

  
      
  • 在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'。如果Oracle认为它可以先过滤掉非X值,那么它只会将to_number()应用于剩下的值,而较小的样本可能在foo中没有非数字值。但是,如果您有and bar = 'Y',则非Y值可能包含非数字, Oracle可能首先在foo 上过滤 ,取决于它认为值的选择性。

道德是永远不要将数字信息存储为字符类型。


我正在寻找一个AskTom参考来支持道德,而first one I looked at方便地指的是“谓词顺序的改变”的效果,并且说“不存储数字” VARCHAR2的”。

答案 1 :(得分:1)

如果涉及数字列或值和字符列,Oracle会将字符列值转换为数字,然后将数字转换为数字。就像你写的那样:

where to_number(col3) = 55555555001

如果单行包含无法转换为数字值的字符串(n col3),则会出现ORA-01722: invalid number错误。

出于这个原因,我们在Oracle数据库中有IS_NUMBER函数,它不会导致错误,但对于无法转换为数字的值返回NULL。然后你可以安全地写:

where is_number(col3) = 55555555001

该功能定义为:

CREATE OR REPLACE FUNCTION is_number (p_str IN VARCHAR2)
  RETURN NUMBER
IS
  l_num NUMBER;
BEGIN
  l_num := to_number(p_str);
  RETURN l_num;

EXCEPTION
  WHEN others THEN
    RETURN NULL;
END is_number;