Как преобразовать строку в целое число и получить 0 в случае ошибки в преобразовании с помощью PostgreSQL?

128

В PostgreSQL у меня есть таблица со столбцом varchar. Данные должны быть целыми числами, и мне они нужны в запросе целочисленным типом. Некоторые значения представляют собой пустые строки. Последующий:

SELECT myfield::integer FROM mytable

доходность ERROR: invalid input syntax for integer: ""

Как я могу запросить приведение и получить 0 в случае ошибки во время приведения в postgres?

silviot
источник

Ответы:

161

Я просто боролся с подобной проблемой, но не хотел накладных расходов на функцию. Я пришел к следующему запросу:

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

Postgres сокращает свои условные выражения, поэтому вы не должны получать какие-либо нецелые числа, попадающие в ваш :: integer cast. Он также обрабатывает значения NULL (они не будут соответствовать регулярному выражению).

Если вам нужны нули вместо того, чтобы не выбирать, тогда должен работать оператор CASE:

SELECT CASE WHEN myfield~E'^\\d+$' THEN myfield::integer ELSE 0 END FROM mytable;
Энтони Бриггс
источник
14
Я настоятельно рекомендую согласиться с предложением Мэтью. Это решение имеет проблемы со строками, которые выглядят как числа, но превышают максимальное значение, которое вы можете поместить в целое число.
pilif
4
второй комментарий пилифа. это максимальное значение - ошибка, ожидающая своего появления. смысл того, чтобы не выдавать ошибку, состоит в том, чтобы не выдавать ошибку, когда данные недействительны. этот принятый ответ НЕ решает этого. спасибо, Мэтью! отличная работа!
Шон Ковач
3
Каким бы прекрасным ни был ответ Мэтью, мне просто нужен был быстрый и грязный способ обработки некоторых данных. Я также признаю, что мне сейчас не хватает собственных знаний по определению функций в SQL. Меня интересовали только числа от 1 до 5 цифр, поэтому я изменил регулярное выражение на E'\\d{1,5}$'.
Bobort 06
3
Да, да, это решение относительно быстрое и грязное, но в моем случае я знал, какие данные у меня есть, и что таблица была относительно короткой. Это намного проще, чем писать (и отлаживать) целую функцию. Приведенный {1,5}выше предел @ Bobort для цифр, возможно, является хорошей идеей, если вы беспокоитесь о переполнении, но он замаскирует большие числа, что может вызвать проблемы при преобразовании таблицы. Лично я предпочел бы заранее сообщить об ошибке запроса и знать, что некоторые из моих "целых чисел" ненадежны (вы также можете выбрать с помощьюE'\\d{6,}$' сначала, чтобы убедиться).
Энтони Бриггс
1
@Anthony Briggs: Это не сработает, если myfield содержит "'" или "," или ".", Или' - '
Стефан Штайгер
100

Вы также можете создать свою собственную функцию преобразования, внутри которой вы можете использовать блоки исключений:

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;

Тестирование:

=# 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
в отличие от принятого ответа, это решение здесь более правильное, поскольку оно одинаково хорошо справляется с числами, слишком большими для того, чтобы поместиться в целое число, и оно также, вероятно, будет быстрее, поскольку оно не работает с проверкой в ​​общем случае (= действительные строки )
pilif
Как бы вы приводите строку в целое число в конкретных областях , используя вашу функцию в то время как в в INSERTзаявлении?
sk
27

У меня была такая же потребность, и я обнаружил, что это хорошо работает для меня (postgres 8.4):

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

Некоторые тестовые примеры для демонстрации:

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"

Если вам нужно обработать возможность того, что поле имеет нечисловой текст (например, «100bad»), вы можете использовать regexp_replace для удаления нечисловых символов перед преобразованием.

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

Тогда значения text / varchar, такие как "b3ad5", также будут давать числа

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

Чтобы решить проблему Криса Когдона по поводу того, что решение не дает 0 для всех случаев, включая такой случай, как «плохой» (вообще нет цифровых символов), я сделал это скорректированное заявление:

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

Он работает аналогично более простым решениям, за исключением того, что он будет давать 0, когда значение для преобразования состоит только из нецифровых символов, например "плохо":

db=> select CAST((COALESCE(NULLIF(REGEXP_REPLACE('no longer bad!', '[^0-9]+', '', 'g'), ''), '0')) AS INTEGER);
     coalesce
----------
        0
(1 row)
ghbarratt
источник
Зачем вам «0» || ? Из документации: «Функция COALESCE возвращает первый из своих аргументов, который не является нулевым». Итак, если у вас есть значение null, Coalesce избавится от него.
Амала
@ Амала Верно. Хорошо поймал. Ред.
ghbarratt
1
Решение работает, только если введено целое число или NULL. Вопрос просил преобразовать любой тип ввода и использовать 0, если его нельзя преобразовать.
Крис Когдон,
@ChrisCogdon Я добавил в решение, чтобы решить вашу проблему, так как не всегда дает ноль, если значение для преобразования «не конвертируется». Эта улучшенная версия решения вернет 0, если в качестве значения для преобразования задана строка без цифровых символов.
ghbarratt
22

