sql >> Database >  >> RDS >> Oracle

Oracle Custom IsNumber-functie met precisie en schaal

Ik denk niet dat er een eenvoudige manier is ingebouwd; en het uitvoeren van een dynamische controle is relatief eenvoudig (zie onderstaand voorbeeld). Maar als een nogal ingewikkelde benadering zou converteer de tekenreeks naar een getal en terug naar een tekenreeks met behulp van een formaatmodel dat is samengesteld op basis van uw precisie en schaal:

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

Alleen getest met een paar waarden, maar lijkt tot nu toe te werken:

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 gebruiken is niet ideaal en je zou dat kunnen vervangen door specifieke uitzonderingshandlers. Ik heb aangenomen dat je wilt dat dit null retourneert als het nummer niet geldig is, maar je kunt natuurlijk alles teruggeven, of je eigen uitzondering maken.

De isNum2 kolom is van een tweede, veel eenvoudigere functie, die de cast gewoon dynamisch uitvoert - wat ik weet dat je niet wilt doen, dit is alleen ter vergelijking:

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

Maar merk op dat cast rondt af als de opgegeven schaal te klein is voor de waarde; Ik heb "voldoet aan" misschien te sterk geïnterpreteerd in de vraag, omdat ik in dat geval een fout maak. Als je iets wilt als '.123', 2, 2 worden toegestaan ​​(geven van .12 ) dan de tweede GetFormat oproep en het vinkje 'schaal te groot' kan worden verwijderd uit mijn IsNumber . Er kunnen ook andere nuances zijn die ik heb gemist of verkeerd heb geïnterpreteerd.

Ook vermeldenswaard is dat de initiële to_number() vertrouwt op NLS-instellingen voor de gegevens en de sessie-overeenkomst - met name het decimaalteken; en het zal geen groepsscheidingsteken toestaan.

Het is misschien eenvoudiger om de doorgegeven numerieke waarde te deconstrueren in zijn interne representatie en te kijken of dat overeenkomt met de precisie en schaal... hoewel de dynamische route veel tijd en moeite bespaart.




  1. INSERT met dynamische tabelnaam in triggerfunctie

  2. Oracle.jdbc.driver.OracleDriver-uitzondering voor Java voor beginners

  3. Maak eerst opgeslagen procedures met behulp van Entity Framework Code?

  4. Is Python flask.ext.mysql verouderd?