web-dev-qa-db-fra.com

Comment convertir une chaîne en entier et avoir en cas d'erreur dans la distribution avec PostgreSQL?

Dans PostgreSQL, j'ai une table avec une colonne varchar. Les données sont supposées être des entiers et j'en ai besoin sous forme de type entier dans une requête. Certaines valeurs sont des chaînes vides . Ce qui suit:

SELECT myfield::integer FROM mytable

rendements ERROR: invalid input syntax for integer: ""

Comment interroger un casting et avoir 0 en cas d'erreur lors du casting dans postgres?

101
silviot

J'étais moi-même aux prises avec un problème similaire, mais je ne voulais pas des frais généraux excessifs. Je suis venu avec la requête suivante:

SELECT myfield::integer FROM mytable WHERE myfield ~ E'^\\d+$';

Postgres raccourcit ses conditions, vous ne devriez donc pas avoir de non-entiers frappant votre distribution :: entier. Il gère également les valeurs NULL (elles ne correspondront pas à l'expression rationnelle).

Si vous voulez des zéros au lieu de ne pas sélectionner, une instruction CASE devrait alors fonctionner:

SELECT CASE WHEN myfield~E'^\\d+$' THEN myfield::integer ELSE 0 END FROM mytable;
131
Anthony Briggs

Vous pouvez également créer votre propre fonction de conversion, à l'intérieur de laquelle vous pouvez utiliser des blocs d'exception:

CREATE OR REPLACE FUNCTION convert_to_integer(v_input text)
RETURNS INTEGER AS $$
DECLARE v_int_value INTEGER DEFAULT NULL;
BEGIN
    BEGIN
        v_int_value := v_input::INTEGER;
    EXCEPTION WHEN OTHERS THEN
        RAISE NOTICE 'Invalid integer value: "%".  Returning NULL.', v_input;
        RETURN NULL;
    END;
RETURN v_int_value;
END;
$$ LANGUAGE plpgsql;

Essai:

=# select convert_to_integer('1234');
 convert_to_integer 
--------------------
               1234
(1 row)

=# select convert_to_integer('');
NOTICE:  Invalid integer value: "".  Returning NULL.
 convert_to_integer 
--------------------

(1 row)

=# select convert_to_integer('chicken');
NOTICE:  Invalid integer value: "chicken".  Returning NULL.
 convert_to_integer 
--------------------

(1 row)
78
Matthew Wood

J'ai eu le même genre de besoin et j'ai trouvé que cela fonctionnait bien pour moi (postgres 8.4):

CAST((COALESCE(myfield,'0')) AS INTEGER)

Quelques cas de test à démontrer:

db=> select CAST((COALESCE(NULL,'0')) AS INTEGER);
 int4
------
    0
(1 row)

db=> select CAST((COALESCE('','0')) AS INTEGER);
 int4
------
    0
(1 row)

db=> select CAST((COALESCE('4','0')) AS INTEGER);
 int4
------
    4
(1 row)

db=> select CAST((COALESCE('bad','0')) AS INTEGER);
ERROR:  invalid input syntax for integer: "bad"

Si vous devez gérer la possibilité que le champ ait un texte non numérique (tel que "100bad"), vous pouvez utiliser regexp_replace pour supprimer les caractères non numériques avant la conversion.

CAST(REGEXP_REPLACE(COALESCE(myfield,'0'), '[^0-9]+', '', 'g') AS INTEGER)

Ensuite, les valeurs textuelles/varchar comme "b3ad5" donneront aussi des nombres

db=> select CAST(REGEXP_REPLACE(COALESCE('b3ad5','0'), '[^0-9]+', '', 'g') AS INTEGER);
 regexp_replace
----------------
             35
(1 row)

Pour répondre à l'inquiétude de Chris Cogdon concernant la solution ne donnant pas 0 dans tous les cas, y compris un cas tel que "mauvais" (aucun caractère numérique), j'ai déclaré ce qui suit corrigé

CAST((COALESCE(NULLIF(REGEXP_REPLACE(myfield, '[^0-9]+', '', 'g'), ''), '0')) AS INTEGER);

Cela fonctionne de la même manière que les solutions les plus simples, sauf que vous obtiendrez 0 lorsque la valeur à convertir est composée uniquement de caractères non numériques, tels que "mauvais":

db=> select CAST((COALESCE(NULLIF(REGEXP_REPLACE('no longer bad!', '[^0-9]+', '', 'g'), ''), '0')) AS INTEGER);
     coalesce
----------
        0
(1 row)
23
ghbarratt

Cela pourrait être un peu un bidouillage, mais il a fait le travail dans notre cas:

(0 || myfield)::integer

Explication (testée sur Postgres 8.4):

L'expression mentionnée ci-dessus génère NULL pour les valeurs NULL dans myfield et 0 pour les chaînes vides (ce comportement peut ou non correspondre à votre cas d'utilisation).

