通过to_number()识别导致异常的行

时间:2011-08-09 12:19:29

标签: oracle

我有一个包含门牌号码的表格,如varchar2,如10,10a等。我想检测所有非数字的行,如'10a'。 我的解决方案是在尝试将“10a”等数据转换为数字时输出导致异常的每一行。

declare
number_correct number;
number_incorrect varchar2(4000);
begin 
for rec in(select '10' house_nr from dual union select '10a' house_nr from dual)
loop
number_correct:=to_number(rec.house_nr);
number_incorrect:=rec.house_nr;
end loop;
exception
when others then
dbms_output.put_line('correct: '||number_correct);
dbms_output.put_line('incorrect: '||number_incorrect);
end;

它应该表明不正确是10a,但它没有。

3 个答案:

答案 0 :(得分:5)

虽然@Dave对你提出的实际问题是正确的,但我认为你想要达到的目标与你正在做的不同。如上所述,PL / SQL块仅评估值,直到它到达第一个非数字。如果你想要评估所有的值,那么你需要这样的东西:

BEGIN
   FOR rec IN (SELECT '10' house_nr FROM DUAL
               UNION ALL
               SELECT '10a' house_nr FROM DUAL
               UNION ALL
               SELECT '11' house_nr FROM DUAL) LOOP
      error_fl         := FALSE;
      BEGIN
         number_correct   := TO_NUMBER(rec.house_nr);
      EXCEPTION
         WHEN VALUE_ERROR THEN
            error_fl   := TRUE;
         WHEN OTHERS THEN
            RAISE;
      END;

      IF error_fl THEN
         DBMS_OUTPUT.put_line('incorrect: ' || rec.house_nr);
      ELSE
         DBMS_OUTPUT.put_line('correct: ' || rec.house_nr);
      END IF;
   END LOOP;
END;

通过将异常处理移动到循环内部,我们可以在引发错误后继续处理。此外,如果发生的错误实际上是转换错误,则此版本仅返回“不正确”消息,而不是意外错误。当你为这样的特定条件编写错误处理时,尽可能精确地重要。


正如@Stephen所指出的,正则表达式通常比PL / SQL过程或函数更快,并且单个SQL语句通常比过程循环更好。但是,正则表达式函数只能在where子句中使用,因此如果要查看所有值的结果,则需要对表进行两次查询:

WITH test_num as (SELECT '10' house_nr FROM DUAL
                  UNION ALL
                  SELECT '10a' house_nr FROM DUAL
                  UNION ALL
                  SELECT '11' house_nr FROM DUAL)
SELECT 'correct' AS status, a.*
FROM   test_num a
WHERE  REGEXP_LIKE(t1, '[^[:digit:]]')
UNION ALL
SELECT 'incorrect', a.*
FROM   test_num a
WHERE  NOT REGEXP_LIKE(t1, '[^[:digit:]]');

如果您对正则表达式不满意,或者无法提供合适的表达式,您仍然可以通过创建自己的函数在单个SQL语句中执行此操作:

CREATE OR REPLACE FUNCTION is_num(p_string VARCHAR2)
   RETURN NUMBER
   DETERMINISTIC IS
   v_test     NUMBER;
BEGIN
   v_test   := p_string;
   RETURN 1;
EXCEPTION
   WHEN VALUE_ERROR THEN
      RETURN 0;
   WHEN OTHERS THEN
      RAISE;
END;

WITH test_num as (SELECT '10' house_nr FROM DUAL
                  UNION ALL
                  SELECT '10a' house_nr FROM DUAL
                  UNION ALL
                  SELECT '11' house_nr FROM DUAL)
SELECT CASE is_num(t1) WHEN 0 THEN 'correct' ELSE 'incorrect' END AS status, a.*
FROM   test_num a;

这在大多数情况下都不如使用正则表达式那么快,但它确实有一个优点:基于函数的索引对于regexp函数非常挑剔,但它们不应该对这样的函数有任何问题。看起来你不需要这个特定查询的索引,但要注意这一点。

答案 1 :(得分:2)

交换这两行的顺序:

number_correct:=to_number(rec.house_nr);
number_incorrect:=rec.house_nr;

使用它们的顺序,当第一行抛出异常时,跳过第二行,因此它报告循环的前一次迭代中的“不正确”值(根据定义,实际上不是不正确,因为它成功完成了。)

我也会删除该行

dbms_output.put_line('correct: '||number_correct);

根据定义,如果to_number()调用抛出异常,则number_correct的赋值将不会发生,因此在异常处理程序中输出其值似乎毫无意义。

答案 2 :(得分:1)

尝试使用正则表达式,例如:

select *
from  
(
    select '10a' a from dual
    union all 
    select '10' a from dual
) where regexp_like(a, '[^[:digit:]]');