Bagaimana cara saya melempar string ke integer dan memiliki 0 jika terjadi kesalahan dalam cetakan dengan PostgreSQL?

128

Di PostgreSQL saya memiliki tabel dengan kolom varchar. Data seharusnya berupa bilangan bulat dan saya membutuhkannya dalam tipe bilangan bulat dalam kueri. Beberapa nilai adalah string kosong. Pengikut:

SELECT myfield::integer FROM mytable

hasil panen ERROR: invalid input syntax for integer: ""

Bagaimana saya bisa meminta para pemain dan memiliki 0 jika terjadi kesalahan selama para pemain di postgres?

silviot
sumber

Jawaban:

161

Saya sendiri hanya bergulat dengan masalah yang sama, tetapi tidak ingin overhead fungsi. Saya datang dengan pertanyaan berikut:

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

Postgres memintas kondisinya, jadi Anda seharusnya tidak mendapatkan non-integer yang mengenai cast :: integer Anda. Itu juga menangani nilai NULL (mereka tidak akan cocok dengan regexp).

Jika Anda ingin nol bukannya tidak memilih, maka pernyataan KASUS akan berfungsi:

SELECT CASE WHEN myfield~E'^\\d+$' THEN myfield::integer ELSE 0 END FROM mytable;
Anthony Briggs
sumber
14
Saya sangat merekomendasikan untuk mengikuti saran Matthew. Solusi ini memiliki masalah dengan string yang terlihat seperti angka tetapi lebih besar dari nilai maksimum yang dapat Anda tempatkan dalam bilangan bulat.
pilif
4
komentar kedua saya pilif. bahwa nilai maks adalah bug yang menunggu untuk terjadi. titik tidak membuang kesalahan adalah untuk tidak membuang kesalahan saat data tidak valid. jawaban yang diterima ini TIDAK menyelesaikannya. terima kasih Matthew! kerja bagus!
Shawn Kovac
3
Sebesar jawaban Matthew, saya hanya perlu cara penanganan cepat dan kotor untuk memeriksa beberapa data. Saya juga mengakui bahwa pengetahuan saya sendiri saat ini kurang dalam mendefinisikan fungsi dalam SQL. Saya hanya tertarik pada angka antara 1 dan 5 digit, jadi saya mengubah regex menjadi E'\\d{1,5}$'.
Bobort
3
Ya, ya solusi ini relatif cepat dan kotor, tetapi dalam kasus saya, saya tahu data apa yang saya miliki dan bahwa tabelnya relatif pendek. Ini jauh lebih mudah daripada menulis (dan men-debug) seluruh fungsi. @ Batas Bobort di {1,5}atas pada digit mungkin adalah ide yang bagus jika Anda khawatir tentang melimpah, tetapi akan menutupi angka yang lebih besar, yang dapat menyebabkan masalah jika Anda mengonversi tabel. Secara pribadi saya lebih suka memiliki kesalahan permintaan di muka dan tahu bahwa beberapa "integer" saya edan (Anda juga dapat memilih dengan yang E'\\d{6,}$'pertama untuk memastikan).
Anthony Briggs
1
@Anthony Briggs: Ini tidak akan berfungsi jika myfield mengandung "'" atau "," atau ".", Atau' - '
Stefan Steiger
100

Anda juga dapat membuat fungsi konversi Anda sendiri, di dalamnya Anda dapat menggunakan blok pengecualian:

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;

Pengujian:

=# 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
sumber
8
sebagai lawan dari jawaban yang diterima, solusi ini di sini lebih tepat karena dapat dengan baik menangani angka yang terlalu besar untuk dimasukkan ke dalam bilangan bulat dan juga cenderung lebih cepat karena tidak ada validasi yang berfungsi dalam kasus umum (= string yang valid )
pilif
Bagaimana Anda memasukkan string ke integer pada bidang tertentu menggunakan fungsi Anda saat dalam INSERTpernyataan?
sk
27

Saya mempunyai kebutuhan yang sama dan merasa ini bekerja dengan baik untuk saya (postgres 8.4):

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

Beberapa kasus uji untuk menunjukkan:

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"

Jika Anda perlu menangani kemungkinan bidang memiliki teks non-numerik (seperti "100bad"), Anda dapat menggunakan regexp_replace untuk menghapus karakter non-numerik sebelum pemeran.

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

Kemudian nilai teks / varchar seperti "b3ad5" juga akan memberikan angka

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

Untuk mengatasi kekhawatiran Chris Cogdon dengan solusi tidak memberikan 0 untuk semua kasus, termasuk kasus seperti "buruk" (tidak ada karakter digit sama sekali), saya membuat pernyataan yang disesuaikan ini:

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

Ini berfungsi mirip dengan solusi yang lebih sederhana, kecuali akan memberikan 0 ketika nilai untuk dikonversi adalah karakter non-digit saja, seperti "buruk":

db=> select CAST((COALESCE(NULLIF(REGEXP_REPLACE('no longer bad!', '[^0-9]+', '', 'g'), ''), '0')) AS INTEGER);
     coalesce
----------
        0
