关于 to_char 和 to_number 的困惑

Confusion regarding to_char and to_number

首先,我了解基础知识。

select to_number('A231') from dual; -- 这行不通但是

select to_char('123') from dual;-- 这行得通

select to_number('123') from dual;-- 这也行

实际上在我的包中,我们有 2 个表 A(X number) 和 B(Y varchar) 有很多列,但我们只担心 XYX 仅包含数值,如 123,456 等,但 Y 包含一些 string 和一些 number,例如 '123','HR123','Hello'。我们必须加入这两个表。它的遗留应用程序,因此我们无法更改表和列。 直到此时以下条件正常工作

to_char(A.x)=B.y;

但是因为Y上有索引,性能组建议我们做 A.x=to_number(B.y);在开发环境中是运行

我的问题是,这个查询在任何情况下都会报错吗?如果它选择 '123',它肯定会给出 123。但如果它选择 'AB123' 那么它将失败。它会失败吗?它可以选择 'AB123' 即使它正在与其他 table.

连接吗

永远不要转换可以包含非数字字符串的 VARCHAR2to_number

这可以部分奏效,但最终肯定会失败。

小例子

create table a as
select rownum X from dual connect by level <= 10;

create table b as
select to_char(rownum) Y from dual connect by level <= 10
union all
select 'Hello' from dual;

这可能有效(因为您限制了行数,以便转换有效;如果您幸运并且 Oracle 选择了正确的执行计划;这是可能的,但不能保证;)

select * 
from a
join b on A.x=to_number(B.y)
where B.y =  '1';

但这会失败

select * 
from a
join b on A.x=to_number(B.y)

ORA-01722: invalid number

性能

But since there is index on Y, performance team suggested us to do A.x=to_number(B.y);

你应该改变团队,就像你在列 (to_number(B.y)) 上使用 函数 索引 不能使用 .

相反,您的原始查询可以完美地使用以下索引:

create index b_y on b(y);
create index a_x on a(x);

查询

select * 
from a
join b on to_char(A.x)=B.y
where A.x = 1;

执行计划

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |     5 |     1   (0)| 00:00:01 |
|   1 |  NESTED LOOPS     |      |     1 |     5 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN| A_X  |     1 |     3 |     1   (0)| 00:00:01 |
|*  3 |   INDEX RANGE SCAN| B_Y  |     1 |     2 |     0   (0)| 00:00:01 |
--------------------------------------------------------------------------

 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - access("A"."X"=1)
   3 - access("B"."Y"=TO_CHAR("A"."X"))

can it fail?

是的。它必须把每一行都经过TO_NUMBER,然后才能检查是否满足过滤条件。因此,如果你有任何一行会失败,那么它总是会失败。


Oracle 12.2(因为你标记了 Oracle 12)你可以使用:

SELECT *
FROM   A
       INNER JOIN B
       ON (A.x = TO_NUMBER(B.y DEFAULT NULL ON CONVERSION ERROR))

或者,在 TO_CHAR(A.x) 上建立索引并使用您的原始查询:

SELECT *
FROM   A
       INNER JOIN B
       ON (TO_CHAR(A.x) = B.y)

另请注意:在 B.y 上有索引并不意味着将使用该索引。如果您在 TO_NUMBER(B.y) 上进行过滤(有或没有默认的转换错误),那么您将需要对您正在使用的函数 TO_NUMBER(B.Y) 进行基于函数的索引。您应该分析查询并检查解释计划以查看索引的使用是否有任何改进或更改。