Jak rzutować ciąg na liczbę całkowitą i mieć 0 w przypadku błędu w rzutowaniu z PostgreSQL?

133

W PostgreSQL mam tabelę z kolumną varchar. Dane mają być liczbami całkowitymi i potrzebuję ich w postaci liczby całkowitej w zapytaniu. Niektóre wartości są pustymi ciągami. Następujące:

SELECT myfield::integer FROM mytable

plony ERROR: invalid input syntax for integer: ""

Jak mogę zapytać o rzut i mieć 0 w przypadku błędu podczas rzutowania w postgres?

silviot
źródło

Odpowiedzi:

166

Po prostu zmagałem się z podobnym problemem, ale nie chciałem narzutu funkcji. Wymyśliłem następujące zapytanie:

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

Postgres skraca swoje warunki warunkowe, więc nie powinieneś otrzymywać żadnych niecałkowitych liczb uderzających w rzutowanie :: integer. Obsługuje również wartości NULL (nie będą one pasować do wyrażenia regularnego).

Jeśli chcesz zer zamiast wybierania, to instrukcja CASE powinna działać:

SELECT CASE WHEN myfield~E'^\\d+$' THEN myfield::integer ELSE 0 END FROM mytable;
Anthony Briggs
źródło
16
Gorąco polecam skorzystać z sugestii Mateusza. W tym rozwiązaniu występują problemy z ciągami, które wyglądają jak liczby, ale są większe niż maksymalna wartość, jaką można umieścić w liczbie całkowitej.
pilif
4
drugi komentarz pilifa. ta maksymalna wartość to błąd, który czeka na wystąpienie. celem nie zgłaszania błędu jest nie zgłaszanie błędu, gdy dane są nieprawidłowe. ta zaakceptowana odpowiedź NIE rozwiązuje tego problemu. dzięki Matthew! świetna robota!
Shawn Kovac
3
Choć odpowiedź Matthew jest świetna, potrzebowałem tylko szybkiego i brudnego sposobu obsługi sprawdzania niektórych danych. Przyznaję też, że w tej chwili brakuje mojej własnej wiedzy na temat definiowania funkcji w SQL. Interesowały mnie tylko liczby od 1 do 5 cyfr, więc zmieniłem wyrażenie regularne na E'\\d{1,5}$'.
Bobort
3
Tak, tak to rozwiązanie jest stosunkowo szybkie i brudne, ale w moim przypadku wiedziałem, jakie mam dane i że tabela była stosunkowo krótka. Jest to o wiele łatwiejsze niż pisanie (i debugowanie) całej funkcji. Limit @ Bobort {1,5}powyżej cyfr jest prawdopodobnie dobrym pomysłem, jeśli obawiasz się przepełnienia, ale będzie on maskować większe liczby, co może powodować problemy, jeśli konwertujesz tabelę. Osobiście wolałbym mieć błąd zapytania na początku i wiedzieć, że niektóre z moich „liczb całkowitych” są niewyraźne (możesz też wybrać z E'\\d{6,}$'pierwszą, aby się upewnić).
Anthony Briggs
1
@Anthony Briggs: To nie zadziała, jeśli myfield zawiera „'” lub „,” lub „.”, Lub „-”
Stefan Steiger
102

Można również utworzyć własną funkcję konwersji, wewnątrz którego można użyć bloków wyjątek:

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;

Testowanie:

=# 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
źródło
8
w przeciwieństwie do akceptowanej odpowiedzi, to rozwiązanie jest tutaj bardziej poprawne, ponieważ może równie dobrze radzić sobie z liczbami zbyt dużymi, aby zmieściły się w liczbie całkowitej, a także prawdopodobnie będzie szybsze, ponieważ nie działa walidacja w typowym przypadku (= prawidłowe ciągi )
pilif
Jak byś rzucił ciąg do liczby całkowitej w określonych dziedzinach wykorzystujących swoją funkcję podczas gdy w na INSERTrachunku?
sk
27

Miałem taką samą potrzebę i stwierdziłem, że działa to dobrze dla mnie (postgres 8.4):

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

Niektóre przypadki testowe do zademonstrowania:

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"

Jeśli chcesz poradzić sobie z możliwością, że pole ma tekst nienumeryczny (na przykład „100bad”), możesz użyć regexp_replace, aby usunąć znaki nienumeryczne przed rzutowaniem.

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

Wtedy wartości text / varchar, takie jak „b3ad5”, również będą zawierać liczby

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

Aby rozwiązać obawę Chrisa Cogdona dotyczącą rozwiązania, które nie daje 0 dla wszystkich przypadków, w tym przypadku takiego jak „zły” (w ogóle bez znaków cyfrowych), poczyniłem to skorygowane stwierdzenie:

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

Działa podobnie do prostszych rozwiązań, z tą różnicą, że daje 0, gdy wartość do konwersji zawiera tylko znaki niecyfrowe, na przykład „zła”:

db=> select CAST((COALESCE(NULLIF(REGEXP_REPLACE('no longer bad!', '[^0-9]+', '', 'g'), ''), '0')) AS INTEGER);
     coalesce
----------
        0
