Меню

Ошибка деление на ноль postgresql

I am trying to run the following query which results me postgres error: division by zero

select 
    request_count,
    response_count, 
    (response_count*100) / (request_count+response_count) AS proportion 
from total_dummy_table; 

How can I avoid and resolve the division by zero error?

Tried to fix using the below query but getting the same result as above

SELECT 
    request_count,
    response_count,
    (response_count*100) / (request_count) AS proportion 
FROM 
    total_dummy_table
ORDER BY 
    (response_count*100) /(CASE request_count WHEN 0 Then NULL ELSE request_count END) DESC NULLS FIRST

Please let me know where am I doing wrong, or how can I fix this.
Thanks!

Result expectation:

The query should return me something like below:

Request_count | Response_count | Proportion

1603423       |  585706        | 36.52

Quick note: Table total_dummy_table does not have column name proportion that is the addition to the result which have calculated proportion in it.

I am trying to run the following query which results me postgres error: division by zero

select 
    request_count,
    response_count, 
    (response_count*100) / (request_count+response_count) AS proportion 
from total_dummy_table; 

How can I avoid and resolve the division by zero error?

Tried to fix using the below query but getting the same result as above

SELECT 
    request_count,
    response_count,
    (response_count*100) / (request_count) AS proportion 
FROM 
    total_dummy_table
ORDER BY 
    (response_count*100) /(CASE request_count WHEN 0 Then NULL ELSE request_count END) DESC NULLS FIRST

Please let me know where am I doing wrong, or how can I fix this.
Thanks!

Result expectation:

The query should return me something like below:

Request_count | Response_count | Proportion

1603423       |  585706        | 36.52

Quick note: Table total_dummy_table does not have column name proportion that is the addition to the result which have calculated proportion in it.

PostgreSQL, SQL, Блог компании OTUS. Онлайн-образование


Рекомендация: подборка платных и бесплатных курсов дизайна интерьера — https://katalog-kursov.ru/

В преддверии старта курса PostgreSQL подготовили небольшой полезный материал.

Большинство языков программирования предназначены для профессиональных разработчиков, знающих алгоритмы и структуру данных. Язык SQL немного отличается.

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

Чтобы заставить сотрудников писать на SQL лучше, мы тщательно изучили отчеты, написанные не разработчиками, и код ревью, собрали распространенные ошибки и возможности по оптимизации в SQL.

Будьте внимательны во время деления целых чисел

В PostgreSQL деление целого числа на целое число в результате дает целое число. Не делайте так:

db=# (
  SELECT tax / price AS tax_ratio
  FROM sale
);
 tax_ratio
----------
    0

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

 db=# (
  SELECT tax / price::float AS tax_ratio
  FROM sale
);
 tax_ratio
----------
 0.17

Неспособность распознать эту ошибку может привести к крайне неточным результатам.

Защита от ошибок деления на ноль

Деление на ноль — известная ошибка:

db=# SELECT 1 / 0
ERROR: division by zero

Деление на ноль является логической ошибкой и ее нужно не просто «обойти», а исправить так, чтобы на первом месте у вас не было делителя, равного нулю. Однако бывают ситуации, когда возможен нулевой делитель. Один из простых способов защиты от ошибок деления на ноль — это присвоить всему выражению неопределенное значение, установив неопределенное значение делителю, если он равен нулю:

db=# SELECT 1 / NULLIF(0, 0);
 ?column?
----------
   -

Функция NULLIF возвращает неопределенное значение null, если первый аргумент равен второму. В этом случае, если знаменатель равен нулю.

При делении любого числа на NULL результатом будет NULL. Чтобы получить некоторое значение, вы можете свернуть все выражение с COALESCE и предоставить дефолтное значение:

db=# SELECT COALESCE(1 / NULLIF(0, 0), 1);
 ?column?
----------
    1

Функция COALESCE очень полезна. Она допускает любое количество аргументов и возвращает первое значение, которое не является неопределенным.

Знайте разницу между UNION и UNION ALL

Классический вопрос на собеседовании начального уровня для разработчиков и администраторов баз данных: «В чем разница между функциями UNION и UNION ALL?».

UNION ALL объединяет результаты одного или нескольких запросов. UNION делает то же самое, и к тому же исключает повторяющиеся строки. Не делайте так:

 SELECT created_by_id FROM sale
  UNION
  SELECT created_by_id FROM past_sale
);
QUERY PLAN
-----------
Unique  (cost=2654611.00..2723233.86 rows=13724572 width=4)
  ->  Sort  (cost=2654611.00..2688922.43 rows=13724572 width=4)
        Sort Key: sale.created_by_id
        ->  Append  (cost=0.00..652261.30 rows=13724572 width=4)
              ->  Seq Scan on sale  (cost=0.00..442374.57 rows=13570157 width=4)
              ->  Seq Scan on past_sale  (cost=0.00..4018.15 rows=154415 width=4)

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

