PostgreSQL ile dökümde hata olması durumunda bir dizeyi tam sayıya nasıl çevirebilirim ve 0 elde edebilirim?


128

PostgreSQL'de varchar sütunu olan bir tablom var. Verilerin tam sayı olması gerekiyor ve bir sorguda tamsayı türünde ona ihtiyacım var. Bazı değerler boş dizelerdir. Devamındaki:

SELECT myfield::integer FROM mytable

verim ERROR: invalid input syntax for integer: ""

Postgres'te yayın sırasında bir oyuncu kadrosunu nasıl sorgulayabilirim ve hata durumunda 0 alabilir miyim?

Yanıtlar:


161

Ben de sadece benzer bir problemle boğuşuyordum ama bir fonksiyonun ek yükünü istemiyordum. Aşağıdaki sorgu ile geldim:

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

Postgres koşullarını kısayollar, bu nedenle tamsayı çevirinize çarpan tamsayı olmayan herhangi bir sayı almamalısınız. Ayrıca NULL değerleri de işler (normal ifadeyle eşleşmezler).

Seçmemek yerine sıfır istiyorsanız, bir CASE ifadesi çalışmalıdır:

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

14
Matthew'un önerisiyle gitmenizi şiddetle tavsiye ederim. Bu çözüm, sayı gibi görünen ancak bir tam sayıya yerleştirebileceğiniz maksimum değerden daha büyük olan dizelerle ilgili sorunlara sahiptir.
pilif

4
pilifin yorumunu ikinci kez yapıyorum. bu maksimum değer, gerçekleşmeyi bekleyen bir hatadır. bir hata atmamanın amacı, veriler geçersiz olduğunda bir hata atmamaktır. bu kabul edilen cevap bunu ÇÖZMEZ. teşekkürler Matthew! harika iş!
Shawn Kovac

3
Matthew'un cevabı kadar harika olsa da, bazı verileri kontrol etmek için hızlı ve kirli bir işleme ihtiyacım vardı. Ayrıca şu anda SQL'de fonksiyonları tanımlamada kendi bilgimin eksik olduğunu kabul ediyorum. Yalnızca 1 ile 5 basamak arasındaki sayılarla ilgilendiğim için normal ifadeyi olarak değiştirdim E'\\d{1,5}$'.
Bobort

3
Evet, evet bu çözüm nispeten hızlı ve kirli, ancak benim durumumda hangi verilere sahip olduğumu ve tablonun nispeten kısa olduğunu biliyordum. Tüm bir işlevi yazmaktan (ve hata ayıklamaktan) çok daha kolaydır. @ Bobort'un {1,5}rakamlar üzerindeki sınırı, taşma konusunda endişeleriniz varsa muhtemelen iyi bir fikirdir, ancak daha büyük sayıları maskeleyecektir, bu da bir tabloyu dönüştürüyorsanız sorun yaratabilir. Şahsen, sorgu hatasını önceden görmeyi ve bazı "tam sayılarımın" acayip olduğunu bilmeyi tercih ederim ( E'\\d{6,}$'Emin olmak için ilk ile de seçim yapabilirsiniz).
Anthony Briggs

1
@Anthony Briggs: Alanım "" "veya", "veya". "Veya '-'
Stefan Steiger,

100

Ayrıca hangi içeride kendi dönüşüm fonksiyonunu oluşturabilir olabilir istisna blokları kullanı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;

Test yapmak:

=# 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
kabul edilen cevabın aksine, buradaki bu çözüm daha doğrudur çünkü bir tam sayıya sığamayacak kadar büyük sayılarla eşit derecede iyi başa çıkabilir ve aynı zamanda ortak durumda hiçbir doğrulama çalışmadığı için daha hızlı olması muhtemeldir (= geçerli dizeler )
pilif

Nasıl işlevini kullanarak belirli alanlarda tamsayı dizesi dökme olur iken in INSERTdeyimi?
sk

27

Aynı ihtiyacım vardı ve bunun benim için işe yaradığını buldum (postgres 8.4):

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

Gösterilecek bazı test senaryoları:

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"

Alanın sayısal olmayan metne ("100bad" gibi) sahip olma olasılığını halletmeniz gerekiyorsa, çevrimden önce sayısal olmayan karakterleri çıkarmak için regexp_replace'i kullanabilirsiniz.

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

Sonra "b3ad5" gibi metin / varchar değerleri de sayılar verecektir

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

Chris Cogdon'ın, "kötü" gibi bir durum da dahil olmak üzere tüm durumlar için 0 vermemesi konusundaki endişesini gidermek için (hiç rakam karakteri yok), bu düzeltilmiş ifadeyi yaptım:

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

Daha basit çözümlere benzer şekilde çalışır, ancak dönüştürülecek değer yalnızca "kötü" gibi rakam olmayan karakterler olduğunda 0 verir:

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

Neden '0'a ihtiyacınız var || ? Dokümanlardan: "KÖMÜRSÜZ işlevi boş olmayan ilk bağımsız değişkenini döndürür." Dolayısıyla, değeriniz olarak sıfır varsa, Coalesce bundan kurtulacaktır.
Amala

@Amala Doğru. İyi yakaladın. Düzenlenen.
ghbarratt

