How to avoid floating point overflows in PostgreSQL? - sql

In PostgreSQL, when running a computation like this:
select 1E300::float * 1E300::float
I'd like to receive an 'Infinity'::float value, similar to when I do that in Java or CockroachDB. Instead, I'm getting:
SQL Error [22003]: ERROR: value out of range: overflow
SQLFiddle here. How can I do this?

if you can encapsulate your select statement in a plpgsql function then you can manage the error like this :
CREATE OR REPLACE FUNCTION multiply_by_float(a float, b float)
RETURNS float LANGUAGE plpgsql IMMUTABLE AS
$$
DECLARE
res float ;
BEGIN
SELECT a * b INTO res ;
RETURN res ;
EXCEPTION WHEN OTHERS THEN
CASE
WHEN (a > 0 and b < 0) or (a < 0 and b > 0)
THEN RETURN '-Infinity';
ELSE RETURN 'Infinity' ;
END CASE ;
END ;
$$ ;
Then SELECT multiply_by_float(1E300::float, 1E300::float) returns Infinity.

you can use numeric and decimal types PG doc
select 1E300::decimal * 1E300::decimal;
result
select 1E300::decimal * 1E300::decimal;
?column?
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

Related

In PostgreSQL what does the CREATE AGGREGATE option SORTOP do?

