Использование между оператором для строки, которая хранит числа

У меня есть столбец, в котором числа хранятся в виде строки из-за характера столбца, где ожидается любой тип данных, например дата, числа, буквенно-цифровые и т. д.

Теперь мне нужно проверить, находятся ли значения в этом столбце в определенном диапазоне или нет, вот пример данных для тестирования.

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')
;

Эти вышеуказанные запросы дают желаемый результат !! :(

Я использую версию БД -- Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64-битная производственная версия


person mradul    schedule 21.03.2016    source источник


Ответы (3)


Просто оставьте формат вне 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'
person Gordon Linoff    schedule 21.03.2016
comment
@mradul - что значит «не работает»? Из ваших выборочных данных оба этих запроса дадут 129 и 200. Почему это неправильно? (Это будет ошибка с датами и буквенно-цифровыми строками, как вы упомянули в вопросе, но ваши образцы данных не имеют ни одного из них) - person Alex Poole; 21.03.2016
comment
@Gordon, Алекс, я попробовал ваши запросы в своей базе данных, но запросы все равно не выводят результатов, поскольку мы видим, что запросы идеальны по своей форме, но теперь я сомневаюсь, что если эти запросы дают вам результаты, то почему бы не в моей БД есть что-то для сделать настройку БД или сеанса, которая не позволяет запросам производить вывод! - person mradul; 22.03.2016
comment
Я ошибался, ваши запросы выдают данные по образцам!! Извиняюсь за предыдущий комментарий :) - person mradul; 22.03.2016

Ответ @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, будет рассматриваться как нуль, который не находится ни между, ни не между любыми указанными вами значениями.

Если вам нужно проверить диапазоны дат, вы можете сделать что-то подобное, но возможны другие ошибки, и у вас могут быть даты в нескольких форматах; вам нужно будет объявить исключения и инициализировать их известными номерами ошибок чтобы поймать те, которые вы ожидаете увидеть. Это не завершено, но вы можете начать с чего-то вроде:

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 — пытаться извлечь полезную информацию, когда все хранится в виде строк, беспорядочно, болезненно и неэффективно.

person Alex Poole    schedule 21.03.2016

Я думаю, что лучший подход, который вы можете попробовать в этом состоянии, — использовать функцию ПЕРЕВОД, чтобы исключить буквенно-цифровые символы. После того, как это будет сделано, все теперь будет СТАРОЙ школьной техникой для проверки данных с помощью функции НЕ МЕЖДУ Надеюсь, это поможет.

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;
person Avrajit Roy    schedule 21.03.2016
comment
В ОП упоминаются даты; это все равно получит ORA-01722 с датой ISO, например 2016-03-22. И, возможно, другие примеры, особенно если это может быть текст в произвольном формате. - person Alex Poole; 22.03.2016
comment
Привет, Алекс. Я не имею в виду блокировать кавычки. Виноват. Я думаю, что заголовки должны заключаться в блочные кавычки, поэтому я так и делаю. - person Avrajit Roy; 22.03.2016
comment
Конечно, постараюсь упростить :) - person Avrajit Roy; 22.03.2016
comment
И если я удалю .+- из перевода, это должно работать и для дат. Вам не кажется? - person Avrajit Roy; 22.03.2016
comment
Да, но тогда он не будет правильно обрабатывать отрицательные числа. - person Alex Poole; 22.03.2016
comment
Давайте продолжим обсуждение в чате. - person Avrajit Roy; 22.03.2016