我有一个表,其中两列的类型为VARCHAR2(3BYTE)和VARCHAR2(32BYTE)。当我执行选择查询(where col1=10
和where col1='10'
)或(where col2=70001
或col2='70001'
)时,每个where子句集中提取的记录数相同。这是怎么发生的? Oracle如何处理字符串文字和数字常量,并与列数据类型的数据进行比较?
但这对VARCHAR2(128BYTE)类型的列不起作用。查询需要where col3='55555555001'
才能正常工作,而where col3=55555555001
会导致ORA-01722错误。
答案 0 :(得分:12)
- 在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;