¿Cómo convierto una cadena a entero y tengo 0 en caso de error en la conversión con PostgreSQL?

128

En PostgreSQL tengo una tabla con una columna varchar. Se supone que los datos son enteros y los necesito en tipo entero en una consulta. Algunos valores son cadenas vacías. El seguimiento:

SELECT myfield::integer FROM mytable

rendimientos ERROR: invalid input syntax for integer: ""

¿Cómo puedo consultar un elenco y tener 0 en caso de error durante el elenco en postgres?

silviot
fuente

Respuestas:

161

Estaba luchando con un problema similar, pero no quería la sobrecarga de una función. Se me ocurrió la siguiente consulta:

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

Postgres ataja sus condicionales, por lo que no deberías obtener ningún no entero que golpee tu :: número entero. También maneja valores NULL (no coincidirán con la expresión regular).

Si desea ceros en lugar de no seleccionar, entonces una declaración CASE debería funcionar:

SELECT CASE WHEN myfield~E'^\\d+$' THEN myfield::integer ELSE 0 END FROM mytable;
Anthony Briggs
fuente
14
Recomiendo encarecidamente ir con la sugerencia de Matthew. Esta solución tiene problemas con las cadenas que parecen números pero son más grandes que el valor máximo que puede colocar en un entero.
Pilif
44
Yo segundo comentario del piloto. ese valor máximo es un error que espera suceder. el punto de no arrojar un error es no arrojar un error cuando los datos no son válidos. Esta respuesta aceptada NO resuelve eso. gracias Matthew! ¡buen trabajo!
Shawn Kovac
3
Por grandiosa que sea la respuesta de Matthew, solo necesitaba una forma rápida y sucia de manejo para verificar algunos datos. También admito que en este momento falta mi propio conocimiento para definir funciones en SQL. Solo estaba interesado en números entre 1 y 5 dígitos, así que cambié la expresión regular a E'\\d{1,5}$'.
Bobort
3
Sí, sí, esta solución es relativamente rápida y sucia, pero en mi caso sabía qué datos tenía y que la tabla era relativamente corta. Es mucho más fácil que escribir (y depurar) una función completa. El {1,5}límite de @ Bobort arriba en los dígitos es posiblemente una buena idea si le preocupa el desbordamiento, pero enmascarará números más grandes, lo que podría causar problemas si está convirtiendo una tabla. Personalmente, prefiero tener el error de consulta por adelantado y saber que algunos de mis "enteros" están atornillados (también puede seleccionar con E'\\d{6,}$'primero para asegurarse).
Anthony Briggs
1
@Anthony Briggs: Esto no funcionará si myfield contiene "'" o "," o ".", O' - '
Stefan Steiger el
100

También puede crear su propia función de conversión, dentro de la cual puede usar bloques de excepción:

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;

Pruebas:

=# 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)
Matthew Wood
fuente
8
a diferencia de la respuesta aceptada, esta solución aquí es más correcta, ya que también puede manejar números demasiado grandes para caber en un número entero y también es probable que sea más rápido ya que no funciona en el caso común (= cadenas válidas )
Pilif
¿Cómo convertirías una cadena en un entero en campos específicos usando tu función mientras estás en una INSERTdeclaración?
sk
27

Tenía el mismo tipo de necesidad y encontré que esto funcionaba bien para mí (postgres 8.4):

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

Algunos casos de prueba para demostrar:

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 necesita manejar la posibilidad de que el campo tenga texto no numérico (como "100bad"), puede usar regexp_replace para quitar caracteres no numéricos antes de la conversión.

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

Luego, los valores de texto / varchar como "b3ad5" también darán números

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

Para abordar la preocupación de Chris Cogdon con la solución de no dar 0 para todos los casos, incluido un caso como "incorrecto" (sin caracteres de dígitos), hice esta declaración ajustada:

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

Funciona de manera similar a las soluciones más simples, excepto que dará 0 cuando el valor a convertir sea solo caracteres que no sean dígitos, como "incorrecto":

db=> select CAST((COALESCE(NULLIF(REGEXP_REPLACE('no longer bad!', '[^0-9]+', '', 'g'), ''), '0')) AS INTEGER);
     coalesce
----------
        0
(1 row)
ghbarratt
fuente
¿Por qué necesitas el '0' || ? De los documentos: "La función COALESCE devuelve el primero de sus argumentos que no es nulo". Entonces, si tiene un valor nulo, Coalesce lo eliminará.
Amala
@Amala True. Buena atrapada. Editado
ghbarratt
1
La solución solo funciona si la entrada es un número entero o NULL. La pregunta consistía en convertir cualquier tipo de entrada y usar 0 si no es convertible.
Chris Cogdon
@ChrisCogdon He agregado a la solución para abordar su preocupación con no siempre dar cero si el valor a convertir "no es convertible". Esta versión modificada de la solución devolverá 0 cuando una cadena sin caracteres de dígitos se da como valor para convertir.
ghbarratt
22