Если вам не нужно удалять повторяющиеся строки, лучше использовать функцию UNION ALL:

 db=# (
  SELECT created_by_id FROM sale
  UNION ALL
  SELECT created_by_id FROM past_sale
);
QUERY PLAN
-----------
 Append  (cost=0.00..515015.58 rows=13724572 width=4)
   ->  Seq Scan on sale  (cost=0.00..442374.57 rows=13570157 width=4)
   ->  Seq Scan on past_sale  (cost=0.00..4018.15 rows=154415 width=4)

Выполняется намного проще. Результаты получены, сортировка не требуется.

Будьте внимательны при подсчете столбцов, допускающих неопределенное значение

При использовании агрегатных функций, таких как COUNT, важно понимать, как они обрабатывают неопределенные значения.
Например, возьмите следующую таблицу:

db=# pset null NULL
Null display is "NULL".
db=# WITH tb AS (
  SELECT 1 AS id
  UNION ALL
  SELECT null AS id
)
SELECT *
FROM tb;
  id
------
    1
 NULL

Столбец id содержит значение null. Посчитаем столбец id:

 db=# WITH tb AS (
  SELECT 1 AS id
  UNION ALL
  SELECT null AS id
)
SELECT COUNT(id)
FROM tb;
 count
-------
     1

В таблице две строки, но функция COUNT возвращает 1. Это произошло, потому что функция COUNT игнорирует неопределенные значения.

Чтобы посчитать строки, используйте функцию COUNT(*):

 db=# WITH tb AS (
  SELECT 1 AS id
  UNION ALL
  SELECT null AS id
)
SELECT COUNT(*)
FROM tb;
 count
-------
  2

Эта особенность также может быть полезна. Например, если поле с именем modified содержит неопределенное значение в том случае, если строка не была изменена, вы можете рассчитать процент измененных строк следующим образом:

db=# (
  SELECT COUNT(modified) / COUNT(*)::float AS modified_pct
  FROM sale
);
 modified_pct
---------------
  0.98

Другие агрегатные функции, такие как SUM, будут игнорировать неопределенные значения. Для демонстрации применим функцию SUM к полю, содержащему только неопределенные значения:

db=# WITH tb AS (
  SELECT null AS id
  UNION ALL
  SELECT null AS id
)
SELECT SUM(id::int)
FROM tb;
 sum
-------
 NULL

Это все документированные операции, так что будьте внимательны!

Обратите внимание на часовые пояса

Часовые пояса всегда являются источником путаницы и ошибок. PostgreSQL отлично справляется с часовыми поясами, но вам все равно придется обратить внимание на некоторые вещи.

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

SELECT created_at::date, COUNT(*)
FROM sale
GROUP BY 1

Без точной настройки часового пояса вы можете получить разные результаты в зависимости от часового пояса, установленного приложением:

 now
------------
 2019-11-08
db=# SET TIME ZONE 'australia/perth';
SET
db=# SELECT now()::date;
now
------------
2019-11-09

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

При обращении к временной метке сначала приведите ее к нужному часовому поясу:

SELECT (timestamp at time zone 'asia/tel_aviv')::date, COUNT(*)
FROM sale
GROUP BY 1;

За установку часового пояса обычно отвечает приложение. Например, чтобы получить часовой пояс, используемый psql:

db=# SHOW timezone;
TimeZone
----------
Israel
db=# SELECT now();
now
-------------------------------
2019-11-09 11:41:45.233529+02

А чтобы установить часовой пояс в PSQL:

 db=# SET timezone TO 'UTC';
SET
db=# SELECT now();
now
-------------------------------
2019-11-09 09:41:55.904474+00

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

Избегайте преобразований в индексированных полях

Использование функций в индексированном поле может помешать базе данных использовать индекс в этом поле:

SELECT * FROM sale
  WHERE created at time ZONE 'asia/tel_aviv' > '2019-10-01'
);
QUERY PLAN
----------
Seq Scan on sale (cost=0.00..510225.35 rows=4523386 width=276)
Filter: timezone('asia/tel_aviv', created) > '2019-10-01 00:00:00'::timestamp without time zone

Поле created индексируется, но поскольку мы преобразовали его с помощью часового пояса, индекс не использовался.

