在运算符之间使用存储数字的字符串

时间:2016-03-21 14:19:40

标签: sql oracle plsql oracle11g oracle12c

我有一个列,其中数字存储为字符串,因为列的性质,预期任何类型的数据类型,如日期,数字,字母数字, 等。

现在我需要检查该列中的值是否在定义的范围内,这里是用于测试的样本数据

create table test (val varchar2(10));

insert into test values ('0'); 
insert into test values ('67');
insert into test values ('129');
insert into test values ('200');
insert into test values ('1');

如果值不在范围内,那么值应该是0-128的预期范围,然后我需要将它们过滤掉以便进一步处理。

为此,我已经写了一些查询但是没有一个是要求输出。

select * 
  from test 
  where val not between '0' and '128';

select * 
  from test  
to_number(val, '9') not between to_number('0', '9') and to_number('128', '9999');

select * from test where  
to_number(val, '9') < TO_NUMBER('0', '9') 
or 
to_number(val, '999') > TO_NUMBER('128', '999')
;

以上查询产生了所需的输出!! :(

我使用的是DB版本 - Oracle Database 12c企业版12.1.0.2.0版 - 64位生产

3 个答案:

答案 0 :(得分:2)

只需将格式保留在to_number()

之外
select * 
from test  
where to_number(val) not between to_number('0') and to_number('128');

转换为字符时需要数字格式。如果你将它传递给to_number(),那么它需要一些格式 - 你可能会得到错误的位数。

或者,更好的是:

select * 
from test  
where to_number(val) not between 0 and 128;

或者,更好的是,将列更改为包含数字而不是字符串。

编辑:

如果问题是您的值不是数字(与原始问题完全不同),那么请测试一下。这是case子句中where适用的一种情况(因为case保证了其参数的评估顺序:

where (case when regexp_like(val, '[^-0-9]') then 'bad'
            when cast(val as number) < 0 then 'bad'
            when cast(val as number) > 128 then 'bad'
            else 'good'
       end) = 'bad'

答案 1 :(得分:1)

@ GordonLinoff的答案适用于您显示的样本数据,但如果您有任何不代表数字的值,则ORA-01722“无效数字”会出错。您的示例数据只有很好的值,但您说的是对于您的真实字段“预期任何类型的数据类型,如日期,数字,字母数字等。”

您可以使用试图将存储的字符串值转换为数字的函数来解决这个问题,如果它获得该异常则返回null。一个简单的例子:

create function safe_to_number (p_str varchar2) return number is
begin
  return to_number(p_str);
exception
  when value_error then
    return null;
end;
/

然后你可以做

select * 
from test  
where safe_to_number(val) not between 0 and 128;

VAL      
----------
129       
200       

任何无法转换并导致ORA-06502值错误异常的内容都将被视为空,在您提供的任何值之间既不是也不是

如果您需要检查日期范围,您可以执行类似的操作,但可能存在更多错误,并且您可能有多种格式的日期;你需要声明异常and initialise them to known error numbers来捕捉你期望看到的异常。这不完整,但您可以从以下内容开始:

create function safe_to_date (p_str varchar2) return date is
  l_formats sys.odcivarchar2list;
  format_ex_1 exception;
  format_ex_2 exception;
  format_ex_3 exception;
  format_ex_4 exception;
  format_ex_5 exception;
  pragma exception_init(format_ex_1, -1840);
  pragma exception_init(format_ex_2, -1841);
  pragma exception_init(format_ex_3, -1847);
  pragma exception_init(format_ex_4, -1858);
  pragma exception_init(format_ex_5, -1861);
  -- add any others you might get
begin
  -- define all expected formats
  l_formats := sys.odcivarchar2list('YYYY-MM-DD', 'DD/MM/YYYY', 'DD-MON-RRRR'); -- add others
  for i in 1..l_formats.count loop
      begin
        return to_date(p_str, l_formats(i));
      exception
        when format_ex_1 or format_ex_2 or format_ex_3 or format_ex_4 or format_ex_5 then
          -- ignore the exception; carry on and try the next format
          null;
      end;
  end loop;

  -- did not match any expected formats
  return null;
end;
/

select * 
from test  
where safe_to_date(val) not between date '2016-02-01' and date '2016-02-29';

虽然我通常不会将between用于日期;如果你没有指定时间,那么你就可以在这里侥幸成功。

您可以使用when others来捕获任何异常,而不必全部声明它们,但即使这样也存在潜在危险 - 如果某些事情以某种方式破坏您不期望您我想知道它,而不是隐藏它。

当然,这是一个对象课程,为什么你应该在NUMBER列中存储数字数据,在DATE或TIMESTAMP字段中存储日期 - 当所有内容都存储为字符串时,尝试提取有用的信息是混乱,痛苦和低效的。

答案 2 :(得分:0)

  

我认为在这种情况下您可以尝试的最佳方法是使用   TRANSLATE函数消除字母数字字符。一旦它   现在做的就是OLD学校使用NOT检查数据的技术   BETWEEN功能希望这有帮助。

SELECT B.NM
FROM
  (SELECT a.nm
  FROM
    (SELECT '0' AS nm FROM dual
    UNION
    SELECT '1' AS nm FROM dual
    UNION
    SELECT '68' AS nm FROM dual
    UNION
    SELECT '129' AS nm FROM dual
    UNION
    SELECT '200' AS nm FROM dual
    UNION
    SELECT '125a' AS nm FROM dual
    )a
  WHERE TRANSLATE(a.nm, ' +-.0123456789', ' ') IS NULL
  )b
WHERE b.nm NOT BETWEEN 1 AND 128;