Это может быть чем-то вроде взлома, но в нашем случае он сделал свою работу:

(0 || myfield)::integer

Объяснение (проверено на Postgres 8.4):

Вышеупомянутое выражение дает NULLзначения NULL в пустых строках myfieldи 0для пустых строк (это точное поведение может соответствовать вашему варианту использования, а может и не соответствовать).

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

Данные испытаний:

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

Запрос даст следующий результат:

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

В то время как select only values::integerприведет к сообщению об ошибке.

Надеюсь это поможет.

Matt
источник
3

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

Я никогда не работал с PostgreSQL, но я проверил руководство на предмет правильного синтаксиса операторов IF в запросах SELECT.

Ян Ганчич
источник
Это работает для стола, как сейчас. Я немного боюсь, что в будущем он может содержать нечисловые значения. Я бы предпочел решение типа "try / catch", но это помогает. Спасибо.
silviot
Возможно, вы могли бы использовать регулярные выражения postgresql.org/docs/8.4/interactive/functions-matching.html, но это может оказаться дорогостоящим. Также примите ответ, если это решение :)
Ян Ганчич
3

@ Мэтью хорошо ответил . Но это может быть проще и быстрее. И вопрос просит преобразовать пустые строки ( '') 0, но не в другой "недопустимый синтаксис ввода" или ввод "вне диапазона":

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;

Это возвращается 0для пустой строки и NULLдля любого другого недопустимого ввода.
Его легко адаптировать для преобразования любого типа данных. .

Ввод блока исключений существенно дороже. Если часто встречаются пустые строки, имеет смысл перехватить этот случай, прежде чем вызывать исключение.
Если пустые строки встречаются очень редко, стоит перенести тест в условие исключения.

Эрвин Брандштеттер
источник
1
CREATE OR REPLACE FUNCTION parse_int(s TEXT) RETURNS INT AS $$
BEGIN
  RETURN regexp_replace(('0' || s), '[^\d]', '', 'g')::INT;
END;
$$ LANGUAGE plpgsql;

Эта функция всегда будет возвращать, 0если во входной строке нет цифр.

SELECT parse_int('test12_3test');

вернется 123

Олег Михайлов
источник
выполняли ли вы какое-либо тестирование производительности для регулярных выражений и строковых функций? Кроме того, как это обрабатывает нули? Вернет ли он 0 или NULL, как ожидалось? Спасибо!
vol7ron 06
1

Я нашел следующий код простым и работающим. Оригинальный ответ здесь https://www.postgresql.org/message-id/371F1510.F86C876B@sferacarta.com

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)

Надеюсь, поможет

Ашиш Рана
источник
1

SUBSTRING может помочь в некоторых случаях, вы можете ограничить размер int.

SELECT CAST(SUBSTRING('X12312333333333', '([\d]{1,9})') AS integer);
Устаревшее
источник
0

Если данные должны быть целыми числами, и вам нужны только эти значения как целые числа, почему бы вам не пройти всю милю и не преобразовать столбец в целочисленный столбец?

Затем вы можете выполнить это преобразование недопустимых значений в нули только один раз в той точке системы, где данные вставляются в таблицу.

С помощью приведенного выше преобразования вы заставляете Postgres преобразовывать эти значения снова и снова для каждой отдельной строки в каждом запросе этой таблицы - это может серьезно снизить производительность, если вы выполняете много запросов к этому столбцу в этой таблице.

Банди-Т
источник
В принципе, вы правы, но в этом конкретном сценарии мне нужно оптимизировать один медленный запрос в приложении. Я не знаю, как работает код, обрабатывающий ввод данных. Я не хочу его трогать. Пока мой переписанный запрос работает, но я бы хотел, чтобы он не ломался в непредвиденных случаях. Изменение архитектуры приложения - это не вариант, даже если он кажется самым разумным.
silviot
0

Следующая функция выполняет

  • используйте значение по умолчанию ( error_result) для неприводимых результатов, например, abcили999999999999999999999999999999999999999999
  • сохраняет nullкакnull
  • удаляет пробелы и другие пробелы во вводе
  • значения, представленные как действительные bigints, сравниваются, lower_boundнапример, с принудительным применением только положительных значений
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;
Чт 00 м² с
источник
-1

У меня тоже есть такая же потребность, но она работает с JPA 2.0 и Hibernate 5.0.2:

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

Творит чудеса. Я думаю, что это работает и с LIKE.

Хенди Ираван
источник
-3

Это также должно работать, но это относится к SQL, а не к postgres.

select avg(cast(mynumber as numeric)) from my table
Ронак
источник