2014-01-21 97 views
3

为什么甲骨文“从双重选择级别”与TO_NUMBER结果不正常工作

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),而类似的版本与数字列即时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的类型是数量和类型nvarchar_2的alphanumeric_column。请注意,上面的例子没有数字比较(n.VAL> = 100)正常工作。

有人知道吗?

+0

那么,你有alphanumeric_column的值不是一个数字。如果您显示表架构和示例数据,这将有所帮助。 – OldProgrammer

+0

否:(从trim_(trim)(alphanumeric_column))中选择nr from my_table 其中not regexp_like(trim(alphanumeric_column),'[^ [:digit:]]')只留下数字值 – Ferenjito

+0

这很奇怪 – OldProgrammer

回答

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> 1约束,这是没有意义的。如果任何人都可以阐明为什么会发生这种情况,我将不胜感激。请参阅SQLFiddle example

更新:我做了一些更多的挖掘,因为我不喜欢不知道为什么某些方法可行。我对两个查询都运行了EXPLAIN PLAN,并得到了一些有趣的结果。

对于失败的查询,该谓语信息如下:

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

你会发现,TO_NUMBER功能在AND条件首先调用,然后 正则表达式来排除alpha值。我认为oracle可能会用AND条件进行短路评估,并且因为它首先执行TO_NUMBER,所以它会失败。

但是,当我们使用CAST函数时,评估顺序被交换,并首先评估正则表达式排除。由于对于alpha值,它是错误的,那么AND子句的第二部分不会被评估,并且查询起作用。

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

Oracle有时候可能会很奇怪。

+0

谢谢,这正是我一直在寻找。 – Ferenjito

+2

甲骨文评估这些措施失灵,因为[谓语推(http://docs.oracle.com/cd/E16655_01/server.121/e15858/tgsql_transform.htm#TGSQL210) 。有很多类型的转换,执行计划通常看起来不像原始查询,这些都是很好的性能特征,但是它们导致了一些这样的问题。最好的解决方案是**从不**存储数字和日期如字符串我不知道100%的解决方案将总是工作,你可能只是得到幸运这是非常困难的,迫使甲骨文以特定的顺序来评估的东西 –

+0

@jonearles - 。TOT盟友同意不把数字存储为字符串。感谢您对谓词推送的洞察力。今天学到了新东西。 – OldProgrammer

1

我相信,当谈到Predicate(where)子句时,Oracle可以/将按照它认为合适的顺序对整个计划进行重新排序。所以关于谓词,它会短路(正如OldProgrammer所指出的那样),但是它不需要评估,而且你也不能保证它发生的确切顺序。

在您当前的SQL中,您将根据谓词删除非数字。一种选择是不使用“WHERE NOT regexp_like ...”,而是使用regexp_substr和coalesce。例如:

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,您可能需要根据您的需求和数据更改该数字。

+0

很酷。更强大。 – OldProgrammer