具有精度和规模的Oracle Custom IsNumber函数

时间:2014-09-17 16:18:28

标签: oracle oracle11g

是否可以编写一个Oracle函数来测试一个字符串是否符合数字精度和比例,同时在运行时提供精度和比例,而不是使用execute immediate?

功能签名就是这样:

FUNCTION IsNumber(pVALUE VARCHAR2, pPRECISION NUMBER, pSCALE NUMBER) RETURN NUMBER

做这样的事情无效:

DECLARE aNUMBER NUMBER(pPRECISION, pSCALE);

关于如何使这样的工作的任何想法?

1 个答案:

答案 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设置和会话匹配 - 尤其是小数分隔符;并且它不允许组分隔符。

将传递的数值解构为其内部表示可能更简单,并查看是否与精度和比例进行比较......尽管动态路由可以节省大量时间和精力。