From the Postgres documentation (https://www.postgresql.org/docs/9.6/sql-createaggregate.html) I find it hard to deduce what the parameter SORTOP does.
Is this option only applicable to an ordered-set aggregate?
Concretely I'm trying to create an aggregate function that finds the most frequent number in a column of numbers. I thought specifying the SORTOP option would sort the data before executing my self defined aggregate function, but this doesn't seem to be the case.
Here is my current implementation that only works when the input data is sorted.
It loops over the rows and keeps track of the largest sequence of previous numbers (largfreq variables in state) and the amount of repetitions seen so far of the number that it's currently on (currfreq variables in state).
CREATE TYPE largfreq_state AS (
largfreq_val INT,
largfreq INT,
currfreq_val INT,
currfreq INT
);
CREATE FUNCTION slargfreq(state largfreq_state, x INT) RETURNS largfreq_state AS $$
BEGIN
if state.currfreq_val <> x then
if state.currfreq >= state.largfreq then
state.largfreq = state.currfreq;
state.largfreq_val = state.currfreq_val;
end if;
state.currfreq = 1;
state.currfreq_val = x;
else
state.currfreq = state.currfreq + 1;
end if;
return state;
END;
$$ language plpgsql;
CREATE FUNCTION flargfreq(state largfreq_state) RETURNS INT AS $$
BEGIN
if state.currfreq >= state.largfreq then
return state.currfreq_val;
else
return state.largfreq_val;
end if;
END;
$$ language plpgsql;
CREATE AGGREGATE largfreq(INT) (
SFUNC = slargfreq,
STYPE = largfreq_state,
FINALFUNC = flargfreq,
INITCOND = '(0, 0, 0, 0)',
SORTOP = <
);
This is well explained in the documentation:
Aggregates that behave like MIN or MAX can sometimes be optimized by looking into an index instead of scanning every input row. If this aggregate can be so optimized, indicate it by specifying a sort operator. The basic requirement is that the aggregate must yield the first element in the sort ordering induced by the operator; in other words:
SELECT agg(col) FROM tab;
must be equivalent to:
SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
So you need that for aggregates that can be calculated using an index scan.

Mathematical operator for rounding the numbers up with lots of decimal places

I have a number: 0.01744649 and I need to round it from behind. I would like to get a result: 0.018
P.S.
I've tried all the possibilities of documentation: enter link description here - every time I get a different result but not this what I want.
Use ceil:
SELECT ceil(0.01744649 * 1000) / 1000
If you need to round one digit at a time, like this: 0.01744649 -> 0.0174465 -> 0.017447 -> 0.01745 -> 0.0175 -> 0.018, here's the function:
CREATE OR REPLACE FUNCTION public.rounding(_value numeric, _precision int)
RETURNS numeric AS
$BODY$
DECLARE
tmp_val numeric;
i integer;
BEGIN
tmp_val = _value;
i = 10;
WHILE i >= _precision LOOP
tmp_val = round(tmp_val, i);
i = i - 1;
END LOOP;
RETURN tmp_val;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Usage:
SELECT public.rounding(0.01744649, 3);
0.018
SELECT public.rounding(0.01744444, 3);
0.017
You just need to add 5/10000 before rounding to 3 decimals.
select round(0.01744649+0.0005,3);
round
-------
0.018
(1 row)
create or replace function dugi_round (
p_fl numeric,
p_pr int,
p_depth int default 0
) returns numeric language plpgsql as $$
declare n_fl numeric;
begin
n_fl := p_fl * 10.0;
-- raise notice 'we have now %, %',n_fl,p_pr;
if floor(n_fl) < n_fl then
-- raise notice 'remaining diff % - % = %',
-- n_fl, floor(n_fl), n_fl - floor(n_fl);
n_fl := dugi_round(n_fl, p_pr, p_depth + 1);
end if;
if (p_depth > p_pr) then
n_fl := round(n_fl / 10);
else
n_fl := round(n_fl / 10, p_pr);
end if;
-- raise notice 'returning %, %', n_fl, p_pr;
return n_fl;
end;
$$
;
ghp=# select dugi_round(0.01744649, 3);
dugi_round
------------
0.018
(1 row)
If you're trying to round it to the 3rd decimal, try to multiply it by 1000, ceil it and divide it by 1000 again. That should produce the result you're expecting.

PostgreSQL: How to pass and array to a function and using it in a query with the IN operator

I have a problem, I want to pass an array to a postgres function and use that array so returns values in a SELECT IN clause.
But It shows me this error:
An error occurred executing the SQL command :
SELECT
*
FROM
get_invtransferences_porders_fporders (300001300 , array [ 300093753 , 300094126 , 300093349 , 300093838 , 300094128 ] ...
ERROR: operator does not exist : integer = integer [ ]
Hint : No operator matches the name and type of arguments. You may need to add explicit type conversions .
Where : PL / pgSQL get_invtransferences_porders_fporders (numeric , integer []) function on line 8 FOR loop around rows of a SELECT
This is my function:
CREATE OR REPLACE FUNCTION public.get_invtransferences_porders_fporders(p_product_id numeric, P_INVTRANSFERENCES_IDS integer[])
RETURNS SETOF record
LANGUAGE plpgsql
AS
$body$
DECLARE
PORDER_PRODUCT RECORD;
COMPONENT RECORD;
COMPONENT2 RECORD;
COMPONENT3 RECORD;
BEGIN
FOR PORDER_PRODUCT IN (
SELECT
'porder' AS "operation_type"
,porders.id AS "porder_id"
,porders.user_id AS "porder_user_id"
,(SELECT name FROM users WHERE users.id = porders.user_id) AS "porder_user_name"
,porders.delivery_datetime AS "porder_delivery_datetime"
,porders_products.requested AS "product_requested"
,porders_products.produced AS "product_produced"
,products.code AS "product_code"
,products.NAME AS "product_name"
,(
SELECT products.name
FROM products
WHERE id = product_components.component_product_id
) AS "component_product_name"
,product_components.quantity AS "component_quantity"
,(
SELECT products.um_id
FROM products
WHERE id = product_components.component_product_id
) AS "component_um_id"
,(product_components.quantity / products.production_base) * porders_products.requested AS "total"
FROM porders
,porders_products
,products
,product_components
WHERE porders.id = porders_products.porder_id
AND porders_products.product_id = products.id
AND porders_products.product_id = product_components.product_id
AND porders.id IN (
SELECT rawm_audit_porders.porder_id
FROM rawm_audit_invtransferences
,rawm_audits
,rawm_audit_porders
WHERE rawm_audit_invtransferences.rawm_audits_id = rawm_audits.id
AND rawm_audit_porders.rawm_audits_id = rawm_audits.id
AND rawm_audit_invtransferences.invtransference_id IN
(
SELECT
invtransferences.id
FROM invtransferences
,invtransferences_products
,products
WHERE invtransferences.id = invtransferences_products.invtransference_id
AND products.id = invtransferences_products.product_id
AND invtransferences.id IN (P_INVTRANSFERENCES_IDS)
)
)
AND product_components.component_product_id = p_product_id
) LOOP
IF(PORDER_PRODUCT.porder_id IS NOT NULL)THEN
RETURN NEXT PORDER_PRODUCT;
END IF;
END LOOP;
RETURN;
END;
$body$
VOLATILE
COST 100
ROWS 1000
I think the error it here `invtransferences.id IN (P_INVTRANSFERENCES_IDS)
This is the select that calls the function:
SELECT
*
FROM
get_invtransferences_porders_fporders(300001300 , array[300093753, 300094126, 300093349, 300093838, 300094128] )
AS
(
"operation_type" varchar,
"porder_id" numeric,
"porder_user_id" numeric,
"porder_user_name" varchar,
"porder_delivery_datetime" date,
"product_requested" numeric,
"product_produced" numeric,
"product_code" varchar,
"product_name" varchar,
"component_product_name" varchar,
"component_quantity" numeric,
"component_um_id" varchar,
"total" numeric
)
ORDER BY
"porder_id";
EDIT: I remove the VARIADIC words that were in the function and in the select that calls the function
Can you hep me Please.
You don't need to declare your function as VARIADIC to pass array to it.
Try this
CREATE OR REPLACE FUNCTION xxx(
p_product_id integer,
P_INVTRANSFERENCES_IDS integer[])
RETURNS SETOF record
LANGUAGE sql
AS
$body$
select p_product_id = ANY(P_INVTRANSFERENCES_IDS)
$body$;
Note there is no VARIADIC before P_INVTRANSFERENCES_IDS.
You also need to use ANY instead of IN to check membership in array.
SqlFiddle

PostgreSQL: get count of occurrences of specified element in array

I need to calculate the count of occurrences of specified element in array, something like:
elem_occurrences_count(ARRAY[a,b,c,a,a], a) = 3
elem_occurrences_count(ARRAY[a,b,c], d) = 0
Is there any function in PostgreSQL that can be used to solve the problem? Any help is appreciated.
You will need to unnest the array and then count the occurrences.
with elements (element) as (
select unnest(ARRAY['a','b','c','a','a'])
)
select count(*)
from elements
where element = 'a';
This can easily be embedded into a function:
create or replace function count_elements(elements text[], to_find text)
returns bigint
as
$body$
select count(*)
from unnest(elements) element
where element = to_find;
$body$
language sql;
Update
Since Postgres 9.5 this can also be done using array_positions() which returns an array of positions where an element was found. The length of that array is the number of occurrences:
select cardinality(array_positions(ARRAY['a','b','c','a','a'], 'a'));
9.5+
There is an easier method now
SELECT
sArray,
c,
coalesce(array_length( array_positions(sArray, c), 1 ),0) AS count
FROM ( VALUES
(ARRAY['a','b','c','a','a'], 'a'),
(ARRAY['a','b','c'], 'd')
) AS t(sArray,c);
sarray | c | count
-------------+---+-------
{a,b,c,a,a} | a | 3
{a,b,c} | d | 0
(2 rows)
The occurrence of all elements in an array can be found with this query:
SELECT count(id), UNNEST(array) as element
FROM myTable
GROUP BY element;
To count the occurrence of a specific element, for example 'c', add a WHERE clause:
SELECT count(id), UNNEST(array) as element
FROM myTable
WHERE EXISTS (SELECT * FROM UNNEST(array) AS x WHERE x='c')
GROUP BY element;
You can use the FILTER clause to count the occurrences.
Let's say we have a questions table with tags (array type) and you want to count the questions with postgresql tag:
SELECT COUNT(*) FILTER (WHERE '{postgresq}' <# (tags)) as tagCount
FROM posts;
More generic function is here;
CREATE FUNCTION count_array_elements (
i_elements pg_catalog.anyarray,
i_element_to_find pg_catalog.anyelement,
out count bigint
)
RETURNS bigint AS
$body$
BEGIN
SELECT count(*) INTO count
FROM unnest(i_elements) u
WHERE u = i_element_to_find;
END;
$body$
LANGUAGE 'plpgsql'
IMMUTABLE
RETURNS NULL ON NULL INPUT;
With this way, we can query like this one below;
SELECT * FROM count_array_elements(array [ TRUE, TRUE, FALSE, FALSE, FALSE ], TRUE);
Thanks to all contributors here, I learnt a few things.
I am building on work of others in this thread and others in stackoverflow.
I tried to create a function that will count for all the unique elements in the array.
I was targeting returning a json but it seems you can only return as SETOF.
result of count_element_3
CREATE OR REPLACE FUNCTION count_element_3(str_array text[])
RETURNS setof text
AS
$$
DECLARE
unique_element_array text[];
cardinality_array int[];
retArray text[];
BEGIN
-- Find unique items first
unique_element_array := array(select distinct unnest(str_array));
FOR I IN array_lower(unique_element_array, 1)..array_upper(unique_element_array, 1)
LOOP
cardinality_array[I] := (select cardinality(array_positions(str_array, unique_element_array[I])));
retArray[I] := concat(unique_element_array[I],':',cardinality_array[I]);
END LOOP;
RETURN QUERY SELECT(retArray::text);
END;
$$
LANGUAGE plpgsql
VOLATILE
RETURNS NULL ON NULL INPUT;
with t1 as (SELECT
sArray,
c,
coalesce(array_length( array_positions(sArray, c), 1 ),0) AS count
FROM ( VALUES
(ARRAY['a','b','c','a','a'], 'a'),
(ARRAY['a','b','c'], 'd')
) AS t(sArray,c)
)
select sarray, count_element_3(sarray) from t1
sarray count_element_3
text[] text
-------------------------------------
"{a,b,c,a,a}" "{c:1,a:3,b:1}"
"{a,b,c}" "{c:1,a:1,b:1}"

Postgres date-numeric not working in function

Can anyone help me to resolve the issue ??
CREATE OR REPLACE FUNCTION func_maj_get_new_user(in_date numeric)
RETURNS integer AS
$BODY$
declare
count_newusr integer;
begin
SELECT count(a.app_uid)
INTO count_newusr
FROM
(SELECT s_start.app_uid
FROM devdba.s_maj_sdk_bs s_start
WHERE s_start.app_rts::date = current_date - in_date
AND (s_start.app_uid,s_start.app_key) NOT IN(SELECT app_uid,app_key
FROM datemp.maj_usr_mstr)
)a;
return count_newusr;
end;
$BODY$
LANGUAGE plpgsql VOLATILE;
The below function throws an error like ,
ERROR: operator does not exist: date - text
LINE 1: ..._start WHERE s_start.app_rts::date = current_date - $1 AND...
in_date must be integer
current_date - in_date::integer
Or just pass it as integer
func_maj_get_new_user(in_date integer)