Come faccio a trasmettere una stringa in numero intero e ho 0 in caso di errore nel cast con PostgreSQL?


128

In PostgreSQL ho una tabella con una colonna varchar. I dati dovrebbero essere numeri interi e ne ho bisogno in un numero intero in una query. Alcuni valori sono stringhe vuote. Il seguente:

SELECT myfield::integer FROM mytable

i rendimenti ERROR: invalid input syntax for integer: ""

Come posso eseguire una query su un cast e avere 0 in caso di errore durante il cast in postgres?

Risposte:


161

Stavo solo lottando con un problema simile, ma non volevo il sovraccarico di una funzione. Ho trovato la seguente domanda:

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

Postgres abbrevia i suoi condizionali, quindi non dovresti avere nessun non intero che colpisce il tuo cast :: integer. Gestisce anche i valori NULL (non corrisponderanno al regexp).

Se vuoi zeri invece di non selezionarli, allora un'istruzione CASE dovrebbe funzionare:

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

14
Consiglio vivamente di seguire il suggerimento di Matthew. Questa soluzione presenta problemi con stringhe che sembrano numeri ma sono maggiori del valore massimo che è possibile inserire in un numero intero.
pilif

4
secondo commento di Pilif. quel valore massimo è un bug in attesa di verificarsi. il punto di non generare un errore è di non lanciare un errore quando i dati non sono validi. questa risposta accettata NON lo risolve. grazie Matteo! ottimo lavoro!
Shawn Kovac,

3
Per quanto grande sia la risposta di Matthew, avevo solo bisogno di un modo rapido e sporco per gestire alcuni dati. Ammetto anche che in questo momento manca la mia conoscenza nel definire le funzioni in SQL. Mi interessavano solo numeri tra 1 e 5 cifre, quindi ho cambiato la regex in E'\\d{1,5}$'.
Bobort,

3
Sì, sì, questa soluzione è relativamente veloce e sporca, ma nel mio caso sapevo quali dati avevo e che la tabella era relativamente breve. È molto più semplice che scrivere (e debug) un'intera funzione. Il {1,5}limite di @ Bobort sopra sulle cifre è probabilmente una buona idea se sei preoccupato per l'overflow, ma maschererà numeri più grandi, che potrebbero causare problemi se stai convertendo una tabella. Personalmente preferirei avere l'errore di query in primo piano e sapere che alcuni dei miei "numeri interi" sono sbagliati (puoi anche selezionare con il E'\\d{6,}$'primo per essere sicuro).
Anthony Briggs,

1
@Anthony Briggs: Questo non funzionerà se myfield contiene "'" o "," o ".", O' - '
Stefan Steiger,

100

È inoltre possibile creare la propria funzione di conversione, all'interno della quale è possibile utilizzare blocchi di eccezioni:

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)

8
al contrario della risposta accettata, questa soluzione qui è più corretta in quanto può anche gestire numeri troppo grandi per adattarsi a un numero intero ed è anche probabile che sia più veloce in quanto non funziona nel caso comune (= stringhe valide )
pilif

Come lanceresti la stringa in numero intero su campi specifici usando la tua funzione mentre sei in INSERTistruzione?
sk

27

Avevo lo stesso tipo di necessità e ho trovato che funzionava bene per me (Postgres 8.4):

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

Alcuni casi di test per dimostrare:

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"

Se è necessario gestire la possibilità che il campo contenga testo non numerico (come "100bad"), è possibile utilizzare regexp_replace per rimuovere i caratteri non numerici prima del cast.

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

Quindi anche valori text / varchar come "b3ad5" forniranno numeri

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

Per rispondere alla preoccupazione di Chris Cogdon con la soluzione che non fornisce 0 per tutti i casi, incluso un caso come "cattivo" (nessun carattere di cifre), ho fatto questa dichiarazione modificata:

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

Funziona in modo simile alle soluzioni più semplici, tranne che fornirà 0 quando il valore da convertire è solo caratteri non numerici, come "cattivo":

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

Perché è necessario lo "0" || ? Dai documenti: "La funzione COALESCE restituisce il primo dei suoi argomenti che non è null." Quindi se hai un valore nullo, Coalesce si sbarazzerà di esso.
Amala,

@Amala True. Bella presa. Modificato.
ghbarratt,

