Oracle
 sql >> база данни >  >> RDS >> Oracle

Персонализирана функция IsNumber на Oracle с точност и мащаб

Не мисля, че има някакъв прост вграден начин; и извършването на динамична проверка е относително лесно (вижте примера по-долу). Но като доста сложен подход вие можете преобразувайте низа в число и обратно в низ, като използвате модел на формат, конструиран от вашата прецизност и мащаб:

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 за съвпадение на данните и сесията - особено десетичния разделител; и няма да позволи групов разделител.

Може да е по-лесно да деконструирате подадената числова стойност в нейното вътрешно представяне и да видите дали това се сравнява с прецизността и мащаба... въпреки че динамичният маршрут спестява много време и усилия.




  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Заявка за таблица и име на колона, съхранени в таблица

  2. Най-добрият начин за обработка на LOB в разпределени бази данни на Oracle

  3. PL/SQL има ли еквивалентен StringTokenizer на Java?

  4. Как да изчислим общата сума в SQL

  5. Събиране на статистика за индекс или създаване на пускане?