(1 row)
ghbarratt
źródło
Dlaczego potrzebujesz „0” || ? Z dokumentacji: „Funkcja COALESCE zwraca pierwszy ze swoich argumentów, który nie jest null.” Więc jeśli masz wartość null, Coalesce się go pozbędzie.
Amala
@Amala True. Dobry chwyt. Edytowano.
ghbarratt
1
Rozwiązanie działa tylko wtedy, gdy dane wejściowe są liczbą całkowitą lub NULL. Pytanie dotyczyło konwersji dowolnego rodzaju danych wejściowych i użycia 0, jeśli nie jest konwertowalne.
Chris Cogdon
@ChrisCogdon Dodałem do rozwiązania, aby rozwiązać twój problem, nie zawsze dając zero, jeśli wartość do konwersji jest „niemożliwa do konwersji”. Ta ulepszona wersja rozwiązania zwróci 0, gdy jako wartość do konwersji zostanie podany ciąg bez znaków cyfrowych.
ghbarratt
22

To może być trochę hack, ale w naszym przypadku wykonało to zadanie:

(0 || myfield)::integer

Wyjaśnienie (testowane na Postgres 8.4):

Wymienione powyżej rentowności wyrażenie NULLdla wartości null w myfieldi 0na pustych strunach (ta dokładna zachowanie może lub nie może zmieścić się na przypadek użycia).

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

Dane testowe:

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

Zapytanie da następujący wynik:

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

Natomiast tylko zaznaczenie values::integerspowoduje wyświetlenie komunikatu o błędzie.

Mam nadzieję że to pomoże.

Matt
źródło
3

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

Nigdy nie pracowałem z PostgreSQL, ale sprawdziłem w podręczniku poprawną składnię instrukcji IF w zapytaniach SELECT.

Jan Hančič
źródło
To działa w przypadku stołu tak, jak jest teraz. Trochę się boję, że w przyszłości może zawierać wartości nienumeryczne. Wolałbym rozwiązanie typu try / catch, ale to załatwia sprawę. Dzięki.
silviot
Może mógłbyś użyć wyrażeń regularnych postgresql.org/docs/8.4/interactive/functions-matching.html, ale może to być kosztowne.
Przyjmij
3

@ Odpowiedź Mateusza jest dobra. Ale może być prostsze i szybsze. A pytanie dotyczy konwersji pustych ciągów ( '') na 0inne dane wejściowe o „nieprawidłowej składni” lub „poza zakresem”, ale nie na inne:

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;

Zwraca to 0dla pustego ciągu i NULLwszelkich innych nieprawidłowych danych wejściowych.
Można go łatwo dostosować do dowolnej konwersji typu danych .

Wprowadzenie do bloku wyjątków jest znacznie droższe. Jeśli puste łańcuchy są powszechne, warto przechwycić ten przypadek przed zgłoszeniem wyjątku.
Jeśli puste łańcuchy są bardzo rzadkie, opłaca się przenieść test do klauzuli wyjątku.

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

Ta funkcja zawsze zwróci, 0jeśli w ciągu wejściowym nie ma cyfr.

SELECT parse_int('test12_3test');

wróci 123

Oleg Michajłow
źródło
czy wykonałeś jakieś testy wydajności dla funkcji regex vs string? Jak to obsługuje wartości null? Czy zwróci 0 lub NULL zgodnie z oczekiwaniami? Dzięki!
vol7ron
1

Poniższy kod jest łatwy i działa. Oryginalna odpowiedź jest tutaj 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)

mam nadzieję, że to pomoże

Ashish Rana
źródło
1

SUBSTRING może pomóc w niektórych przypadkach, możesz ograniczyć rozmiar int.

SELECT CAST(SUBSTRING('X12312333333333', '([\d]{1,9})') AS integer);
przestarzałe
źródło
0

Jeśli dane mają być liczbami całkowitymi, a potrzebujesz tylko tych wartości jako liczb całkowitych, dlaczego nie przejdziesz przez całą milę i nie zamienisz kolumny na kolumnę całkowitą?

Następnie możesz wykonać tę konwersję niedozwolonych wartości na zera tylko raz, w punkcie systemu, w którym dane są wstawiane do tabeli.

Dzięki powyższej konwersji zmuszasz Postgres do wielokrotnego konwertowania tych wartości dla każdego pojedynczego wiersza w każdym zapytaniu dla tej tabeli - może to poważnie obniżyć wydajność, jeśli wykonasz wiele zapytań w tej kolumnie w tej tabeli.

Bandyta
źródło
W zasadzie masz rację, ale w tym konkretnym scenariuszu muszę zoptymalizować pojedyncze wolne zapytanie w aplikacji. Nie wiem, jak działa kod obsługujący wprowadzanie danych. Nie chcę tego dotykać. Jak dotąd moje przepisane zapytanie działa, ale chciałbym, aby nie przerywało w nieprzewidzianych przypadkach. Przeprojektowanie aplikacji nie wchodzi w grę, nawet jeśli wydaje się to najbardziej sensowne.
silviot
0

Następująca funkcja działa

  • użyj wartości domyślnej ( error_result) dla wyników, których nie można rzutować, np. abclub999999999999999999999999999999999999999999
  • zachowuje nulljaknull
  • usuwa spacje i inne białe znaki na wejściu
  • wartości uznane za prawidłowe bigintssą porównywane z lower_boundnp. wymuszaniem tylko wartości dodatnich
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;
Cz 00 mies
źródło
-2

Też mam tę samą potrzebę, ale to działa z JPA 2.0 i Hibernate 5.0.2:

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

Działa cuda. Myślę, że działa też z LIKE.

Hendy Irawan
źródło
-4

Powinno to również załatwić sprawę, ale dotyczy to SQL, a nie Postgres.

select avg(cast(mynumber as numeric)) from my table
ronak
źródło