2010-01-17 2 views
88

Dans PostgreSQL j'ai une table avec une colonne varchar. Les données sont supposées être des entiers et j'en ai besoin en type entier dans une requête. Certaines valeurs sont des chaînes vides. qui suit:Comment convertir une chaîne en nombre entier et avoir 0 en cas d'erreur dans la distribution avec PostgreSQL?

SELECT myfield::integer FROM mytable 

cède ERROR: invalid input syntax for integer: ""

Comment puis-je interroger un casting et ont 0 en cas d'erreur lors de la distribution dans Postgres?

Répondre

107

Je ne faisais que la lutte avec un problème similaire moi-même, mais Je ne voulais pas les frais généraux d'une fonction. Je suis venu avec la requête suivante:

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

Postgres raccourcis ses conditionals, vous ne devriez pas obtenir des non-entiers :: frapper votre cast entier. Il gère également les valeurs NULL (elles ne correspondront pas à l'expression rationnelle).

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

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

Je recommande fortement d'aller avec la suggestion de Matthew. Cette solution a des problèmes avec les chaînes qui ressemblent à des nombres mais qui sont plus grandes que la valeur maximale que vous pouvez placer dans un nombre entier. – pilif

+2

je commente le deuxième pilif. cette valeur maximale est un bug qui attend de se produire. le point de ne pas lancer une erreur est de ne pas lancer une erreur lorsque les données sont invalides. cette réponse acceptée ne résout PAS cela. merci Matthew! bon travail! –

+3

Aussi bien que la réponse de Matthew est, j'avais juste besoin d'un moyen de manipulation rapide et sale pour vérifier certaines données. J'avoue également que mes propres connaissances manquent actuellement dans la définition des fonctions en SQL. Je ne m'intéressais qu'aux nombres compris entre 1 et 5 chiffres, donc j'ai changé l'expression rationnelle en 'E '\\ d {1,5} $''. – Bobort

3

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

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

+0

Cela fonctionne pour la table telle qu'elle est maintenant. J'ai un peu peur qu'à l'avenir cela puisse contenir des valeurs non numériques. J'aurais préféré une solution try/catch-like, mais cela fait l'affaire. Merci. – silviot

+0

Peut-être que vous pourriez utiliser des expressions régulières http://www.postgresql.org/docs/8.4/interactive/functions-matching.html mais cela pourrait être coûteux. Acceptez aussi la réponse si c'est la solution :) –

+1

+1 pour se soucier assez de rechercher la syntaxe exacte – Earlz

0

Si les données sont supposées être des entiers, et que vous avez seulement besoin de ces valeurs comme entiers, pourquoi ne faites-vous pas tout le mile et convertissez la colonne en une colonne entière?

Ensuite, vous pourriez faire cette conversion de valeurs illégales 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 forcez 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 faites beaucoup de requêtes sur cette colonne dans cette table.

+0

En principe, vous avez raison, mais dans ce scénario particulier, je dois optimiser une seule requête lente dans une application. Je ne sais pas comment fonctionne le code qui gère la saisie de données. Je ne veux pas le toucher. Jusqu'à présent, ma requête réécrite fonctionne, mais je voudrais qu'elle ne casse pas dans des cas imprévus. Ré-architecturer l'application n'est pas une option, même si cela semble la chose la plus sensée. – silviot

69

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

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; 

Test:

=# 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) 
+2

par opposition à la réponse acceptée, cette solution est ici plus correcte car elle peut aussi bien traiter des nombres trop grands pour tenir dans un nombre entier et elle est également susceptible d'être plus rapide car elle ne fonctionne pas dans le cas courant (= valide cordes) – pilif

-3

Cela devrait également faire le travail, mais cela est dans SQL et non Postgres spécifiques.

select avg(cast(mynumber as numeric)) from my table 
15

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

(0 || myfield)::integer 

Explication (Testé sur Postgres 8.4):

mentionnées ci-dessus expression donne NULL pour les valeurs NULL dans myfield et 0 pour les chaînes vides (ce comportement exact peut ne pas 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 donne le résultat suivant:

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

Alors sélectionnez uniquement values::integer se traduira par un message d'erreur.

Espérons que cela aide.

+0

Pourriez-vous montrer des exemples? N'a pas travaillé pour moi. –

+0

J'ai mis à jour l'exemple. J'espère que cela aidera à éclaircir les choses. – Matt

21

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

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

Certains cas de test pour 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 avez besoin pour gérer la Si le champ contient un texte non numérique (par exemple "100bad"), vous pouvez utiliser regexp_replace pour supprimer les caractères non numériques avant le cast.

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

valeurs Puis text/varchar comme « b3ad5 » aussi donner des chiffres

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

Pour répondre aux préoccupations de Chris Cogdon avec la solution ne donnant pas 0 pour tous les cas, y compris un cas comme « mauvais » (pas de caractères à deux chiffres du tout), je fait cette déclaration ajustée:

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

Il fonctionne de manière similaire aux solutions plus simples, à l'exception donnera 0 lorsque la valeur est de convertir les caractères non-chiffres seulement, su ch comme « mauvais »:

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

Pourquoi avez-vous besoin du '0' || ? De la docs: "La fonction COALESCE retourne le premier de ses arguments qui n'est pas nul." Donc, si vous avez null comme valeur, Coalesce s'en débarrassera. – Amala

+0

@Amala Vrai. Belle prise. Édité. – ghbarratt

+0

La solution fonctionne uniquement si l'entrée est un entier ou NULL. La question demandait de convertir n'importe quel type d'entrée, et utilise 0 si elle n'est pas convertible. –

1
CREATE OR REPLACE FUNCTION parse_int(s TEXT) RETURNS INT AS $$ 
BEGIN 
    RETURN regexp_replace(('0' || s), '[^\d]', '', 'g')::INT; 
END; 
$$ LANGUAGE plpgsql; 

Cette fonction retournera toujours 0 s'il n'y a pas de chiffres dans la chaîne d'entrée.

SELECT parse_int('test12_3test');

retournera 123

1

Je trouve le code suivant facile et de travail. 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) 

elle pourra être utile

3

@Matthew's answer est bon. Mais cela peut être plus simple et plus rapide. Et la question demande de convertir des chaînes vides ('') à 0, mais pas d'autres « syntaxe d'entrée non valide » ou « hors de portée » entrée:

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; 

Ce retourne 0 pour une chaîne vide et NULL pour tout autre invalide contribution.
Il peut facilement être adapté pour toute conversion de type de données.

La saisie d'un bloc d'exception est sensiblement plus coûteuse. Si les chaînes vides sont commun il est logique d'attraper ce cas avant de déclencher une exception.
Si les chaînes vides sont très rares, il est rentable de déplacer le test vers la clause d'exception.

0

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

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

merveilles Works. Je pense que cela fonctionne avec LIKE aussi.

Questions connexes