sql >> Databasteknik >  >> RDS >> Oracle

Oracle Custom IsNumber-funktion med precision och skala

Jag tror inte att det finns något enkelt inbyggt sätt; och att göra en dynamisk kontroll är relativt lätt (se exempel nedan). Men som ett ganska invecklat tillvägagångssätt kunde du konvertera strängen till ett tal och tillbaka till en sträng med hjälp av en formatmodell konstruerad utifrån din precision och skala:

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;
/

Endast testat med ett fåtal värden men verkar fungera än så länge:

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 

Använder WHEN OTHERS är inte idealiskt och du kan ersätta det med specifika undantagshanterare. Jag har antagit att du vill att detta ska returnera null om numret inte är giltigt, men du kan naturligtvis returnera vad som helst eller göra ditt eget undantag.

isNum2 kolumnen är från en andra, mycket enklare funktion, som bara gör casten dynamiskt - vilket jag vet att du inte vill göra, det här är bara för jämförelse:

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;
/

Men observera att cast avrundar om den angivna skalan är för liten för värdet; Jag kan ha tolkat "överensstämmer med" för starkt i frågan då jag i så fall gör fel. Om du vill ha något som '.123', 2, 2 att tillåtas (ge .12 ) sedan den andra GetFormat samtalet och kryssrutan "skala för stor" kan tas bort från mitt IsNumber . Det kan finnas andra nyanser som jag har missat eller misstolkat också.

Också värt att notera att den initiala to_number() förlitar sig på NLS-inställningar för data och sessionsmatchning - särskilt decimalavgränsaren; och det tillåter inte en gruppseparator.

Det kan vara enklare att dekonstruera det passerade numeriska värdet till dess interna representation och se om det kan jämföras med precisionen och skalan... även om den dynamiska rutten sparar mycket tid och ansträngning.




  1. MYSQL + Välj 2 kolumner - 1 är unik

  2. Hur konverterar jag ett heltal till sträng som en del av en PostgreSQL-fråga?

  3. Komma igång med SQL Server 2017 på Linux i Azure-portalen

  4. Oracle Concurrent Manager – CP Analyzer för E-Business Suite