为什么
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)。
有人知道吗?
答案 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,您可能希望根据您的需要和数据更改该数字。