Не мисля, че има някакъв прост вграден начин; и извършването на динамична проверка е относително лесно (вижте примера по-долу). Но като доста сложен подход вие можете преобразувайте низа в число и обратно в низ, като използвате модел на формат, конструиран от вашата прецизност и мащаб:
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;
/
Тествано само с няколко стойности, но изглежда работи досега:
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
Използване на WHEN OTHERS
не е идеален и можете да го замените със специфични манипулатори на изключения. Предполагам, че искате това да върне нула, ако номерът не е валиден, но разбира се можете да върнете всичко или да хвърлите собствено изключение.
isNum2
колоната е от втора, много по-проста функция, която просто извършва динамичното прехвърляне - което знам, че не искате да правите, това е само за сравнение:
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;
/
Но имайте предвид, че cast
закръглява, ако посоченият мащаб е твърде малък за стойността; Може да съм тълкувал твърде силно „съответства на“ във въпроса, тъй като греша в този случай. Ако искате нещо като '.123', 2, 2
да бъде разрешено (давайки .12
), след това вторият GetFormat
повикване и проверката „мащабът е твърде голям“ може да бъде премахнат от моя IsNumber
. Възможно е да има и други нюанси, които съм пропуснал или изтълкувал погрешно.
Също така си струва да се отбележи, че първоначалният to_number()
разчита на настройките на NLS за съвпадение на данните и сесията - особено десетичния разделител; и няма да позволи групов разделител.
Може да е по-лесно да деконструирате подадената числова стойност в нейното вътрешно представяне и да видите дали това се сравнява с прецизността и мащаба... въпреки че динамичният маршрут спестява много време и усилия.