Як передати рядок до цілого числа і мати 0 у випадку помилки в складі з PostgreSQL?


128

У PostgreSQL у мене є таблиця із стовпчиком varchar. Дані повинні бути цілими числами, і мені потрібні цілі типи у запиті. Деякі значення є порожніми рядками. Наступне:

SELECT myfield::integer FROM mytable

врожайність ERROR: invalid input syntax for integer: ""

Як я можу отримати запит на амплітуду та мати 0 у випадку помилки під час трансляції в postgres?

Відповіді:


161

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

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

Постгреси клавіш швидкого доступу до його умовних умов, тому ви не повинні отримувати жодних не цілих чисел, що впливають на ваш :: ціле число. Він також обробляє значення NULL (вони не збігаються з регулярним виразом).

Якщо ви хочете нулі замість того, щоб не вибирати, тоді оператор CASE повинен працювати:

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

14
Я настійно рекомендую піти з пропозицією Метью. У цьому рішенні є проблеми з рядками, які виглядають як числа, але перевищують максимальне значення, яке ви можете розмістити в ціле число.
пилиф

4
я другий коментар pilif. що максимальне значення - це помилка, яка чекає, що станеться. сенс не кидати помилку - не кидати помилку, коли дані недійсні. ця прийнята відповідь НЕ вирішує цього. дякую Метью! чудова робота!
Шон Ковач

3
Настільки ж велика, як відповідь Метью, мені просто потрібен був швидкий і брудний спосіб поводження для перевірки деяких даних. Я також визнаю, що зараз не вистачає моїх власних знань у визначенні функцій у SQL. Мене цікавили лише цифри від 1 до 5 цифр, тому я змінив регулярний вираз E'\\d{1,5}$'.
Бобборт

3
Так, так, це рішення є відносно швидким і брудним, але в моєму випадку я знав, які дані маю, і що таблиця була відносно короткою. Це набагато простіше, ніж написання (і налагодження) цілої функції. @ Обмеження Боборта на цифрах {1,5}вище, можливо, є хорошою ідеєю, якщо ви стурбовані переповненням, але це маскує великі числа, що може спричинити проблеми при перетворенні таблиці. Особисто я вважаю за краще помилку запиту на передній план і знаю, що деякі мої "цілі числа" є гнучкими (Ви також можете вибрати E'\\d{6,}$'перший, щоб переконатися).
Ентоні Бріггс

1
@Anthony Briggs: Це не працюватиме, якщо мійфілд містить "" "або", "або". ", Або" - "
Стефан Штайгер

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)

Тоді значення тексту / 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)

Для чого вам потрібен '0' || ? З документів: "Функція COALESCE повертає перший із своїх аргументів, який не є нульовим." Тож якщо у вас є цінність як null, Coalesce позбудеться цього.
Амала

@Amala True. Гарний улов. Відредаговано.
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   |
 ---------------------

Тоді як вибір лише values::integerпризведе до повідомлення про помилку.

Сподіваюся, це допомагає.


3

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

Я ніколи не працював з PostgreSQL, але перевірив посібник на правильність синтаксису операторів IF у SELECT-запитах.


Це працює для столу, як зараз. Я трохи боюся, що в майбутньому це може містити нечислові значення. Я б віддав перевагу приміщенню, яке намагається спробувати / улов, але це не вдається. Дякую.
сильвіот

Можливо, ви можете використовувати регулярні вирази 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


Ви виконували тестування на ефективність функції regex vs string? Крім того, як це обробляє нулі? Він поверне 0 або NULL, як очікувалося? Дякую!
vol7ron


1

Підписка може допомогти в деяких випадках, ви можете обмежити розмір int.

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

0

Якщо дані повинні бути цілими числами, і вам потрібні лише ці значення як цілі числа, чому б вам не пройти цілу милю і не перетворити стовпець у цілий стовпець?

Тоді ви могли б здійснити це перетворення незаконних значень у нулі лише один раз, у точці системи, де дані вставлені в таблицю.

З переліченою вище конверсією ви змушуєте Postgres знову і знову перетворювати ці значення для кожного рядка в кожному запиті для цієї таблиці - це може серйозно погіршити ефективність, якщо ви зробите багато запитів проти цього стовпця в цій таблиці.


В принципі ви праві, але в цьому конкретному сценарії я повинен оптимізувати один повільний запит у програмі. Я не знаю, як працює код, який обробляє введення даних. Я не хочу це чіпати. Поки мій переписаний запит працює, але я хотів би, щоб він не ламався у непередбачених випадках. Реконструювати додаток - це не варіант, навіть якщо це здається найрозумнішим.
сильвіот

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;

-1

У мене також є така ж потреба, але це працює з JPA 2.0 та Hibernate 5.0.2:

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

Творить чудеса. Я думаю, що це працює і з LIKE.


-3

Це також повинно виконувати роботу, але це в межах SQL, а не в конкретних постграфах.

select avg(cast(mynumber as numeric)) from my table
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.