SELECT id, (0 || values)::integer from test_table ORDER BY id

Données de test:

CREATE TABLE test_table
(
  id integer NOT NULL,
  description character varying,
  "values" character varying,
  CONSTRAINT id PRIMARY KEY (id)
)

-- Insert Test Data
INSERT INTO test_table VALUES (1, 'null', NULL);
INSERT INTO test_table VALUES (2, 'empty string', '');
INSERT INTO test_table VALUES (3, 'one', '1');

La requête donnera le résultat suivant:

 ---------------------
 |1|null        |NULL|
 |2|empty string|0   |
 |3|one         |1   |
 ---------------------

Considérant que sélectionner uniquement values::integer entraînera un message d'erreur.

J'espère que cela t'aides.

18
Matt

@ La réponse de Matthew est bon. Mais cela peut être plus simple et plus rapide. Et la question demande de convertir les chaînes vides ('') en 0, mais pas avec les autres entrées "syntaxe d'entrée non valide" ou "hors limites":

CREATE OR REPLACE FUNCTION convert_to_int(text)
  RETURNS int AS
$func$
BEGIN
   IF $1 = '' THEN  -- special case for empty string like requested
      RETURN 0;
   ELSE
      RETURN $1::int;
   END IF;

EXCEPTION WHEN OTHERS THEN
   RETURN NULL;  -- NULL for other invalid input

END
$func$  LANGUAGE plpgsql IMMUTABLE;

Ceci retourne 0 pour une chaîne vide et NULL pour toute autre entrée non valide.
Il peut facilement être adapté pour toute conversion de type de données.

La saisie d'un bloc d'exception est considérablement plus chère. Si les chaînes vides sont communes _, il est judicieux d'intercepter ce cas avant de générer une exception.
Si les chaînes vides sont très rares, il est utile de déplacer le test vers la clause exception.

3
Erwin Brandstetter

SELECT CASE WHEN myfield="" THEN 0 ELSE myfield::integer END FROM mytable

Je n'ai jamais travaillé avec PostgreSQL mais j'ai vérifié la syntaxe correcte des instructions IF dans manual dans les requêtes SELECT.

3
Jan Hančič

J'ai trouvé le code suivant facile et fonctionnel. La réponse originale est ici https://www.postgresql.org/message-id/[email protected]

prova=> create table test(t text, i integer);
CREATE

prova=> insert into test values('123',123);
INSERT 64579 1

prova=> select cast(i as text),cast(t as int)from test;
text|int4
----+----
123| 123
(1 row)

j'espère que ça aide

1
Ashish Rana

J'ai aussi le même besoin mais cela fonctionne avec JPA 2.0 et Hibernate 5.0.2:

SELECT p FROM MatchProfile p WHERE CONCAT(p.id, '') = :keyword

Fonctionne à merveille. Je pense que ça marche avec LIKE aussi.

0
Hendy Irawan

Si les données sont supposées être des entiers et que vous n'avez besoin que de ces valeurs sous forme d'entiers, pourquoi ne parcourez-vous pas tout le chemin et convertissez-vous la colonne en une colonne entière?

Vous pouvez ensuite effectuer cette conversion des valeurs non conformes en zéros une seule fois, au point du système où les données sont insérées dans la table.

Avec la conversion ci-dessus, vous obligez Postgres à convertir ces valeurs encore et encore pour chaque ligne de chaque requête de cette table. Cela peut sérieusement dégrader les performances si vous effectuez de nombreuses requêtes sur cette colonne de cette table.

0
Bandi-T