#sql #postgresql #plpgsql
#sql #postgresql #plpgsql
Вопрос:
- Учитывая следующий пример функции:
CREATE OR REPLACE FUNCTION add_max_value(_x BIGINT)
RETURNS BIGINT
LANGUAGE sql
AS $$
SELECT 9223372036854775807 _x;
$$;
- Если эта функция вызывается с любым положительным значением, возвращается следующая ошибка:
SELECT add_max_value(1); -- Expecting -9223372036854775808 if math wrapped
-- SQL Error [22003]: ERROR: bigint out of range
- Как я могу выполнить математику с переносом при переполнении целых чисел в Postgres?
- Пожалуйста, обратите внимание:
- Я хочу сделать это в базе данных, а не в приложении
- Я не хочу, чтобы он повышал значение до целого числа произвольной точности (
NUMERIC
) - Хотя в примере выполняется только сложение, на практике меня интересуют и другие операции
- Пожалуйста, обратите внимание:
Комментарии:
1. Действительно ли функция выдает ошибку даже при отрицательном вводе?
2. @a_horse_with_no_name Во втором блоке кода есть ожидаемый результат. Повторяю, такое же поведение переноса, которое предлагают многие основные языки (C, Java).
3. @jarlh Вы правы, хороший вызов; положительные целые числа являются проблемой в примере. Вопрос обновлен.
4. Я не вижу другого способа, кроме как использовать промежуточную
numeric
переменную в PL / pgSQL и «переносить» вручную
Ответ №1:
В качестве функции SQL нет способа. Функции SQL не могут обрабатывать исключения. Но plpgsql
функция может:
CREATE OR REPLACE FUNCTION add_max_value(_x BIGINT)
RETURNS BIGINT
LANGUAGE plpgsql
AS $$
declare
bigx bigint;
begin
bigx = 9223372036854775807 _x;
return bigx;
exception
when sqlstate '22003' then
return (9223372036854775807::numeric _x - 2^64)::bigint;
end;
$$;
Комментарии:
1. Вы бы знали намного лучше, чем я. Но да, вы также могли бы просто «вернуть 1».
2. Никаких проблем. Специфика не имеет значения, это концепция обработки исключения, и при этом особенно специфического исключения.
3. Было бы эффективнее сначала присвоить результат добавления
numeric
переменной, а затем проверить, больше ли она9223372036854775807
? Обработка исключений довольно дорогая4. Да, за надуманный пример, где представлены исключение составляют преднамеренно вызванные, но я думаю, что не для общего случая арифметическое переполнение. Я не думаю, что проблема с OP связана с конкретным представленным примером, но с общим случаем. Если проблема в конкретном примере, то более эффективно просто перейти прямо к сброшенному значению с помощью SQL-функции оператора «select (9223372036854775807::numeric _x — 2 ^ 64)::bigint;»
Ответ №2:
Это легко входит в пятерку самых расточительных SQL, которые я когда-либо писал:
create or replace function add_max_value(_x bigint)
returns bigint
language sql
as $$
with recursive inputs as (
select s.rn, r.a::int, s.b::int, (r.a::int s.b::int) % 2 as sumbit,
(r.a::bit amp; s.b::bit)::int as carry
from regexp_split_to_table((9223372036854775807::bit(64))::text, '') with ordinality as r(a, rn)
join regexp_split_to_table((_x::bit(64))::text, '') with ordinality as s(b, rn)
on s.rn = r.rn
), addition as (
select rn, sumbit, sumbit as s2, carry, carry as upcarry
from inputs
where rn = 64
union
select i.rn, i.sumbit, (i.sumbit a.upcarry) % 2, i.carry,
(i.carry::bit | a.upcarry::bit)::int
from addition a
join inputs i on i.rn = a.rn - 1
)
select (string_agg(s2::text, '' order by rn)::bit(64))::bigint
from addition
$$;