web-dev-qa-db-fra.com

"Safe" TO_NUMBER ()

SELECT TO_NUMBER('*') FROM DUAL

Cela me donne évidemment une exception:

ORA-01722: numéro invalide

Existe-t-il un moyen de le "sauter" et d'obtenir 0 ou NULL à la place?

Toute la question: j'ai NVARCHAR2 champ, qui contient des nombres et pas presque ;-) (comme *) et je dois sélectionner le plus grand nombre dans la colonne.

Oui, je sais que c'est un design terrible, mais c'est ce dont j'ai besoin maintenant ...: -S

UPD :

Pour moi, j'ai résolu ce problème avec

COALESCE(TO_NUMBER(REGEXP_SUBSTR(field, '^\d+')), 0)
50
zerkms

Je n'ai rien trouvé de mieux que ça:

function safe_to_number(p varchar2) return number is
    v number;
  begin
    v := to_number(p);
    return v;
  exception when others then return 0;
end;
15
Gabe
COALESCE(TO_NUMBER(REGEXP_SUBSTR(field, '^\d+(\.\d+)?')), 0) 

obtiendra également des nombres avec une échelle> 0 (chiffres à droite de la virgule décimale).

26
pweitzman

De Oracle Database 12c Release 2 vous pouvez utiliser TO_NUMBER avec DEFAULT ... ON CONVERSION ERROR:

SELECT TO_NUMBER('*' DEFAULT 0 ON CONVERSION ERROR) AS "Value"
FROM DUAL;

Ou CAST:

SELECT CAST('*' AS NUMBER DEFAULT 0 ON CONVERSION ERROR) AS "Value"
FROM DUAL;

démo db <> violon

13
Lukasz Szozda
select COALESCE(TO_NUMBER(REGEXP_SUBSTR( field, '^(-|+)?\d+(\.|,)?(\d+)?$')), 0) from dual;

Il convertira 123 en 12, mais 123a ou 12a3 à .

10
sOliver

S'adapter à la question d'origine et à une école plutôt ancienne

select a, decode(trim(translate(b,'0123456789.',' ')),null,to_number(b),0)  from 
(
    select '1' a, 'not a number' b from dual
    union
    select '2' a, '1234' b from dual
)
7
stjohnroe

Il est probablement un peu compliqué de lancer votre propre expression rationnelle pour tester un nombre, mais le code ci-dessous pourrait fonctionner. Je pense que l'autre solution de Gabe impliquant une fonction définie par l'utilisateur est plus robuste car vous utilisez la fonctionnalité Oracle intégrée (et mon expression rationnelle n'est probablement pas correcte à 100%) mais cela pourrait valoir la peine:

with my_sample_data as (
  select '12345' as mynum from dual union all
  select '54-3' as mynum from dual union all
  select '123.4567' as mynum from dual union all
  select '.34567' as mynum from dual union all
  select '-0.3462' as mynum from dual union all
  select '0.34.62' as mynum from dual union all
  select '1243.64' as mynum from dual 
)
select 
  mynum, 
  case when regexp_like(mynum, '^-?\d+(\.\d+)?$') 
    then to_number(mynum) end as is_num
from my_sample_data

Cela donnera alors la sortie suivante:

MYNUM   IS_NUM
-------- ----------
12345   12345
54-3    
123.4567    123.4567
.34567  
-0.3462 -0.3462
0.34.62 
1243.64 1243.64
1
Mike Meyers
select DECODE(trim(TRANSLATE(replace(replace(A, ' '), ',', '.'), '0123456789.-', ' ')),
              null,
              DECODE(INSTR(replace(replace(A, ' '), ',', '.'), '.', INSTR(replace(replace(A, ' '), ',', '.'), '.') + 1),
                     0,
                     DECODE(INSTR(replace(replace(A, ' '), ',', '.'), '-', 2),
                            0,
                            TO_NUMBER(replace(replace(A, ' '), ',', '.'))))) A
  from (select '-1.1' A from DUAL union all select '-1-1' A from DUAL union all select ',1' A from DUAL union all select '1..1' A from DUAL) A;

Ce code exclut des chaînes telles que: -1-1, 1..1, 12-2 et ainsi de suite. Et je n'ai pas utilisé d'expressions régulières ici.

1
usbo

La meilleure méthode semble être la solution fonctionnelle, mais si vous ne disposez pas des privilèges nécessaires dans l'environnement que vous rencontrez (comme moi), vous pouvez essayer celle-ci:

SELECT
 CASE
  WHEN
     INSTR(TRANSLATE('123O0',
                     ' qwertyuıopğüasdfghjklşizxcvbnmöçQWERTYUIOPĞÜASDFGHJKLŞİZXCVBNMÖÇ~*\/(){}&%^#$<>;@€|:_=',
                     'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
                     ),
       'X') > 0
  THEN 'Y'
  ELSE 'N'
END is_nonnumeric
FROM DUAL

Soit dit en passant: dans mon cas, le problème était dû à "," et "." :) Alors prenez cela en considération. Inspiré de celui-ci . Aussi celui-ci semble plus concis.

Au fait 2: Cher Oracle, pouvez-vous s'il vous plaît créer des fonctions intégrées pour des besoins aussi petits mais précieux?

0
Gultekin

Une combinaison de solutions précédentes (de @sOliver et @Mike Meyers) et en essayant d'attraper autant de numéros que possible en supprimant le dernier "$" de REGEXP.

Il peut être utilisé pour filtrer le nombre réel à partir d'une table de configuration et avoir un "genre" de commentaire à côté du nombre "12 jours".

with my_sample_data as ( select '12345' as mynum from dual union all select '123.4567' as mynum from dual union all select '-0.3462' as mynum from dual union all select '.34567' as mynum from dual union all select '-.1234' as mynum from dual union all select '**' as mynum from dual union all select '0.34.62' as mynum from dual union all select '24Days' as mynum from dual union all select '42ab' as mynum from dual union all select '54-3' as mynum from dual ) SELECT mynum, COALESCE( TO_NUMBER( REGEXP_SUBSTR( mynum, '^(-|+)?\d*(.|,)?(\d+)?') ) , 0) is_num FROM my_sample_data;

donnerait


MYNUM    IS_NUM                                  
-------- ----------
12345    12345                                   
123.4567 123.4567                                
-0.3462  -0.3462                                 
.34567   0.34567                                 
-.1234   -0.1234                                 
**       0                                       
0.34.62  0.34                                    
24Days   24                                      
42ab     42                                      
54-3     54                                      
0
Victor H