sql >> Database teknologi >  >> RDS >> Oracle

Oracle Custom IsNumber-funktion med præcision og skalering

Jeg tror ikke, der er nogen enkel indbygget måde; og det er relativt nemt at lave et dynamisk tjek (se eksempel nedenfor). Men som en ret indviklet tilgang kunne du konverter strengen til et tal og tilbage til en streng ved hjælp af en formatmodel konstrueret ud fra din præcision og skala:

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

Kun testet med få værdier, men ser ud til at virke indtil videre:

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 

Brug af WHEN OTHERS er ikke ideel, og du kan erstatte det med specifikke undtagelsesbehandlere. Jeg har antaget, at du ønsker, at dette skal returnere null, hvis nummeret ikke er gyldigt, men du kan selvfølgelig returnere hvad som helst eller kaste din egen undtagelse.

isNum2 kolonne er fra en anden, meget enklere funktion, som bare udfører castet dynamisk - hvilket jeg ved, du ikke vil gøre, dette er kun til sammenligning:

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

Men bemærk, at cast runder, hvis den angivne skala er for lille til værdien; Jeg kan have fortolket "overensstemmende med" for stærkt i spørgsmålet, da jeg tager fejl i det tilfælde. Hvis du vil have noget som '.123', 2, 2 skal tillades (giver .12 ) derefter den anden GetFormat opkald og "skala for stor"-check kan fjernes fra mit IsNumber . Der kan også være andre nuancer, jeg har savnet eller misfortolket.

Også værd at bemærke, at den indledende to_number() er afhængig af NLS-indstillinger for dataene og sessionsmatchningen - især decimalseparatoren; og det tillader ikke en gruppeseparator.

Det kan være lettere at dekonstruere den beståede numeriske værdi i dens interne repræsentation og se, om det kan sammenlignes med præcisionen og skalaen... selvom den dynamiske rute sparer en masse tid og kræfter.




  1. Holder flere elementer i en kolonne på mysql

  2. MYSQl Optimer tabel over blogindlæg med kommentarer

  3. SQLite kan ikke åbne databasefil (kode 14) ved hyppig SELECT-forespørgsel

  4. Mysql joinforespørgsel på tre tabeller med flere kolonner