Oracle
 sql >> Datenbank >  >> RDS >> Oracle

Benutzerdefinierte IsNumber-Funktion von Oracle mit Genauigkeit und Skalierung

Ich glaube nicht, dass es einen einfachen eingebauten Weg gibt; und eine dynamische Prüfung durchzuführen ist relativ einfach (siehe Beispiel unten). Aber als ziemlich komplizierter Ansatz, den Sie könnten Konvertieren Sie die Zeichenfolge in eine Zahl und zurück in eine Zeichenfolge, indem Sie ein Formatmodell verwenden, das aus Ihrer Genauigkeit und Skalierung erstellt wurde:

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

Nur mit wenigen Werten getestet, scheint aber bisher zu funktionieren:

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 

Verwenden von WHEN OTHERS ist nicht ideal und Sie könnten dies durch bestimmte Ausnahmehandler ersetzen. Ich bin davon ausgegangen, dass Sie möchten, dass dies null zurückgibt, wenn die Zahl nicht gültig ist, aber Sie könnten natürlich alles zurückgeben oder Ihre eigene Ausnahme auslösen.

Der isNum2 Spalte stammt von einer zweiten, viel einfacheren Funktion, die die Umwandlung nur dynamisch durchführt - was ich weiß, dass Sie das nicht tun möchten, dies dient nur zum Vergleich:

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

Aber beachten Sie, dass cast rundet, wenn die angegebene Skala zu klein für den Wert ist; Möglicherweise habe ich "entspricht" in der Frage zu stark interpretiert, da ich mich in diesem Fall irre. Wenn Sie etwas wie '.123', 2, 2 wollen erlaubt sein (mit .12 ) dann das zweite GetFormat Call und der Haken bei 'Scale too large' kann von meiner IsNumber entfernt werden . Es kann auch andere Nuancen geben, die ich übersehen oder falsch interpretiert habe.

Erwähnenswert ist auch, dass die anfängliche to_number() stützt sich auf NLS-Einstellungen für die Daten und den Sitzungsabgleich - insbesondere das Dezimaltrennzeichen; und es erlaubt kein Gruppentrennzeichen.

Es könnte einfacher sein, den übergebenen numerischen Wert in seine interne Darstellung zu zerlegen und zu sehen, ob dies mit der Genauigkeit und Skalierung vergleichbar ist ... obwohl die dynamische Route viel Zeit und Mühe spart.