Oracle“从双重选择级别”与to_number结果无法正常工作

时间:2014-01-21 15:40:32

标签: sql oracle

为什么

select *
from (
   SELECT LEVEL as VAL
   FROM DUAL
   CONNECT BY LEVEL <= 1000
   ORDER BY LEVEL
) n
left outer join (select to_number(trim(alphanumeric_column)) as nr from my_table
where NOT regexp_like (trim(alphanumeric_column),'[^[:digit:]]')) d
on n.VAL = d.nr
where d.nr is null 
and n.VAL >= 100

抛出一个ORA-01722无效数字(原因是最后一行,n.VAL),而类似版本的数字列im my_table工作正常:

 select *
    from (
       SELECT LEVEL as VAL
       FROM DUAL
       CONNECT BY LEVEL <= 1000
       ORDER BY LEVEL
    ) n
    left outer join (select numeric_column as nr from my_table) d
    on n.VAL = d.nr
    where d.nr is null 
    and n.VAL >= 100

假设numeric_column的类型为number,而alphanumeric_column的类型为nvarchar_2。请注意,上部示例在没有数字比较的情况下工作正常(n.VAL&gt; = 100)。

有人知道吗?

2 个答案:

答案 0 :(得分:3)

这个问题让我发疯了。我将问题缩小到更简单的查询

SELECT *
  FROM (SELECT TO_NUMBER(TRIM (alphanumeric_column)) AS nr
          FROM my_table
         WHERE NOT REGEXP_LIKE (TRIM (alphanumeric_column), '[^[:digit:]]')) d
 WHERE d.nr > 1

使用alphanumeric_colum值('100','200','XXXX');运行上面的语句给出了“无效数字”错误。然后我稍微更改了查询以使用CAST函数而不是TO_NUMBER:

SELECT *
  FROM (SELECT CAST (TRIM (alphanumeric_column) AS NUMBER) AS nr
          FROM my_table
         WHERE NOT REGEXP_LIKE (TRIM (alphanumeric_column), '[^[:digit:]]')) d
 WHERE d.nr > 1

这正确地返回 - 100,200。我认为这些功能在行为上是相似的。几乎看起来好像oracle试图评估d.nr&gt;在构造视图之前有1个约束,这没有任何意义。如果有人能说清楚为什么会这样,我将不胜感激。见SQLFiddle example

更新:我做了一些挖掘工作,因为我不喜欢不知道为什么有些东西可行。我在两个查询中都运行了EXPLAIN PLAN并获得了一些有趣的结果。

对于失败的查询,谓词信息如下所示:

   1 - filter(TO_NUMBER(TRIM("ALPHANUMERIC_COLUMN"))>1 AND  NOT 
              REGEXP_LIKE (TRIM("ALPHANUMERIC_COLUMN"),'[^[:digit:]]'))

您会注意到在AND条件中首先调用TO_NUMBER函数,然后 正则表达式排除alpha值。我在想oracle可能会对AND条件进行短路评估,并且因为它首先执行TO_NUMBER,所以它会失败。

但是,当我们使用CAST函数时,交换评估顺序,并且 首先评估regexp排除。因为对于alpha值,它是假的,然后是 不评估AND子句的第二部分,并且查询有效。

   1 - filter( NOT REGEXP_LIKE (TRIM("ALPHANUMERIC_COLUMN"),'[^[:digit:]
              ]') AND CAST(TRIM("ALPHANUMERIC_COLUMN") AS NUMBER)>1)

Oracle有时会很奇怪。

答案 1 :(得分:1)

我相信,当涉及Predicate(where)子句时,Oracle可以/将根据需要对整个计划进行重新排序。因此,对于谓词,它会短路(如OldProgrammer所指出的)它想要的评估,并且你无法保证它发生的确切顺序。

在当前的SQL中,您依赖于谓词来删除非数字。一种选择是不使用“WHERE NOT regexp_like ...”而是使用带有coalesce的regexp_substr。例如:

create table t_tab2
(
  col varchar2(10)
);

create index t_tab2_idx on t_tab2(col);

insert into t_tab2
select level from dual
connect by level <= 100;

insert into t_tab2 values ('123ABC456');
commit;

-- select values > 95 (96->100 exclude non numbers)
select d.* from 
(
  select COALESCE(TO_NUMBER(REGEXP_SUBSTR(trim(col), '^\d+$')), 0) as nr
  from t_tab2
) d
where d.nr > 95;

这应该运行而不会丢失无效的数字错误。请注意,对于来自数据的任何非数字,coalesce将返回数字0,您可能希望根据您的需要和数据更改该数字。