Oracle
 sql >> Base de données >  >> RDS >> Oracle

Fonction personnalisée Oracle IsNumber avec précision et échelle

Je ne pense pas qu'il y ait une méthode intégrée simple; et faire une vérification dynamique est relativement facile (voir exemple ci-dessous). Mais en tant qu'approche plutôt alambiquée, vous pourriez convertissez la chaîne en nombre et de nouveau en chaîne à l'aide d'un modèle de format construit à partir de votre précision et de votre échelle :

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

Seulement testé avec quelques valeurs mais semble fonctionner jusqu'à présent :

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 

Utiliser WHEN OTHERS n'est pas idéal et vous pouvez le remplacer par des gestionnaires d'exceptions spécifiques. J'ai supposé que vous vouliez que cela renvoie null si le nombre n'est pas valide, mais bien sûr, vous pouvez renvoyer n'importe quoi ou lancer votre propre exception.

Le isNum2 colonne provient d'une deuxième fonction beaucoup plus simple, qui fait juste le cast dynamiquement - ce que je sais que vous ne voulez pas faire, c'est juste pour la comparaison :

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

Mais notez que cast arrondit si l'échelle spécifiée est trop petite pour la valeur ; J'ai peut-être trop interprété "conforme à" dans la question car je me trompe dans ce cas. Si vous voulez quelque chose comme '.123', 2, 2 être autorisé (donnant .12 ) puis le deuxième GetFormat appel et la vérification "échelle trop grande" peut être supprimée de mon IsNumber . Il peut y avoir d'autres nuances que j'ai manquées ou mal interprétées également.

Il convient également de noter que l'initiale to_number() s'appuie sur les paramètres NLS pour les données et la correspondance de session - le séparateur décimal en particulier ; et il n'autorisera pas de séparateur de groupe.

Il pourrait être plus simple de déconstruire la valeur numérique transmise dans sa représentation interne et de voir si cela se compare à la précision et à l'échelle... bien que la route dynamique économise beaucoup de temps et d'efforts.