Esto podría ser algo así como un truco, pero hizo el trabajo en nuestro caso:

(0 || myfield)::integer

Explicación (Probado en Postgres 8.4):

La expresión mencionada anteriormente produce NULLvalores NULL en myfieldy 0para cadenas vacías (este comportamiento exacto puede o no ajustarse a su caso de uso).

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

Datos de prueba:

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 consulta arrojará el siguiente resultado:

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

Mientras que select solo values::integerdará como resultado un mensaje de error.

Espero que esto ayude.

Mate
fuente
3

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

Nunca he trabajado con PostgreSQL pero revisé el manual para ver la sintaxis correcta de las declaraciones IF en las consultas SELECT.

Jan Hančič
fuente
Eso funciona para la mesa tal como está ahora. Tengo un poco de miedo de que en el futuro pueda contener valores no numéricos. Hubiera preferido una solución de prueba / captura, pero esto funciona. Gracias.
silviot
Tal vez podría usar expresiones regulares postgresql.org/docs/8.4/interactive/functions-matching.html pero eso podría ser costoso. También acepte la respuesta si es la solución :)
Jan Hančič
3

La respuesta de @ Matthew es buena. Pero puede ser más simple y más rápido. Y la pregunta pide convertir cadenas vacías ( '') a 0, pero no a otra "sintaxis de entrada no válida" o entrada "fuera de rango":

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;

Esto devuelve 0para una cadena vacía y NULLpara cualquier otra entrada no válida.
Se puede adaptar fácilmente para cualquier conversión de tipo de datos .

Entrar en un bloque de excepción es sustancialmente más costoso. Si las cadenas vacías son comunes , tiene sentido detectar ese caso antes de generar una excepción.
Si las cadenas vacías son muy raras, vale la pena mover la prueba a la cláusula de excepción.

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

Esta función siempre regresará 0si no hay dígitos en la cadena de entrada.

SELECT parse_int('test12_3test');

volverá 123

Oleg Mikhailov
fuente
¿Ha realizado alguna prueba de rendimiento para la función regex vs cadena? Además, ¿cómo maneja esto los nulos? ¿Devuelve 0 o NULL como se esperaba? ¡Gracias!
vol7ron
1

Encontré el siguiente código fácil y funcional. La respuesta original está aquí 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)

Espero eso ayude

Ashish Rana
fuente
1

SUBSTRING puede ayudar en algunos casos, puede limitar el tamaño de la int.

SELECT CAST(SUBSTRING('X12312333333333', '([\d]{1,9})') AS integer);
obsoleto
fuente
0

Si se supone que los datos son enteros, y solo necesita esos valores como enteros, ¿por qué no hace todo lo posible y convierte la columna en una columna entera?

Entonces podría hacer esta conversión de valores ilegales a ceros solo una vez, en el punto del sistema donde los datos se insertan en la tabla.

Con la conversión anterior, está obligando a Postgres a convertir esos valores una y otra vez para cada fila individual en cada consulta para esa tabla; esto puede degradar seriamente el rendimiento si realiza muchas consultas en esta columna de esta tabla.

Bandido
fuente
En principio tiene razón, pero en este escenario particular tengo que optimizar una única consulta lenta en una aplicación. No sé cómo funciona el código que maneja la entrada de datos. No quiero tocarlo. Hasta ahora, mi consulta reescrita funciona, pero me gustaría que no se rompa en casos imprevistos. Volver a diseñar la aplicación no es una opción, incluso si parece lo más sensato.
silviot
0

La siguiente función hace

  • use un valor predeterminado ( error_result) para resultados no convertibles, p. ej.abc o999999999999999999999999999999999999999999
  • mantiene null comonull
  • recorta espacios y otros espacios en blanco en la entrada
  • los valores emitidos como válidos bigintsse comparan lower_boundpara, por ejemplo, imponer valores positivos únicamente
CREATE OR REPLACE FUNCTION cast_to_bigint(text) 
RETURNS BIGINT AS $$
DECLARE big_int_value BIGINT DEFAULT NULL;
DECLARE error_result  BIGINT DEFAULT -1;
DECLARE lower_bound   BIGINT DEFAULT 0;
BEGIN
    BEGIN
        big_int_value := CASE WHEN $1 IS NOT NULL THEN GREATEST(TRIM($1)::BIGINT, lower_bound) END;
    EXCEPTION WHEN OTHERS THEN
        big_int_value := error_result;
    END;
RETURN big_int_value;
END;
Th 00 mÄ s
fuente
-1

También tengo la misma necesidad, pero eso funciona con JPA 2.0 e Hibernate 5.0.2:

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

Funciona de maravillas. Creo que también funciona con LIKE.

Hendy Irawan
fuente
-3

Esto también debería hacer el trabajo, pero esto es a través de SQL y no específico de postgres.

select avg(cast(mynumber as numeric)) from my table
ronak
fuente