1
La soluzione funziona solo se l'input è un numero intero o NULL. La domanda chiedeva di convertire qualsiasi tipo di input e usare 0 se non è convertibile.
Chris Cogdon,

@ChrisCogdon Ho aggiunto la soluzione per rispondere alla tua preoccupazione di non dare sempre zero se il valore da convertire "non è convertibile". Questa versione ottimizzata della soluzione restituirà 0 quando viene data una stringa senza caratteri numerici come valore da convertire.
ghbarratt,

22

Questo potrebbe essere un po 'un trucco, ma ha fatto il lavoro nel nostro caso:

(0 || myfield)::integer

Spiegazione (Testato su Postgres 8.4):

L'espressione sopra menzionata produce NULLvalori NULL in myfielde 0per stringhe vuote (questo comportamento esatto può o meno adattarsi al caso d'uso).

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

Dati di 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 query produrrà il seguente risultato:

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

Considerando che solo selezionare values::integergenererà un messaggio di errore.

Spero che questo ti aiuti.


3

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

Non ho mai lavorato con PostgreSQL ma ho controllato il manuale per la sintassi corretta delle istruzioni IF nelle query SELECT.


Funziona per il tavolo come è adesso. Ho un po 'paura che in futuro possa contenere valori non numerici. Avrei preferito una soluzione simile a try / catch, ma questo è il trucco. Grazie.
Silviot,

Forse potresti usare le espressioni regolari postgresql.org/docs/8.4/interactive/functions-matching.html ma potrebbe essere costoso. Accetta anche la risposta se è la soluzione :)
Jan Hančič,

3

@La risposta di Matthew è buona. Ma può essere più semplice e veloce. E la domanda chiede di convertire stringhe vuote ( '') in 0, ma non altre "sintassi di input non valide" o input "fuori intervallo":

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;

Questo ritorna 0per una stringa vuota e NULLper qualsiasi altro input non valido.
Può essere facilmente adattato per qualsiasi conversione di tipo di dati .

L'immissione di un blocco di eccezioni è sostanzialmente più costosa. Se le stringhe vuote sono comuni , ha senso catturare quel caso prima di sollevare un'eccezione.
Se le stringhe vuote sono molto rare, vale la pena spostare il test nella clausola di eccezione.


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

Questa funzione tornerà sempre 0se non ci sono cifre nella stringa di input.

SELECT parse_int('test12_3test');

sarà di ritorno 123


hai eseguito dei test delle prestazioni per la funzione regex vs stringa? Inoltre, come gestisce i null? Restituirà 0 o NULL come previsto? Grazie!
vol7ron,


1

SUBSTRING può aiutare in alcuni casi, è possibile limitare la dimensione dell'int.

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

0

Se i dati dovrebbero essere numeri interi e hai bisogno solo di quei valori come numeri interi, perché non percorri l'intero miglio e converti la colonna in una colonna intera?

Quindi potresti fare questa conversione di valori illegali in zero solo una volta, nel punto del sistema in cui i dati sono inseriti nella tabella.

Con la conversione di cui sopra stai forzando Postgres a convertire quei valori ancora e ancora per ogni singola riga in ogni query per quella tabella - questo può compromettere seriamente le prestazioni se fai molte query contro questa colonna in questa tabella.


In linea di principio hai ragione, ma in questo particolare scenario devo ottimizzare una singola query lenta in un'applicazione. Non so come funzioni il codice che gestisce l'inserimento dei dati. Non voglio toccarlo. Finora la mia query riscritta funziona, ma vorrei che non si rompesse in casi imprevisti. La riprogettazione dell'applicazione non è un'opzione, anche se sembra la cosa più sensata.
Silviot,

0

La seguente funzione fa

  • utilizzare un valore predefinito ( error_result) per risultati non calcolabili, ad esempio abco999999999999999999999999999999999999999999
  • continua nullcomenull
  • taglia gli spazi e altri spazi bianchi in input
  • i valori espressi come validi bigintsvengono confrontati con, lower_boundad esempio, impongono solo valori positivi
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;

-1

Ho anche lo stesso bisogno, ma funziona con JPA 2.0 e Hibernate 5.0.2:

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

Funziona a meraviglia. Penso che funzioni anche con LIKE.


-3

Questo dovrebbe fare anche il lavoro, ma questo è su SQL e non specifico per Postgres.

select avg(cast(mynumber as numeric)) from my table
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.