sql >> Databáze >  >> RDS >> Oracle

Funkce Oracle Custom IsNumber s přesností a měřítkem

Nemyslím si, že existuje nějaký jednoduchý způsob; a provedení dynamické kontroly je relativně snadné (viz příklad níže). Ale jako poněkud spletitý přístup byste mohli převeďte řetězec na číslo a zpět na řetězec pomocí formátu formátu vytvořeného z vaší přesnosti a měřítka:

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

Testováno pouze s několika hodnotami, ale zdá se, že zatím funguje:

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 

Pomocí WHEN OTHERS není ideální a můžete to nahradit konkrétními obslužnými nástroji výjimek. Předpokládal jsem, že chcete, aby to vrátilo hodnotu null, pokud číslo není platné, ale samozřejmě můžete vrátit cokoli nebo vyvolat vlastní výjimku.

isNum2 sloupec je z druhé, mnohem jednodušší funkce, která pouze dynamicky provádí přetypování - což vím, že nechcete, toto je jen pro srovnání:

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

Všimněte si však, že cast zaokrouhlí, pokud je zadané měřítko pro danou hodnotu příliš malé; Možná jsem v otázce interpretoval „vyhovuje“ příliš silně, protože se v tom případě mýlím. Pokud chcete něco jako '.123', 2, 2 má být povoleno (poskytuje .12 ) a poté druhý GetFormat a zaškrtnutí 'měřítko je příliš velké' může být odstraněno z mého IsNumber . Mohou existovat i další nuance, které jsem přehlédl nebo si je špatně vyložil.

Také stojí za zmínku, že počáteční to_number() spoléhá na nastavení NLS pro data a shodu relace - zejména oddělovač desetinných míst; a nepovolí oddělovač skupin.

Mohlo by být jednodušší dekonstruovat předávanou číselnou hodnotu do její vnitřní reprezentace a zjistit, zda se to srovnává s přesností a měřítkem... i když dynamická trasa ušetří spoustu času a úsilí.




  1. Vyberte nejnovější uzel pro každý definovaný termín taxonomie v Drupalu 6

  2. Jak zkontrolovat, zda je datum mezi datem1 a date2 pomocí mysql?

  3. PHP skript s PostgreSQL příkazy vracejícími NULL pro data JSon

  4. Převést WM_CONCAT na Listagg