No creo que haya una manera simple de construir; y hacer una verificación dinámica es relativamente fácil (vea el ejemplo a continuación). Pero como un enfoque bastante enrevesado, podría convierta la cadena en un número y vuelva a ser una cadena usando un modelo de formato construido a partir de su precisión y escala:
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;
/
Solo se probó con algunos valores, pero parece funcionar hasta ahora:
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
Usando WHEN OTHERS
no es ideal y podría reemplazarlo con controladores de excepción específicos. Supuse que desea que esto devuelva nulo si el número no es válido, pero, por supuesto, puede devolver cualquier cosa o lanzar su propia excepción.
El isNum2
column es de una segunda función mucho más simple, que solo está haciendo el lanzamiento dinámicamente, lo que sé que no quieres hacer, esto es solo para comparar:
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;
/
Pero tenga en cuenta que cast
redondea si la escala especificada es demasiado pequeña para el valor; Es posible que haya interpretado "se ajusta a" con demasiada fuerza en la pregunta, ya que me estoy equivocando en ese caso. Si quieres algo como '.123', 2, 2
para ser permitido (dando .12
) luego el segundo GetFormat
la llamada y la verificación de "escala demasiado grande" se pueden eliminar de mi IsNumber
. Puede haber otros matices que también me he perdido o malinterpretado.
También vale la pena señalar que el to_number()
inicial se basa en la configuración de NLS para los datos y la coincidencia de sesión, en particular el separador decimal; y no permitirá un separador de grupo.
Podría ser más sencillo deconstruir el valor numérico pasado en su representación interna y ver si eso se compara con la precisión y la escala... aunque la ruta dinámica ahorra mucho tiempo y esfuerzo.