Non penso che ci sia un modo semplice costruito; ed eseguire un controllo dinamico è relativamente facile (vedi esempio sotto). Ma come approccio piuttosto contorto potresti converti la stringa in un numero e torna in una stringa utilizzando un modello di formato costruito in base alla tua precisione e scala:
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;
/
Testato solo con pochi valori, ma finora sembra funzionare:
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
Utilizzando WHEN OTHERS
non è l'ideale e potresti sostituirlo con gestori di eccezioni specifici. Ho pensato che tu voglia che questo restituisca null se il numero non è valido, ma ovviamente potresti restituire qualsiasi cosa o lanciare la tua eccezione.
Il isNum2
colonna proviene da una seconda funzione, molto più semplice, che sta eseguendo il cast in modo dinamico, cosa che so che non vuoi fare, questo è solo per confronto:
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;
/
Ma nota che cast
arrotonda se la scala specificata è troppo piccola per il valore; Potrei aver interpretato "conforme a" troppo fortemente nella domanda poiché in quel caso sbaglio. Se vuoi qualcosa come '.123', 2, 2
essere consentito (dando .12
) quindi il secondo GetFormat
call e il controllo "scala troppo grande" possono essere rimossi dal mio IsNumber
. Potrebbero esserci anche altre sfumature che ho perso o interpretato male.
Vale anche la pena notare che l'iniziale to_number()
si basa sulle impostazioni NLS per i dati e la corrispondenza della sessione, in particolare il separatore decimale; e non consentirà un separatore di gruppo.
Potrebbe essere più semplice decostruire il valore numerico passato nella sua rappresentazione interna e vedere se si confronta con la precisione e la scala... anche se il percorso dinamico fa risparmiare molto tempo e fatica.