Один из способов использования индекса в этом случае — применить преобразование в правой части:

SELECT * FROM sale WHERE created > '2019-10-01' AT TIME ZONE 'asia/tel_aviv' );
QUERY PLAN
----------
Index Scan using sale_created_ix on sale  (cost=0.43..4.51 rows=1 width=276)
Index Cond: (created > '2019-10-01 00:00:00'::timestamp with time zone)

Другим примером использования дат является фильтрация определенного периода:

 db=# (
 SELECT * FROM sale WHERE created + INTERVAL '1 day' > '2019-10-01'
);
QUERY PLAN
----------
 Seq Scan on sale  (cost=0.00..510225.35 rows=4523386 width=276)
   Filter: ((created + '1 day'::interval) > '2019-10-01 00:00:00+03'::timestamp with time zone)

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

 SELECT *
  FROM sale
  WHERE created > '2019-10-01'::date - INTERVAL '1 day'
);
QUERY PLAN
----------
 Index Scan using sale_created_ix on sale  (cost=0.43..4.51 rows=1 width=276)
   Index Cond: (created > '2019-10-01 00:00:00'::timestamp without time zone)

Заключение

Применение приведенных выше советов в повседневной жизни помогает нам поддерживать работоспособную базу данных с минимальными потерями. Мы обнаружили, что обучение разработчиков и не разработчиков тому, как лучше писать на SQL, может иметь большое значение. Если у вас есть какие-то советы по SQL, которые мы могли пропустить, дайте нам знать, и мы добавим их сюда!

Во встроенном процедурном языке PL/pgSQL для СУБД PostgreSQL отсутствуют привычные операторы TRY / CATCH для для перехвата исключений возникающих в коде во время выполнения. Аналогом является оператор EXCEPTION, который используется в конструкции:

BEGIN
  -- код, в котором может возникнуть исключение
EXCEPTION WHEN OTHERS -- аналог catch  
THEN
    -- код, обрабатывающий исключение
END

Если необходимо обработать только конкретную ошибку, то в условии WHEN нужно указать идентификатор или код конкретной ошибки:

BEGIN
  -- код, в котором может возникнуть исключение
EXCEPTION WHEN '<идентификатор_или_код_ошибки>'
THEN
    -- код, обрабатывающий исключение
END

Внутри секции EXCEPTION код ошибки можно получить из переменной SQLSTATE, а текст ошибки из переменной SQLERRM:

BEGIN
  -- код, в котором может возникнуть исключение
EXCEPTION WHEN OTHERS  
THEN
    RAISE NOTICE 'ERROR CODE: %. MESSAGE TEXT: %', SQLSTATE, SQLERRM;
END

Более подробную информацию по исключению можно получить командой GET STACKED DIAGNOSTICS:

BEGIN
  -- код, в котором может возникнуть исключение
EXCEPTION WHEN OTHERS  
THEN
		GET STACKED DIAGNOSTICS
    	err_code = RETURNED_SQLSTATE, -- код ошибки
		msg_text = MESSAGE_TEXT, -- текст ошибки
    	exc_context = PG_CONTEXT, -- контекст исключения
 		msg_detail = PG_EXCEPTION_DETAIL, -- подробный текст ошибки
 		exc_hint = PG_EXCEPTION_HINT; -- текст подсказки к исключению

    RAISE NOTICE 'ERROR CODE: % MESSAGE TEXT: % CONTEXT: % DETAIL: % HINT: %', 
   	err_code, msg_text, exc_context, msg_detail, exc_hint;
END

Полный список переменных, которые можно получить командой GET STACKED DIAGNOSTICS:

Имя

Тип

Описание

RETURNED_SQLSTATE

text

код исключения

COLUMN_NAME

text

имя столбца, относящегося к исключению

CONSTRAINT_NAME

text

имя ограничения целостности, относящегося к исключению

PG_DATATYPE_NAME

text

имя типа данных, относящегося к исключению

MESSAGE_TEXT

text

текст основного сообщения исключения

TABLE_NAME

text

имя таблицы, относящейся к исключению

SCHEMA_NAME

text

имя схемы, относящейся к исключению

PG_EXCEPTION_DETAIL

text

текст детального сообщения исключения (если есть)

PG_EXCEPTION_HINT

text

текст подсказки к исключению (если есть)

PG_EXCEPTION_CONTEXT

text

строки текста, описывающие стек вызовов в момент исключения

Пример обработки исключения

В качестве примера будет рассмотрена обработка ошибки деления на ноль в функции catch_exception:

