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:
|
Имя |
Тип |
Описание |
|
|
|
код исключения |
|
|
|
имя столбца, относящегося к исключению |
|
|
|
имя ограничения целостности, относящегося к исключению |
|
|
|
имя типа данных, относящегося к исключению |
|
|
|
текст основного сообщения исключения |
|
|
|
имя таблицы, относящейся к исключению |
|
|
|
имя схемы, относящейся к исключению |
|
|
|
текст детального сообщения исключения (если есть) |
|
|
|
текст подсказки к исключению (если есть) |
|
|
|
строки текста, описывающие стек вызовов в момент исключения |
Пример обработки исключения
В качестве примера будет рассмотрена обработка ошибки деления на ноль в функции 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 row3. @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.