(1 row)
ghbarratt
sumber
Mengapa Anda membutuhkan '0' || ? Dari dokumen: "Fungsi COALESCE mengembalikan argumen pertama yang bukan nol." Jadi jika Anda memiliki null sebagai nilainya, Coalesce akan menyingkirkannya.
Amala
@ Amala Benar. Tangkapan bagus. Diedit.
ghbarratt
1
Solusi hanya berfungsi jika inputnya bilangan bulat atau NULL. Pertanyaan diminta untuk mengonversi input apa pun, dan gunakan 0 jika tidak dapat dikonversi.
Chris Cogdon
@ ChrisCogdon Saya telah menambahkan solusi untuk mengatasi masalah Anda dengan tidak selalu memberikan nol jika nilai untuk dikonversi adalah "tidak dapat dikonversi." Versi tweak dari solusi ini akan mengembalikan 0 ketika sebuah string tanpa karakter digit diberikan sebagai nilai untuk dikonversi.
ghbarratt
22

Ini mungkin semacam peretasan, tapi itu menyelesaikan pekerjaan dalam kasus kami:

(0 || myfield)::integer

Penjelasan (Diuji pada Postgres 8.4):

Ekspresi yang disebutkan di atas menghasilkan NULLuntuk nilai-nilai NULL di myfielddan 0untuk string kosong (Perilaku tepat ini mungkin cocok atau tidak cocok dengan kasus penggunaan Anda).

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

Data uji:

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');

Kueri akan menghasilkan hasil berikut:

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

Sedangkan pilih saja values::integerakan menghasilkan pesan kesalahan.

Semoga ini membantu.

Mat
sumber
3

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

Saya belum pernah bekerja dengan PostgreSQL tapi saya memeriksa manual untuk sintaks yang benar dari pernyataan IF dalam permintaan SELECT.

Jan Hančič
sumber
Itu berfungsi untuk meja seperti sekarang. Saya agak takut bahwa di masa depan mungkin berisi nilai-nilai non-numerik. Saya lebih suka solusi try / catch-like, tetapi ini yang berhasil. Terima kasih.
silviot
Mungkin Anda bisa menggunakan ekspresi reguler postgresql.org/docs/8.4/interactive/functions-matching.html tetapi itu bisa mahal. Terima juga jawaban jika itu solusinya :)
Jan Hančič
3

@ Jawaban Matthew bagus. Tetapi bisa lebih sederhana dan lebih cepat. Dan pertanyaannya meminta untuk mengonversi string kosong ( '') menjadi 0, tetapi bukan input "sintaks input tidak valid" atau "di luar kisaran" lainnya:

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;

Ini mengembalikan 0string kosong dan NULLinput tidak valid lainnya.
Itu dapat dengan mudah diadaptasi untuk konversi tipe data apa pun .

Memasuki blok pengecualian jauh lebih mahal. Jika string kosong adalah umum , masuk akal untuk menangkap kasus itu sebelum mengajukan pengecualian.
Jika string kosong sangat jarang, ia membayar untuk memindahkan tes ke klausa pengecualian.

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

Fungsi ini akan selalu kembali 0jika tidak ada digit dalam string input.

SELECT parse_int('test12_3test');

akan kembali 123

Oleg Mikhailov
sumber
apakah Anda sudah melakukan pengujian kinerja untuk fungsi regex vs string? Juga, bagaimana ini menangani nulls? Apakah mengembalikan 0 atau NULL seperti yang diharapkan? Terima kasih!
vol7ron
1

Saya menemukan kode berikut ini mudah dan berfungsi. Jawaban asli ada di sini 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)

semoga membantu

Ashish Rana
sumber
1

SUBSTRING dapat membantu untuk beberapa kasus, Anda dapat membatasi ukuran int.

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

Jika data seharusnya berupa bilangan bulat, dan Anda hanya perlu nilai-nilai itu sebagai bilangan bulat, mengapa Anda tidak melakukan yang terbaik dan mengonversi kolom menjadi kolom bilangan bulat?

Kemudian Anda bisa melakukan konversi nilai ilegal ini menjadi nol hanya sekali, pada titik sistem di mana data dimasukkan ke dalam tabel.

Dengan konversi di atas, Anda memaksa Postgres untuk mengonversi nilai-nilai itu lagi dan lagi untuk setiap baris di setiap kueri untuk tabel itu - ini secara serius dapat menurunkan kinerja jika Anda melakukan banyak pertanyaan terhadap kolom ini dalam tabel ini.

Bandit
sumber
Pada prinsipnya Anda benar, tetapi dalam skenario khusus ini saya harus mengoptimalkan permintaan lambat tunggal dalam aplikasi. Saya tidak tahu bagaimana kode yang menangani pekerjaan input data. Saya tidak ingin menyentuhnya. Sejauh ini permintaan saya yang ditulis ulang berfungsi, tetapi saya ingin agar tidak terputus dalam kasus yang tidak terduga. Merancang ulang aplikasi bukanlah pilihan, bahkan jika itu tampaknya hal yang paling masuk akal.
silviot
0

Fungsi berikut tidak

  • gunakan nilai default ( error_result) untuk hasil yang tidak dapat dicasting misalnya abcatau999999999999999999999999999999999999999999
  • terus nullsebagainull
  • memangkas ruang dan spasi putih lainnya dalam input
  • nilai-nilai yang dicantumkan valid bigintsdibandingkan lower_bounddengan misalnya hanya menegakkan nilai-nilai positif
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
sumber
-1

Saya juga memiliki kebutuhan yang sama tetapi bekerja dengan JPA 2.0 dan Hibernate 5.0.2:

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

Keajaiban bekerja. Saya pikir itu bekerja dengan LIKE juga.

Hendy Irawan
sumber
-3

Ini juga harus melakukan pekerjaan tetapi ini di SQL dan tidak spesifik postgres.

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