CREATE OR REPLACE FUNCTION catch_exception
(
	arg_1 int,
	arg_2 int,
	OUT res int
)
LANGUAGE plpgsql
AS $$
DECLARE 
	err_code text;
	msg_text text;
	exc_context text;
BEGIN
	BEGIN
		res := arg_1 / arg_2;
	EXCEPTION 
	WHEN OTHERS 
  THEN
		res := 0;
    
		GET STACKED DIAGNOSTICS
    	err_code = RETURNED_SQLSTATE,
		msg_text = MESSAGE_TEXT,
    	exc_context = PG_CONTEXT;

   		RAISE NOTICE 'ERROR CODE: % MESSAGE TEXT: % CONTEXT: %', 
   		err_code, msg_text, exc_context;
  END;
END;
$$;

Вызов функции catch_exception со значением 0 в качестве второго параметра вызовет ошибку деления на ноль:

DO $$
DECLARE 
	res int;
BEGIN
	SELECT e.res INTO res
	FROM catch_exception(4, 0) AS e;
	
	RAISE NOTICE 'Result: %', res;
END;
$$;

Результаты обработки ошибки будут выведены на консоль:

ERROR CODE: 22012 
MESSAGE TEXT: деление на ноль 
CONTEXT: функция PL/pgSQL catch_exception(integer,integer), строка 14, оператор 
GET STACKED DIAGNOSTICS
SQL-оператор: "SELECT e.res FROM catch_exception(4, 0) AS e"
функция PL/pgSQL inline_code_block, строка 5, оператор SQL-оператор
Result: 0

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

(CASE(COALESCE(COUNT(column_name),1)) WHEN 0 THEN 1
ELSE (COALESCE(COUNT(column_name),1)) END) 

интересно, есть ли лучший способ сделать это?

5 ответов


С count() никогда не возвращает NULL (в отличие от других агрегатных функций), вам нужно только поймать 0 case (который является единственным проблемным случаем в любом случае):

CASE count(column_name)
   WHEN 0 THEN 1
   ELSE count(column_name)
END

цитирование руководства по агрегатным функциям:

следует отметить, что за исключением count, эти функции возвращают
значение null, если строки не выбраны.

26

автор: Erwin Brandstetter


вы можете использовать функцию NULLIF, например

something/NULLIF(column_name,0)

, Если значение column_name равно 0-результат всего выражения будет равен NULL

115

автор: Yuriy Galanter


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

greatest( count(column_name), 1 )  -- NULL and 0 are valid argument values

Примечание:
Я бы предпочел либо вернуть NULL, как в ответе Эрвина и Юрия, либо решить это логически, обнаружив значение 0 перед операцией деления, и возвращение 0. В противном случае данные могут быть искажены с помощью 1.


Если вы хотите, чтобы делитель был 1, Когда счетчик равен нулю:

count(column_name) + 1 * (count(column_name) = 0)::integer

С true to integer это 1.


другое решение, избегающее только деления на ноль, изменяя деление на 1

select column + (column = 0)::integer;

Я получаю эту ошибку: ОШИБКА: деление на ноль. Состояние SQL: 22012

Ниже мой запрос —

UPDATE USR
SET    PRCNT_SATSFCTN = (SELECT (SELECT COUNT(*) 
             FROM   ORDR 
             WHERE  USR.USR_ID = ORDR.USR_ID AND 
                    STSFD_SW = 'Y') * 100 / COUNT(*) 
                 FROM   ORDR
                     WHERE  USR.USR_ID = ORDR.USR_ID)

2 ответы

Вы можете попробовать:

UPDATE usr
SET    prcnt_satsfctn = o.share
FROM  (
    SELECT usr_id
         ,(count(CASE WHEN stsfd_sw = 'Y' THEN 1 ELSE NULL END) * 100)
         / count(*) AS share   -- cannot be NULL!
    FROM   ordr
    GROUP  BY 1
    ) o
WHERE  usr.usr_id = o.usr_id

Этот запрос следует улучшить по нескольким направлениям:

  • Есть ли не обновить любую строку в usr, где нет соответствующей строки в ordr. Вот где происходит деление на 0. (Запрос @Jan обновится с NULL вместо.)

  • Деления на 0 здесь быть не может.

  • Быстрее, потому что нужно только сканировать таблицу ordr один раз.

  • Короче, чище.

Создан 08 фев.

Как говорится в ошибке. Расчет завершится ошибкой, если ваш счетчик ORDR (*) равен нулю. Может быть, добавьте дополнительную проверку, чтобы убедиться, что правая часть вашего деления никогда не будет равна нулю.

