Oracle
 sql >> Teknologi Basis Data >  >> RDS >> Oracle

Fungsi Oracle Custom IsNumber dengan Presisi dan Skala

Saya tidak berpikir ada cara yang sederhana; dan melakukan pemeriksaan dinamis relatif mudah (lihat contoh di bawah). Tetapi sebagai pendekatan yang agak berbelit-belit, Anda bisa mengonversi string menjadi angka dan kembali ke string menggunakan model format yang dibuat dari presisi dan skala Anda:

CREATE OR REPLACE FUNCTION IsNumber(pVALUE VARCHAR2, pPRECISION NUMBER,
  pSCALE NUMBER) RETURN NUMBER
IS
  lFORMAT VARCHAR2(80);
  lNUMBER NUMBER;
  lSTRING NUMBER;

  FUNCTION GetFormat(p NUMBER, s NUMBER) RETURN VARCHAR2 AS
  BEGIN
    RETURN
      CASE WHEN p >= s THEN LPAD('9', p - s, '9') END
        || CASE WHEN s > 0 THEN '.' || CASE WHEN s > p THEN
            LPAD('0', s - p, '0') || RPAD('9', p, '9')
          ELSE RPAD('9', s, '9') END
      END;
  END GetFormat;
BEGIN
  -- sanity-check values; other checks needed (precision <= 38?)
  IF pPRECISION = 0 THEN
    RETURN NULL;
  END IF;

  -- check it's actually a number
  lNUMBER := TO_NUMBER(pVALUE);

  -- get it into the expected format; this will error if the precision is
  -- exceeded, but scale is rounded so doesn't error
  lFORMAT := GetFormat(pPRECISION, pSCALE);
  lSTRING := to_char(lNUMBER, lFORMAT, 'NLS_NUMERIC_CHARACTERS='',.''');

  -- to catch scale rounding, check against a greater scale
  -- note: this means we reject numbers that CAST will allow but round
  lFORMAT := GetFormat(pPRECISION + 1, pSCALE + 1);

  IF lSTRING != to_char(lNUMBER, lFORMAT, 'NLS_NUMERIC_CHARACTERS='',.''') THEN
    RETURN NULL;  -- scale too large
  END IF;
  RETURN lNUMBER;
EXCEPTION
  WHEN OTHERS THEN
    RETURN NULL;  -- not a number, precision too large, etc.
END IsNumber;
/

Hanya diuji dengan beberapa nilai tetapi tampaknya berfungsi sejauh ini:

with t as (
  select '0.123' as value, 3 as precision, 3 as scale from dual
  union all select '.123', 2, 2 from dual
  union all select '.123', 1, 3 from dual
  union all select '.123', 2, 2 from dual
  union all select '1234', 4, 0 from dual
  union all select '1234', 3, 1 from dual
  union all select '123', 2, 0 from dual
  union all select '.123', 0, 3 from dual
  union all select '-123.3', 4, 1 from dual
  union all select '123456.789', 6, 3 from dual
  union all select '123456.789', 7, 3 from dual
  union all select '101.23253232', 3, 8 from dual
  union all select '101.23253232', 11, 8 from dual
)
select value, precision, scale,
  isNumber(value, precision, scale) isNum,
  isNumber2(value, precision, scale) isNum2
from t;

VALUE         PRECISION      SCALE      ISNUM     ISNUM2
------------ ---------- ---------- ---------- ----------
0.123                 3          3       .123       .123 
.123                  2          2                   .12 
.123                  1          3       .123            
.123                  2          2                   .12 
1234                  4          0       1234       1234 
1234                  3          1                       
123                   2          0                       
.123                  0          3                       
-123.3                4          1     -123.3     -123.3 
123456.789            6          3                       
123456.789            7          3                       
101.23253232          3          8                       
101.23253232         11          8 101.232532 101.232532 

Menggunakan WHEN OTHERS tidak ideal dan Anda bisa menggantinya dengan penangan pengecualian khusus. Saya berasumsi Anda ingin ini mengembalikan nol jika nomornya tidak valid, tetapi tentu saja Anda dapat mengembalikan apa pun, atau melemparkan pengecualian Anda sendiri.

isNum2 kolom berasal dari fungsi kedua yang jauh lebih sederhana, yang hanya melakukan pemeran secara dinamis - yang saya tahu Anda tidak ingin melakukannya, ini hanya untuk perbandingan:

CREATE OR REPLACE FUNCTION IsNumber2(pVALUE VARCHAR2, pPRECISION NUMBER,
  pSCALE NUMBER) RETURN NUMBER
IS
  str VARCHAR2(80);
  num NUMBER;
BEGIN
  str := 'SELECT CAST(:v AS NUMBER(' || pPRECISION ||','|| pSCALE ||')) FROM DUAL';
  EXECUTE IMMEDIATE str INTO num USING pVALUE;
  RETURN num;
EXCEPTION
  WHEN OTHERS THEN
    RETURN NULL;
END IsNumber2;
/

Namun perhatikan bahwa cast putaran jika skala yang ditentukan terlalu kecil untuk nilainya; Saya mungkin telah menafsirkan "sesuai dengan" terlalu kuat dalam pertanyaan karena saya salah dalam kasus itu. Jika Anda menginginkan sesuatu seperti '.123', 2, 2 diizinkan (memberi .12 ) lalu GetFormat kedua panggilan dan centang 'skala terlalu besar' dapat dihapus dari IsNumber saya . Mungkin ada nuansa lain yang saya lewatkan atau salah tafsirkan.

Juga perlu diperhatikan bahwa to_number() initial awal bergantung pada pengaturan NLS untuk data dan pencocokan sesi - khususnya pemisah desimal; dan itu tidak akan mengizinkan pemisah grup.

Mungkin lebih mudah untuk mendekonstruksi nilai numerik yang diteruskan ke dalam representasi internalnya dan melihat apakah itu sebanding dengan presisi dan skalanya... meskipun rute dinamis menghemat banyak waktu dan tenaga.




  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Jumlah hari Jumat di antara dua tanggal

  2. Parsing Json menggunakan Oracle SQL - JSON_TABLE

  3. Mengapa ada panjang maksimum untuk nama prosedur tersimpan?

  4. Cara memperbaiki kesalahan "Tidak ada pemetaan Dialek untuk tipe JDBC:-1" di java

  5. SQL bukan fungsi grup grup tunggal