我遇到了一个Oracle Invalid Number
错误,这对我来说没有意义。我了解what this error means,但在这种情况下不应该发生。很抱歉,这个问题很长,但是请耐心等待,以便我能彻底解释。
我有一个表,其中将ID存储到不同的源,并且某些ID可以包含字母。因此,该列为VARCHAR
。
其中一个来源具有数字ID,我想加入该来源:
SELECT *
FROM (
SELECT AGGPROJ_ID -- this column is a VARCHAR
FROM AGG_MATCHES -- this is the table storing the matches
WHERE AGGSRC = 'source_a'
) m
JOIN SOURCE_A a ON a.ID = TO_NUMBER(m.AGGPROJ_ID);
在大多数情况下,这是可行的,但是取决于诸如select子句中的哪些列,如果使用左连接或内部连接等之类的随机因素,我将开始看到Invalid Number
错误。
我已多次验证AGG_MATCHES
列中AGGSRC = 'source_a'
的所有条目在AGGPROJ_ID
列中均不包含非数字字符:
-- this returns no results
SELECT AGGPROJ_ID
FROM AGG_MATCHES
WHERE AGGSRC = 'source_a' AND REGEXP_LIKE(AGGPROJ_ID, '[^0-9]');
我知道Oracle基本上是在内部重写查询以进行优化。回到第一个SQL示例,我的最佳猜测是,根据整个查询的编写方式,在某些情况下, Oracle试图在子查询之前执行JOIN 。换句话说,它正在尝试将整个AGG_MATCHES
表联接到SOURCE_A
上,而不仅仅是将子查询返回的子集联接在一起。如果是这样,AGGPROJ_ID
列中将包含非数字值的行。
有人能确定这是导致错误的原因吗?如果是原因,那么我是否仍然有必要强制Oracle首先执行子查询部分,以便它仅尝试联接AGG_MATCHES
表的子集?
更多背景信息:
这显然是一个简化的示例,以说明问题。 AGG_MATCHES
表用于存储不同来源(即项目)之间的“匹配项”。换句话说,通常是说sourceA中的项目与sourceB中的项目匹配。
我不是一遍又一遍地编写相同的SQL,而是为我们常用的源创建了视图。想法是有一个包含两列的视图,一列用于SourceA,一列用于SourceB。因此,我不想在源表的TO_CHAR
列上使用ID
,因为开发人员每次执行连接时都必须记住这样做,试图删除代码重复。另外,由于SOURCE_A
中的ID是一个数字,因此我认为存储SOURCE_A.ID
的所有视图都应继续并将其转换为数字。
答案 0 :(得分:5)
您是对的,因为Oracle正在以与您编写的顺序不同的顺序执行该语句,从而导致转换错误。
按顺序解决此问题的最佳方法是:
TO_CHAR
将数字转换为字符串。如果您使用的是12.2,请使用DEFAULT return_value ON CONVERSION ERROR
语法将字符串转换为数字,如下所示:
SELECT *
FROM (
SELECT AGGPROJ_ID -- this column is a VARCHAR
FROM AGG_MATCHES -- this is the table storing the matches
WHERE AGGSRC = 'source_a'
) m
JOIN SOURCE_A a ON a.ID = TO_NUMBER(m.AGGPROJ_ID default null on conversion error);
向内联视图添加ROWNUM
,以防止可能重写语句的优化器转换。 ROWNUM
总是在末尾求值,即使没有使用ROWNUM
,它也会强制Oracle按一定顺序运行。 (官方提示是执行此操作的方法,但是正确获取提示太困难了。)
SELECT *
FROM (
SELECT AGGPROJ_ID -- this column is a VARCHAR
FROM AGG_MATCHES -- this is the table storing the matches
WHERE AGGSRC = 'source_a'
--Prevent optimizer transformations for type safety.
AND ROWNUM >= 1
) m
JOIN SOURCE_A a ON a.ID = TO_NUMBER(m.AGGPROJ_ID);
答案 1 :(得分:0)
我认为最简单的解决方案使用case
,它在评估顺序上有更多保证:
SELECT a.*
FROM AGG_MATCHES m JOIN
SOURCE_A a
ON a.ID = (CASE WHEN m.AGGSRC = 'source_a' THEN TO_NUMBER(m.AGGPROJ_ID) END);
或者更好的是,转换为字符串:
SELECT a.*
FROM AGG_MATCHES m JOIN
SOURCE_A a
ON TO_CHAR(a.ID) = m.AGGPROJ_ID AND
m.AGGSRC = 'source_a' ;
也就是说,最好的建议是修复数据模型。
在您的情况下,最好的解决方案可能只是视图或生成列:
create view v_agg_matches_a as
select . . .,
(case when regexp_like(AGGPROJ_ID, '^[0-9]+$')
then to_number(AGGPROJ_ID)
end) as AGGPROJ_ID
from agg_matches am
where m.AGGSRC = 'source_a';
如果使用视图,则case
可能不是必需的,但它更安全。
然后在后续查询中使用该视图。