1
Çözüm yalnızca giriş bir tamsayı veya NULL ise çalışır. Soru, herhangi bir tür girdiyi dönüştürmek ve dönüştürülemezse 0'ı kullanmaktı.
Chris Cogdon

@ChrisCogdon Dönüştürülecek değer "dönüştürülemez" ise her zaman sıfır vermeyerek endişenizi gidermek için çözüme ekledim. Çözümün bu ince ayarlanmış sürümü, dönüştürülecek değer olarak rakam karakterleri olmayan bir dize verildiğinde 0 döndürür.
ghbarratt

22

Bu biraz hack olabilir ama bizim durumumuzda işi bitirdi:

(0 || myfield)::integer

Açıklama (Postgres 8.4'te test edilmiştir):

Yukarıda belirtilen ifade , boş dizelerdeki ve boş dizelerdeki NULLNULL değerleri için verir (Bu tam davranış, kullanım durumunuza uyabilir veya uymayabilir).myfield0

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

Test verisi:

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

Sorgu aşağıdaki sonucu verecektir:

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

Oysa seçim yalnızca values::integerbir hata mesajıyla sonuçlanacaktır.

Bu yardımcı olur umarım.


3

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

PostgreSQL ile hiç çalışmadım ama SELECT sorgularında IF ifadelerinin doğru sözdizimi için kılavuza baktım .


Bu, şimdi olduğu gibi masa için çalışıyor. Gelecekte sayısal olmayan değerler içerebileceğinden biraz korkuyorum. Deneme / yakalama benzeri bir çözümü tercih ederdim, ancak bu hile yapıyor. Teşekkürler.
silviot

Postgresql.org/docs/8.4/interactive/functions-matching.html normal ifadeler kullanabilirsiniz, ancak bu maliyetli olabilir. Çözüm buysa yanıtı da kabul edin :)
Jan Hančič

3

@ Matthew'un cevabı iyidir. Ancak daha basit ve daha hızlı olabilir. Ve soru, boş dizeleri ( '') dönüştürmeyi ister 0, ancak diğer "geçersiz girdi sözdizimi" veya "aralık dışı" girdiye dönüştürmeyi istemez:

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;

Bu 0, boş bir dizge ve NULLdiğer geçersiz girişler için döner . Herhangi bir veri türü dönüşümü
için kolayca uyarlanabilir .

Bir istisna bloğuna girmek önemli ölçüde daha pahalıdır. Boş dizeler ise ortak bu durum istisna önce çantayı yakalamak için mantıklı.
Boş dizeler çok nadir ise, testi istisna maddesine taşımak öder.


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

0Giriş dizesinde hiç rakam yoksa bu işlev her zaman geri döner .

SELECT parse_int('test12_3test');

dönecek 123


regex ve string işlevi için herhangi bir performans testi gerçekleştirdiniz mi? Ayrıca, bu boş değerleri nasıl ele alır? Beklendiği gibi 0 veya NULL döndürür mü? Teşekkürler!
vol7ron


1

SUBSTRING bazı durumlarda yardımcı olabilir, int boyutunu sınırlayabilirsiniz.

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

0

Verilerin tamsayı olması gerekiyorsa ve bu değerlere yalnızca tamsayı olarak ihtiyacınız varsa, neden tüm mili gidip sütunu bir tam sayı sütununa dönüştürmüyorsunuz?

Daha sonra, verilerin tabloya eklendiği sistem noktasında, geçersiz değerlerin sıfıra dönüştürülmesini yalnızca bir kez yapabilirsiniz.

Yukarıdaki dönüşümle, Postgres'i bu tablo için her sorgudaki her bir satır için bu değerleri tekrar tekrar dönüştürmeye zorluyorsunuz - bu tablodaki bu sütuna karşı çok sayıda sorgu yaparsanız bu, performansı ciddi şekilde düşürebilir.


Prensipte haklısınız, ancak bu özel senaryoda bir uygulamadaki tek bir yavaş sorguyu optimize etmem gerekiyor. Veri girişini işleyen kodun nasıl çalıştığını bilmiyorum. Ona dokunmak istemiyorum. Şimdiye kadar, yeniden yazdığım sorgu işe yarıyor, ancak öngörülemeyen durumlarda bozulmamasını istiyorum. En mantıklı şey gibi görünse bile, uygulamayı yeniden tasarlamak bir seçenek değildir.
silviot

0

Aşağıdaki işlev

  • dönüştürülemeyen error_resultsonuçlar için varsayılan bir değer ( ) kullanın, örneğin abcveya999999999999999999999999999999999999999999
  • tutar nullolaraknull
  • girişteki boşlukları ve diğer boşlukları düzeltir
  • geçerli bigintsolarak atılan değerler lower_bound, örneğin yalnızca pozitif değerleri zorlamak için karşılaştırılır
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

Benim de aynı ihtiyacım var ama bu JPA 2.0 ve Hibernate 5.0.2 ile çalışıyor:

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

Harikalar yaratıyor. Bence LIKE ile de çalışıyor.


-3

Bu aynı zamanda işi de yapmalıdır, ancak bu SQL genelinde ve postaya özgü değildir.

select avg(cast(mynumber as numeric)) from my table
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.