Menggunakan operator antar untuk string yang menyimpan angka

Saya memiliki kolom di mana angka-angka disimpan sebagai string karena sifat kolom di mana segala jenis tipe data diharapkan seperti tanggal, angka, alfa numerik, dll.

Sekarang saya perlu memeriksa apakah nilai di kolom itu berada dalam kisaran yang ditentukan atau tidak. Berikut adalah contoh data untuk pengujian

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

Di sini rentang yang diharapkan di mana nilai seharusnya berada adalah 0-128 jika nilai tidak berada dalam rentang maka saya perlu memfilternya untuk diproses lebih lanjut.

Untuk ini saya telah menulis beberapa pertanyaan tetapi tidak ada satu pun yang memberikan keluaran yang diperlukan.

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

Kueri di atas menghasilkan keluaran yang diinginkan !! :(

Saya menggunakan versi DB - Oracle Database 12c Enterprise Edition Rilis 12.1.0.2.0 - Produksi 64bit


person mradul    schedule 21.03.2016    source sumber


Jawaban (3)


Biarkan saja formatnya keluar dari to_number():

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

Format numerik diperlukan untuk konversi ke karakter. Jika Anda meneruskannya ke to_number(), maka ia mengharapkan angka dengan format tersebut -- dan Anda mungkin salah memasukkan jumlah digitnya.

Atau, lebih baik lagi:

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

Atau, lebih baik lagi, ubah kolom agar berisi angka, bukan string.

Sunting:

Jika masalahnya adalah nilai Anda bukan angka (yang sangat berbeda dari pertanyaan awal Anda), maka ujilah. Ini adalah situasi di mana case sesuai dalam klausa where (karena case menjamin urutan evaluasi argumennya:

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 - apa artinya 'tidak berfungsi'? Dari data sampel Anda, kedua kueri ini akan menghasilkan 129 dan 200. Mengapa itu salah? (Ini akan error dengan tanggal dan string alfanumerik seperti yang Anda sebutkan dalam pertanyaan, tetapi data sampel Anda tidak memilikinya) - person Alex Poole; 21.03.2016
comment
@Gordon, Alex saya mencoba kueri Anda di database saya tetapi kueri tersebut tetap tidak menghasilkan keluaran karena kita dapat melihat kueri sempurna dalam bentuknya tetapi sekarang saya ragu jika kueri ini memberi Anda keluaran, mengapa tidak di DB saya, apakah ada sesuatu untuk lakukan pengaturan DB atau sesi yang mencegah kueri menghasilkan keluaran! - person mradul; 22.03.2016
comment
Saya salah, pertanyaan Anda memberikan keluaran pada data sampel !! Mohon maaf atas komentar sebelumnya :) - person mradul; 22.03.2016

Jawaban @ GordonLinoff berfungsi dengan data sampel yang Anda tunjukkan, tetapi akan terjadi kesalahan dengan ORA-01722 "nomor tidak valid" jika Anda memiliki nilai apa pun yang tidak mewakili angka. Data sampel Anda hanya memiliki nilai yang baik, tetapi Anda mengatakan bahwa untuk bidang sebenarnya "segala jenis tipe data diharapkan seperti tanggal, angka, alfa numerik, dll."

Anda dapat menyiasatinya dengan fungsi yang mencoba mengonversi nilai string yang disimpan menjadi angka, dan mengembalikan null jika mendapat pengecualian tersebut. Contoh sederhana:

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;
/

Anda kemudian dapat melakukannya

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

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

Apa pun yang tidak dapat dikonversi dan menyebabkan pengecualian kesalahan nilai ORA-06502 akan dianggap nol, yang bukan berada di antara atau tidak di antara nilai apa pun yang Anda berikan.

Jika Anda perlu memeriksa rentang tanggal, Anda dapat melakukan hal serupa, tetapi kemungkinan kesalahan lebih banyak, dan Anda mungkin memiliki tanggal dalam berbagai format; Anda perlu mendeklarasikan pengecualian dan menginisialisasinya ke nomor kesalahan yang diketahuiuntuk menangkap yang Anda harapkan untuk dilihat. Ini belum lengkap, tapi Anda bisa mulai dengan sesuatu seperti:

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

Meskipun saya biasanya tidak menggunakan between untuk tanggal; jika Anda tidak memilikinya dengan waktu yang ditentukan maka Anda akan lolos begitu saja di sini.

Anda dapat menggunakan when others untuk menangkap pengecualian apa pun tanpa harus mendeklarasikan semuanya, namun bahkan untuk pengecualian ini, hal tersebut berpotensi berbahaya - jika ada sesuatu yang rusak dengan cara yang tidak Anda duga, Anda ingin mengetahuinya, bukan Sembunyikan itu.


Tentu saja, ini adalah pelajaran penting mengapa Anda harus menyimpan data numerik di kolom NUMBER dan tanggal di bidang DATE atau TIMESTAMP - mencoba mengekstrak informasi berguna ketika semuanya disimpan sebagai string adalah hal yang berantakan, menyakitkan, dan tidak efisien.

person Alex Poole    schedule 21.03.2016

Saya rasa pendekatan terbaik yang dapat Anda coba dalam kondisi ini adalah menggunakan fungsi TRANSLATE untuk menghilangkan karakter alfanumerik. Setelah selesai semuanya sekarang adalah teknik jadul untuk memeriksa data dengan menggunakan fungsi NOT BETWEEN Semoga ini bisa membantu.

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
OP menyebutkan tanggal; ini masih akan mendapatkan ORA-01722 dengan tanggal ISO seperti 22-03-2016. Dan mungkin contoh lainnya, terutama jika itu berupa teks berformat bebas. - person Alex Poole; 22.03.2016
comment
Hai Alex. Saya tidak bermaksud memblokir penawaran apa pun. Salahku. Saya pikir headernya harus diblok, jadi saya melakukannya. - person Avrajit Roy; 22.03.2016
comment
Tentu akan mencoba untuk tetap sederhana :) - person Avrajit Roy; 22.03.2016
comment
Dan jika saya menghapus .+- dari terjemahan, itu juga akan berfungsi untuk tanggal. Tidakkah kamu berpikir demikian? - person Avrajit Roy; 22.03.2016
comment
Ya, tapi itu tidak akan menangani angka negatif dengan benar. - person Alex Poole; 22.03.2016
comment
Mari kita melanjutkan diskusi ini dalam chat. - person Avrajit Roy; 22.03.2016