连接条款中的Oracle无效编号

时间:2019-03-24 04:19:01

标签: sql oracle oracle12c

我遇到了一个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的所有视图都应继续并将其转换为数字。

2 个答案:

答案 0 :(得分:5)

您是对的,因为Oracle正在以与您编写的顺序不同的顺序执行该语句,从而导致转换错误。

按顺序解决此问题的最佳方法是:

  1. 更改数据模型以始终将数据存储为正确的类型。始终将数字存储为数字,将日期存储为日期,将字符串存储为字符串。 (您已经知道这一点,并说您无法更改数据模型,这是对未来读者的警告。)
  2. 使用TO_CHAR将数字转换为字符串。
  3. 如果您使用的是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);
    
  4. 向内联视图添加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可能不是必需的,但它更安全。

然后在后续查询中使用该视图。