首页 > 数据库 > Oracle > 必须重视的Oracle自动类型转换
Oracle中对不同类型的处理具有显式类型转换(Explicit)和自动类型转换(隐式类型转换Implicit)两种方式,对于显式类型转换,我们是可控的,但是对于自动类型转换,当然不建议使用,因为很难控制,有不少缺点,但是我们很难避免碰到自动类型转换,如果不了解自动类型转换的规则,那么往往会改变我们SQL的执行计划,从而可能导致效率降低或其它问题,所以,Oracle开发人员很有必要了解Oracle自动类型转换的相关规则,从而避免自动类型转换导致相关问题的产生。
29.1 数据类型优先级
■ Datetime and interval 类型
■ BINARY_DOUBLE
■ BINARY_FLOAT
■ NUMBER
■ 字符类型
■ 所有其它内置类型
29.2 自动类型转换规则
DINGJUN123>select 5*10+'james' from dual;
select 5*10+'james' from dual
第 1 行出现错误:
ORA-01722: 无效数字
DINGJUN123>select 5*10+'2' from dual;
----------
29.2.1为什么不建议使用自动类型转换?
1.使用显示类型转换会让我们的SQL更加容易被理解,也就是可读性更强,但是自动类型转换却没有这个优点,如:
DINGJUN123>select to_date(sysdate,'yyyymm') from
dual;
DINGJUN123>alter session set
nls_date_format='yyyymm';
会话已更改。
DINGJUN123>select to_date(sysdate,'yyyymm') from
dual;
TO_DAT
------
201005
DINGJUN123>alter session set
nls_date_format='yyyymondd';
会话已更改。
DINGJUN123>select to_date(sysdate,'yyyymondd') from
dual;
TO_DATE(SYSDAT
--------------
20105月 16
DINGJUN123>alter session set
nls_date_language='American';
会话已更改。
DINGJUN123>select to_date(sysdate,'yyyymondd') from
dual;
TO_DATE(SYSD
------------
2010may16
2.
DINGJUN123>drop table t;
表已删除。
DINGJUN123>create table t(name varchar2(10));
表已创建。
DINGJUN123>insert into t values('abc');
已创建 1 行。
DINGJUN123>insert into t values('1');
已创建 1 行。
DINGJUN123>commit;
提交完成。
DINGJUN123>create index idx_t on t (name);
索引已创建。
-------------------------------------案例1:自动类型转换导致出错------------------------------------
DINGJUN123>select * from t where name = 1;
select * from t where name = 1
第 1 行出现错误:
ORA-01722: 无效数字
DINGJUN123>select * from t where name = '1';
NAME
--------------------
1
--------------------------------------案例2:自动类型转换导致本该用索引而没有用----------
DINGJUN123>explain plan for select * from t where
name = 1;
已解释。
DINGJUN123>select * from
table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------
Plan hash value: 1601196873
----------------------------------
| Id
----------------------------------
|
|*
----------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------
Note
-----
DINGJUN123>explain plan for
select
已解释。
DINGJUN123>select * from
table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
Plan hash value: 2296882198
----------------------------------
| Id
----------------------------------
|
|*
----------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------------
Note
-----
我们看案例1,如果这个语句很庞大,找这个错误还真不容易,如果是显示转换的话,找个错误就容易多了。案例2我使用RBO优化器的,我没有收集统计信息,而且还加了rule,这里不加rule一样,如果列自动发生了类型转换,很可能使索引失效,这句select
* from t where name = 1没有写select * from t where to_number(name)
=1发现索引失效明显。但是如果我们感觉应该用索引而没有用上索引,而且左边的列和右边的值类型不一样,那么很可能发生了自动类型转换,当然看执行计划有这样的类型转换信息,虽然我们没有显示地写,往往看执行计划是我们第1步寻找问题的方法。
3.
4.
5.
29.2.2 自动类型转换规则
自动类型转换矩阵图

Oracle自动类型转换有如下规则(转换方向):
1.
这很容易理解,当然最终存到我们目标列的类型是要符合定义的,如:
DINGJUN123>drop table t;
表已删除。
DINGJUN123>create table t(x varchar2(100));
表已创建。
DINGJUN123>insert into t values(sysdate);
已创建 1 行。
DINGJUN123>select x from t;
X
--------------------
2010may16
看到了吧,其实sysdate在插入的时候就已经根据nls_date_format和nls_date_language参数转为字符类型varchar2(100)了。
2.在SELECT中,Oracle会自动将查询到的列的值转为目标变量的类型。如:
DINGJUN123>declare
var is 1
3.
4.
DINGJUN123>explain plan for select * from t where x
= 1;
DINGJUN123>select * from
table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
Plan hash value: 1601196873
----------------------------------
| Id
----------------------------------
|
|*
----------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
Note
-----
5.在字符类型、NUMBER数值类型与浮点类型的数值之间相互转换,可能会丢失精度,因为NUMBER是以10进制(0-9)精度表示数字的,而浮点类型数值是以二进制(0和1)表示的精度。
DINGJUN123>drop table t;
表已删除。
DINGJUN123>create table t(x binary_float);
表已创建。
DINGJUN123>insert into t values(1234567);
已创建 1 行。
DINGJUN123>insert into t values(123456789);
已创建 1 行。
DINGJUN123>column x
format
DINGJUN123>select * from t;
------------------------------------------------------
我们插入的时候是NUMBER类型,但是实际表是BINARY_FLOAT,那么肯定要转为BINARY_FLOAT类型,看123456789插入的时候就发生了精度的丢失。
6.将CLOB转为字符类型或将BLOB转为RAW类型的时候,如果被转换的类型长度比目标类型长,那么会出错,其实,其他的类型转换在自动类型,显示类型转换中如果被转换的类型的长度比目标类型长,那么都是会报错的(但是在某些函数中自动截断,不报错,见第14)。
DINGJUN123>drop table t;
表已删除。
DINGJUN123>create table t(x varchar2(10));
表已创建。
DINGJUN123>insert into t
values(to_clob('12212121212121'));
insert into t values(to_clob('12212121212121'))
第 1 行出现错误:
ORA-12899: 列 "DINGJUN123"."T"."X" 的值太大 (实际值: 14, 最大值: 10)
7.BINARY_FLOAT自动转为BINARY_DOUBLE是准确的,当然这毋庸置疑。反之,BINARY_DOUBLE自动转为BINARY_FLOAT可能就是不准确的了,如BINARY_DOUBLE转为BINARY_FLOAT需要更多的精度位的支持。
8.当字符串与DATE类型比较,DATE类型具有较高优先级,将字符串转为DATE类型,这种自动转换需要上下文的支持,见前面DATE转为字符串的例子。
DINGJUN123>drop table t;
表已删除。
DINGJUN123>create table t(x date);
表已创建。
DINGJUN123>insert into t
values(to_date('2010-01-01','yyyy-mm-dd'));
已创建 1 行。
DINGJUN123>select * from t where x
='2010-01-01';
select * from t where x ='2010-01-01'
第 1 行出现错误:
ORA-01861: 文字与格式字符串不匹配
DINGJUN123>alter session set
nls_date_format='yyyy-mm-dd';
会话已更改。
DINGJUN123>select * from t where x
='2010-01-01';
X
----------
2010-01-01
看,的确可以自动类型转换。'2010-01-01'根据nls_date_format和nls_date_language转为了DATE类型。
9.当使用SQL函数或操作符的时候,如果传入的类型和实际应该接受的类型不一致,那么将传入的类型根据上下文环境转为一致。
DINGJUN123>select replace(12345,4) from dual;
REPLACE(
--------
1235
DINGJUN123>select '10'+0 from dual;
--------------------------------------------------------------------------
DINGJUN123>select '10'|| 0 from dual;
'10'||
------
100
10.当做赋值操作(=)的时候,Oracle会将右边被赋的值的类型自动转为和左边目标类型一致的类型。其实前面我们说的select语句的值赋给目标变量也类似。注意我们这里说的赋值操作可不是where
xx = yy中=(这里的是比较操作),而是赋值给变量或列,比如insert,update,PL/SQL中的赋值操作。
11.在做连接操作的时候,Oracle会将非字符类型转为CHAR或NCHAR。第9点已经举了例子说明。
12.在字符和非字符之间的算术和比较操作中,ORACLE会根据日期,ROWID,数值类型优先级最大来进行转换。算术操作一般都要转为NUMBER,比如where
rowid='…'要将字符串转为ROWID,where date ='….'会将字符串根据nls的设置转为日期类型。
DINGJUN123>select rowid from t;
ROWID
------------------
AAAOi7AAEAAAPpWAAA
DINGJUN123>select * from t where rowid =
'AAAOi7AAEAAAPpWAAA';
X
----------
2010-01-01
DINGJUN123>select * from t where x =
'2010-01-01'
X
----------
2010-01-01
DINGJUN123>select to_char(x,'yyyymmdd')+1 from
t;
--------------------------------------------------