是否可以编写一个Oracle函数来测试一个字符串是否符合数字精度和比例,同时在运行时提供精度和比例,而不是使用execute immediate?
功能签名就是这样:
FUNCTION IsNumber(pVALUE VARCHAR2, pPRECISION NUMBER, pSCALE NUMBER) RETURN NUMBER
做这样的事情无效:
DECLARE aNUMBER NUMBER(pPRECISION, pSCALE);
关于如何使这样的工作的任何想法?
答案 0 :(得分:2)
我认为没有任何简单的内置方式;并且进行动态检查相对容易(参见下面的示例)。但是作为一种相当复杂的方法,你可以将字符串转换为数字并使用根据你的精度和比例构建的格式模型返回字符串:
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
并不理想,您可以使用特定的异常处理程序替换它。如果数字不是有效的话,我假设您希望此返回null,但当然您可以返回任何内容,或者抛出您自己的异常。
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
会舍入;我可能已经解释了#34;符合&#34;问题过于强烈,因为我在这种情况下出错了。如果您希望允许'.123', 2, 2
之类的内容(提供.12
),那么第二次GetFormat
来电和“缩放太大”#39}可以从我的IsNumber
中删除支票。我可能还有其他细微差别或错误解释。
另外值得注意的是,初始to_number()
依赖于数据的NLS设置和会话匹配 - 尤其是小数分隔符;并且它不允许组分隔符。
将传递的数值解构为其内部表示可能更简单,并查看是否与精度和比例进行比较......尽管动态路由可以节省大量时间和精力。