UPDATE USR SET    PRCNT_SATSFCTN = (SELECT (SELECT COUNT(*) 
              FROM   ORDR 
              WHERE  USR.USR_ID = ORDR.USR_ID AND 
                     STSFD_SW = 'Y') * 100 / COUNT(*) 
                  FROM   ORDR
                      WHERE  USR.USR_ID = ORDR.USR_ID 
                      HAVING COUNT(*) > 0)

Создан 08 янв.

Не тот ответ, который вы ищете? Просмотрите другие вопросы с метками

postgresql
bulkinsert

or задайте свой вопрос.

#sql #postgresql #where-clause

#sql #postgresql #where-предложение

Вопрос:

Выполнение одного и того же запроса в разных версиях PostgreSQL дает разный результат.

 WITH t(a, b) AS (VALUES (1, 0))
SELECT a / b
  FROM t
 WHERE b <> 0;
 

На v11.10 я не получаю строк, как ожидалось, скрипка
На v13.1 я получаю ERROR: division by zero
скрипку

Почему я получаю деление на ноль для PostgreSQL v13.1 ? Я полагаю, что строки должны быть отфильтрованы, как это было сделано для v11.10

UPD
Я думаю, что когда мы присоединяемся, оптимизатор не должен иногда вызывать боковую функцию (когда это возможно) и экономить время: скрипка

Комментарии:

1. @GMB: ваша скрипка работает без ошибок. Что вы имеете в виду, говоря о баге? он должен или должен подняться division by zero ?

Ответ №1:

Более новые версии PostgreSQL (начиная с 12) просматривают ваше странное использование списка ЗНАЧЕНИЙ constant single row, чтобы увидеть возможность сворачивания константы. Но он складывает константу деления перед тем, как она складывает константу WHERE, и поэтому срабатывает ошибка.

В 11 вы все равно получите ошибку в других подобных местах, например:

 select 1/0 from pgbench_accounts where aid < -100;
 

Это может раздражать, если вы пытаетесь придумать очень простой пример, который сам по себе не полезен, а просто для проверки чего-то другого. Но я просто списываю это на «Играть в глупые игры, выигрывать глупые призы» и нахожу какой-то другой способ проверить то, что я хочу протестировать. В вашем случае вы можете просто материализовать CTE.

 WITH t(a, b) AS materialized (VALUES (1, 0))
SELECT a / b            
  FROM t
 WHERE b <> 0;
 

Комментарии:

1. Какой-то гуру в IRC советует мне использовать offset 0 и боковое соединение для случаев, когда выражение является функцией: dbfiddle.uk /…

2. Я обнаружил, что если я создам вторую строку, то также не будет постоянного свертывания: dbfiddle.uk /… Поэтому я выделяю single row

3. @EugenKonkov: также нет ошибки, если вы используете реальную таблицу, а не CTE: dbfiddle.uk /…

Ответ №2:

Похоже, порядок вычисления не такой, каким можно было бы ожидать: то есть можно было бы ожидать, что 0 знаменатели будут отфильтрованы where предложением до того, как произойдет вычисление в select предложении. Очевидно, что база данных предпочитает делать что-то по-другому.

Однако вы можете легко обойти эту проблему с помощью nullif() :

 with t(a, b) as (values (1, 0))
select a / nullif(b, 0)
from t
where b <> 0;
 

Это правильно приводит к отсутствию строк.

Я бы склонен квалифицировать это как ошибку или, по крайней мере, регресс. Играя в db<>fiddle, ваш исходный код отлично работает в версиях 9.4, 9.5, 9.6, 10 и 11, но терпит неудачу в версиях 12 и 13.

Комментарии:

1. Спасибо. Я попытаюсь отправить PostgreSQL hackers group

2. @EugenKonkov: Мне было бы интересно прочитать, как они это оценят.

3. Ответ от IRC: это сделано намеренно. Требуется для постоянной кратной оптимизации

4. ответ @jjanes объясняет этот случай намного лучше, чем этот, он сводится к постоянному сворачиванию и встраиванию CTE.

0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии

А вот еще интересные материалы:

  • Яшка сломя голову остановился исправьте ошибки
  • Ясность цели позволяет целеустремленно добиваться намеченного исправьте ошибки
  • Ясность цели позволяет целеустремленно добиваться намеченного где ошибка
  • Ошибка деление на 0 формула используемая при расчете оклад долянеполногорабочеговремени времявднях
  • Ошибка деление на 0 при увольнении