web-dev-qa-db-fra.com

isnumeric () avec PostgreSQL

J'ai besoin de déterminer si une chaîne donnée peut être interprétée comme un nombre (entier ou virgule flottante) dans une instruction SQL. Comme ci-dessous:

SELECT AVG(CASE WHEN x ~ '^[0-9]*.?[0-9]*$' THEN x::float ELSE NULL END) FROM test

J'ai trouvé que Postgres ' pattern matching pouvait être utilisé pour cela. Et j'ai donc adapté la déclaration donnée dans cet endroit pour incorporer des nombres à virgule flottante. Voici mon code:

WITH test(x) AS (
    VALUES (''), ('.'), ('.0'), ('0.'), ('0'), ('1'), ('123'),
    ('123.456'), ('abc'), ('1..2'), ('1.2.3.4'))

SELECT x
     , x ~ '^[0-9]*.?[0-9]*$' AS isnumeric
FROM test;

Le résultat:

    x    | isnumeric 
---------+-----------
         | t
 .       | t
 .0      | t
 0.      | t
 0       | t
 1       | t
 123     | t
 123.456 | t
 abc     | f
 1..2    | f
 1.2.3.4 | f
(11 rows)

Comme vous pouvez le voir, les deux premiers éléments (la chaîne vide '' et la seule période '.') sont mal classés comme étant de type numérique (ce qu'ils ne sont pas). Je ne peux pas me rapprocher de cela pour le moment. Toute aide appréciée!


Mise à jour Sur la base de cette réponse (et ses commentaires), j'ai adapté le modèle pour:

WITH test(x) AS (
    VALUES (''), ('.'), ('.0'), ('0.'), ('0'), ('1'), ('123'),
    ('123.456'), ('abc'), ('1..2'), ('1.2.3.4'), ('1x234'), ('1.234e-5'))

SELECT x
     , x ~ '^([0-9]+[.]?[0-9]*|[.][0-9]+)$' AS isnumeric
FROM test;

Qui donne:

     x    | isnumeric 
----------+-----------
          | f
 .        | f
 .0       | t
 0.       | t
 0        | t
 1        | t
 123      | t
 123.456  | t
 abc      | f
 1..2     | f
 1.2.3.4  | f
 1x234    | f
 1.234e-5 | f
(13 rows)

Il y a encore quelques problèmes avec la notation scientifique et avec les nombres négatifs, comme je le vois maintenant.

32
moooeeeep

Comme vous pouvez le remarquer, la méthode basée sur les expressions rationnelles est presque impossible à faire correctement. Par exemple, votre test indique que 1.234e-5 N'est pas un nombre valide, alors qu'il l'est vraiment. De plus, vous avez manqué des nombres négatifs. Que faire si quelque chose ressemble à un nombre, mais lorsque vous essayez de le stocker, cela provoquera un débordement?

Au lieu de cela, je recommanderais de créer une fonction qui essaie réellement de convertir en NUMERIC (ou FLOAT si votre tâche l'exige) et retourne TRUE ou FALSE selon sur le succès ou l'échec de ce casting.

Ce code simulera entièrement la fonction ISNUMERIC():

CREATE OR REPLACE FUNCTION isnumeric(text) RETURNS BOOLEAN AS $$
DECLARE x NUMERIC;
BEGIN
    x = $1::NUMERIC;
    RETURN TRUE;
EXCEPTION WHEN others THEN
    RETURN FALSE;
END;
$$
STRICT
LANGUAGE plpgsql IMMUTABLE;

L'appel de cette fonction sur vos données donne les résultats suivants:

WITH test(x) AS ( VALUES (''), ('.'), ('.0'), ('0.'), ('0'), ('1'), ('123'),
  ('123.456'), ('abc'), ('1..2'), ('1.2.3.4'), ('1x234'), ('1.234e-5'))
SELECT x, isnumeric(x) FROM test;

    x     | isnumeric
----------+-----------
          | f
 .        | f
 .0       | t
 0.       | t
 0        | t
 1        | t
 123      | t
 123.456  | t
 abc      | f
 1..2     | f
 1.2.3.4  | f
 1x234    | f
 1.234e-5 | t
 (13 rows)

Non seulement il est plus correct et plus facile à lire, mais il fonctionnera également plus rapidement si les données étaient en fait un nombre.

70
mvp

Votre problème est les deux éléments 0 ou plus [0-9] de chaque côté du séparateur décimal. Vous devez utiliser une logique OR | dans la ligne d'identification du numéro:

~'^([0-9]+\.?[0-9]*|\.[0-9]+)$'

Cela exclura un point décimal seul comme un nombre valide.

11
Mr Rho