sql >> データベース >  >> RDS >> Oracle

精度とスケールを備えたOracleカスタムIsNumber関数

    単純な組み込みの方法はないと思います。動的チェックの実行は比較的簡単です(以下の例を参照)。しかし、かなり複雑なアプローチとして、可能性があります 精度とスケールから構築されたフォーマットモデルを使用して、文字列を数値に変換し、文字列に戻します。

    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設定とセッションマッチング(特に小数点記号)に依存します。グループ区切り文字は使用できません。

    渡された数値を内部表現に分解し、それが精度やスケールと比較されるかどうかを確認する方が簡単かもしれません...動的ルートは多くの時間と労力を節約しますが。




    1. Python + MySQLdb executemany

    2. 今日の日付のmysql結果を取得するにはどうすればよいですか?

    3. クエリを使用して既存のテーブルのSQL作成スクリプトを生成する

    4. 異なるMySQL列の日付と時刻を組み合わせて完全なDateTimeと比較するにはどうすればよいですか?