ใช้ระหว่างตัวดำเนินการสำหรับสตริงที่เก็บตัวเลข

ฉันมีคอลัมน์ที่เก็บตัวเลขเป็นสตริงเนื่องจากลักษณะของคอลัมน์ที่คาดว่าจะมีประเภทข้อมูลประเภทใดก็ได้ เช่น วันที่ ตัวเลข ตัวเลขอัลฟ่า ฯลฯ

ตอนนี้ฉันต้องตรวจสอบว่าค่าในคอลัมน์นั้นอยู่ในช่วงที่กำหนดหรือไม่ นี่คือข้อมูลตัวอย่างสำหรับการทดสอบ

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 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
@ กอร์ดอน อเล็กซ์ ฉันลองใช้คำสั่งของคุณบนฐานข้อมูลของฉัน แต่แบบสอบถามยังคงไม่สร้างผลลัพธ์ใด ๆ เนื่องจากเราเห็นว่าแบบสอบถามนั้นสมบูรณ์แบบในรูปแบบของมัน แต่ตอนนี้ฉันสงสัยว่าหากแบบสอบถามเหล่านี้ให้ผลลัพธ์แก่คุณแล้วทำไมไม่อยู่ในฐานข้อมูลของฉัน จึงมีบางอย่างที่ต้องทำ ทำการตั้งค่า DB หรือเซสชันซึ่งป้องกันไม่ให้แบบสอบถามสร้างเอาต์พุต! - person mradul; 22.03.2016
comment
ฉันผิดที่คำสั่งของคุณให้ผลลัพธ์กับข้อมูลตัวอย่าง !! ขออภัยสำหรับความเห็นที่แล้ว :) - person mradul; 22.03.2016

คำตอบของ @ GordonLinoff ใช้ได้กับข้อมูลตัวอย่างที่คุณแสดง แต่จะมีข้อผิดพลาดกับ ORA-01722 "หมายเลขที่ไม่ถูกต้อง" หากคุณมีค่าใด ๆ ที่ไม่ได้แสดงถึงตัวเลข ข้อมูลตัวอย่างของคุณมีค่าที่ดีเท่านั้น แต่คุณบอกว่าสำหรับสนามจริงของคุณ "คาดว่าจะมีข้อมูลประเภทใดก็ได้ เช่น วันที่ ตัวเลข ตัวเลขอัลฟ่า ฯลฯ"

คุณสามารถหลีกเลี่ยงสิ่งนั้นได้ด้วยฟังก์ชันที่พยายามแปลงค่าสตริงที่เก็บไว้เป็นตัวเลข และส่งคืนค่าว่างหากได้รับข้อยกเว้นนั้น ตัวอย่างง่ายๆ:

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

ฉันคิดว่าแนวทางที่ดีที่สุดที่คุณสามารถลองใช้ได้ในเงื่อนไขนี้คือการใช้ฟังก์ชัน TRANSLATE เพื่อกำจัดอักขระตัวอักษรและตัวเลข เมื่อเสร็จแล้ว ตอนนี้เป็นเทคนิคของ OLD school ที่จะตรวจสอบข้อมูลโดยใช้ฟังก์ชัน 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;
person Avrajit Roy    schedule 21.03.2016
comment
OP กล่าวถึงวันที่ สิ่งนี้จะยังคงได้รับ ORA-01722 โดยมีวันที่ ISO เช่น 22-03-2559 และอาจเป็นตัวอย่างอื่นๆ โดยเฉพาะอย่างยิ่งหากเป็นข้อความที่มีรูปแบบอิสระ - 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