単純な組み込みの方法はないと思います。動的チェックの実行は比較的簡単です(以下の例を参照)。しかし、かなり複雑なアプローチとして、可能性があります 精度とスケールから構築されたフォーマットモデルを使用して、文字列を数値に変換し、文字列に戻します。
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
列は、キャストを動的に実行する、2番目のはるかに単純な関数からのものです。これは、実行したくないことはわかっています。これは比較のためです。
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
を与える )次に、2番目のGetFormat
呼び出しと「スケールが大きすぎます」チェックをIsNumber
から削除できます 。私が見逃した、または誤解した他のニュアンスもあるかもしれません。
また、最初のto_number()
データのNLS設定とセッションマッチング(特に小数点記号)に依存します。グループ区切り文字は使用できません。
渡された数値を内部表現に分解し、それが精度やスケールと比較されるかどうかを確認する方が簡単かもしれません...動的ルートは多くの時間と労力を節約しますが。