Nemyslím si, že existuje nějaký jednoduchý způsob; a provedení dynamické kontroly je relativně snadné (viz příklad níže). Ale jako poněkud spletitý přístup byste mohli převeďte řetězec na číslo a zpět na řetězec pomocí formátu formátu vytvořeného z vaší přesnosti a měřítka:
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;
/
Testováno pouze s několika hodnotami, ale zdá se, že zatím funguje:
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
Pomocí WHEN OTHERS
není ideální a můžete to nahradit konkrétními obslužnými nástroji výjimek. Předpokládal jsem, že chcete, aby to vrátilo hodnotu null, pokud číslo není platné, ale samozřejmě můžete vrátit cokoli nebo vyvolat vlastní výjimku.
isNum2
sloupec je z druhé, mnohem jednodušší funkce, která pouze dynamicky provádí přetypování - což vím, že nechcete, toto je jen pro srovnání:
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;
/
Všimněte si však, že cast
zaokrouhlí, pokud je zadané měřítko pro danou hodnotu příliš malé; Možná jsem v otázce interpretoval „vyhovuje“ příliš silně, protože se v tom případě mýlím. Pokud chcete něco jako '.123', 2, 2
má být povoleno (poskytuje .12
) a poté druhý GetFormat
a zaškrtnutí 'měřítko je příliš velké' může být odstraněno z mého IsNumber
. Mohou existovat i další nuance, které jsem přehlédl nebo si je špatně vyložil.
Také stojí za zmínku, že počáteční to_number()
spoléhá na nastavení NLS pro data a shodu relace - zejména oddělovač desetinných míst; a nepovolí oddělovač skupin.
Mohlo by být jednodušší dekonstruovat předávanou číselnou hodnotu do její vnitřní reprezentace a zjistit, zda se to srovnává s přesností a měřítkem... i když dynamická trasa ušetří spoustu času a úsilí.