Find SUM and MAX in custom aggregate function - sql

I've been working recently with custom aggregate function.
In this custom aggregate, the first function, doesn't compute the sum and the max value correctly.
I'm using a composite type to return the sum and the max value.
I've tried with appending everything with the array, but it's not an efficient way to work
CREATE TYPE sum_max_complex AS (sum real, max_v real);
CREATE OR REPLACE FUNCTION calculateSum(sum_max_complex, real) RETURNS sum_max_complex AS $$
DECLARE
sumValue real := 0;
max_v real := $2;
output sum_max_complex;
BEGIN
RAISE NOTICE '-------------------';
RAISE NOTICE 'IL PRIMO VALORE DI INPUT E: % ... %',$1.sum,$1.max_v;
RAISE NOTICE 'IL SECONDO VALORE DI INPUT E: %',$2;
IF $2 IS NOT NULL THEN
sumValue := calculateSumAggregate(sumValue,$2) + sumValue;
ELSE
sumValue := sumValue;
END IF;
max_v := searchmaximumvalue(max_v,$2);
output.sum := sumValue;
output.max_v := max_v;
RAISE NOTICE '-------------------';
RAISE NOTICE 'IL VALORE DI OUTPUT SONO: % ... %',output.sum,output.max_v;
RETURN output;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION addLaplacianNoiseSum(sum_max_complex) RETURNS real AS $$
DECLARE
epsilon real := 0.005;
sensivity real := $1.max_v;
laplaceDistribution real;
BEGIN
laplaceDistribution := generaterandomvalues(sensivity / (epsilon));
RETURN $1.sum + laplaceDistribution;
END;
$$ LANGUAGE plpgsql;
CREATE AGGREGATE SUM_LAPLACE(real)
(
SFUNC = calculateSum,
STYPE = sum_max_complex,
FINALFUNC = addLaplacianNoiseSum
);
In my table column, I have as values: 19,22,22.5,27.
It takes the correct value in the $2 parameter method, in the 1st function, but doesn't accumulate and sum every value.

It doesn't look like you are ever adding to the values stored in the sum_max_complex type. Here's a simplified example that shows approximately what you should do. I don't know what calculateSumAggregate or generaterandomvalues do, so I wasn't able to reproduce those.
CREATE TYPE sum_max_complex AS (sum real, max_v real);
CREATE OR REPLACE FUNCTION calculateSum(sum_max_complex, real) RETURNS sum_max_complex AS $$
select ROW(
$1.sum + coalesce($2, 0),
greatest($1.max_v, $2)
)::sum_max_complex;
$$ LANGUAGE SQL IMMUTABLE;
CREATE OR REPLACE FUNCTION addLaplacianNoiseSum(sum_max_complex) RETURNS real AS $$
select $1.sum + ($1.max_v/0.005);
$$ LANGUAGE SQL IMMUTABLE;
CREATE AGGREGATE SUM_LAPLACE(real)
(
SFUNC = calculateSum,
STYPE = sum_max_complex,
FINALFUNC = addLaplacianNoiseSum,
INITCOND = '(0, 0)'
);
with a as (select a from (values (19), (22), (22.5), (27)) v(a))
select sum_laplace(a) from a;
sum_laplace
-------------
5490.5

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.

PostgreSQL 11 - using format to assign to variable

I have been awake for well beyond my schedule and I have been stuck with this issue for a long time, I don't even know what I am looking for to solve, but I wish to use format to insert values that I'll be using for column names, and then executing it... but it keeps giving me errors no matter how much I try changing it :c
Heres the part that im trying to do something that doesnt work, but i think you get the idea what im trying to achieve
ratelimit := EXECUTE format('(SELECT %I
FROM users.ratelimits
WHERE user_id = $2)
', $1);
and heres the full code for the brave
CREATE OR REPLACE FUNCTION users.consume_ratelimit(_name text,__user_id integer)
RETURNS boolean
LANGUAGE 'plpgsql'
VOLATILE
PARALLEL UNSAFE
COST 100
AS $BODY$DECLARE
ratelimit INTEGER;
reset_timeout timestamptz;
premium BOOLEAN;
BEGIN
ratelimit := EXECUTE format('(SELECT %I
FROM users.ratelimits
WHERE user_id = $2)
', $1);
reset_timeout := EXECUTE format('(SELECT %I_refresh
FROM users.ratelimits
WHERE user_id = $2)
', $1);
premium := (SELECT users.is_premium($2));
IF premium THEN
RETURN TRUE;
ELSIF reset_timeout <= NOW() THEN
UPDATE users.ratelimits
SET image_refresh = NOW() + '1 hour'::interval,
image = DEFAULT
WHERE user_id = $2;
RAISE NOTICE 'reset';
RETURN TRUE;
ELSE
IF ratelimit > 0 THEN
EXECUTE format('UPDATE users.ratelimits
SET %I = %I - 1
WHERE user_id = $2', $1, $1);
RAISE NOTICE 'decrement';
RETURN TRUE;
ELSIF ratelimit <= 0 THEN
RAISE NOTICE 'out of credits';
RETURN FALSE;
ELSE
EXECUTE format('INSERT INTO users.ratelimits(user_id) VALUES ($2)
ON CONFLICT DO UPDATE SET
%I = excluded.%I,
%I_refresh = excluded.%I_refresh', $1, $1, $1, $1);
RAISE NOTICE 'create';
RETURN TRUE;
END IF;
END IF;
END;$BODY$;
As documented in the manual you need to use into together with EXECUTE to store the result into a variable. This can handle multiple columns/variables as well, so you only need a single EXECUTE to get both values.
For clarity you should reference parameters by name, not by position.
EXECUTE format('SELECT %I, %I_refresh
FROM users.ratelimits WHERE user_id = $1'),
_name, _name)
USING __user_id
INTO ratelimit, reset_timeout;
Note the $1 inside the string for format() is a parameter placeholder used when the SQL statement is executed, and will be replaced with the value of the variable specified in the USING clause.
Variable assignment is also more efficient without a SELECT:
premium := users.is_premium(__user_id);
It seems some_var := EXECUTE ... will not work. Additionally, params inside of an EXECUTE statement are not the same as those in the function - you need to supply them to the execute statement.
I have not tested this, but perhaps
-- q declared above as text
q := format('
SELECT %I
FROM users.ratelimits
WHERE user_id = %s;
', $1, $2);
EXECUTE q INTO ratelimit;
will work? I also removed the parens from the query, those are unnecessary and may the problem themselves.
I have tested the function
CREATE OR REPLACE FUNCTION test_sum (a int, b int)
RETURNS int
AS $$
DECLARE
q text;
result int;
BEGIN
q := FORMAT ('SELECT %s + %s', $1, $2);
EXECUTE q INTO result;
RETURN result;
END
$$ LANGUAGE plpgsql
and that does work. If my suggestion above does not work, perhaps you can use the above as a starting point.

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.

Returning multiple values from an Oracle 12c function

I am writing a function in Oracle 12c (and it has to be a function) that should return 3 values, order_line.o_id, a variable total that I create in the function, and a discounted_amount variable that I create in the function.
I have gotten my function to create the discounted_amount variable and return it but I am not sure how to get it to return these other two values.
CREATE OR replace FUNCTION DiscountsReport (input_o_id IN REAL)
RETURN REAL
IS
output_o_id NUMBER;
total REAL;
discounted_total REAL;
percent REAL;
BEGIN
output_o_id := input_o_id;
SELECT SUM(o.ol_quantity * o.ol_price)
INTO total
FROM order_line o
WHERE o.o_id = input_o_id;
IF total > 100 THEN
percent := 0.1;
ELSIF total > 200 THEN
percent := 0.2;
ELSE
percent := 0.0;
END IF;
discounted_total := ( total * ( 1 - percent ) );
RETURN discounted_total;
END;
Create a new type:
CREATE OR REPLACE TYPE new_type AS OBJECT(v1 type1, v2 type2, v3 type3);
and use it after RETURN (call the result output - its type is new_type).
You can access those values using:
output.v1
output.v2
output.v3

Calling postgres function with multiple params

This is my sql function in postgresql:
FUNCTION test(year integer)
RETURNS SETOF json AS
$BODY$
SELECT ARRAY_TO_JSON(ARRAY_AGG(T))
FROM table t
WHERE year = $1;
$BODY$
This works quite good. But now I want specify more parameters and
I'd like to get a return with the condition if parameters are set. For example following function call:
test(year := 2014, location := 'Belo Horizonte')
How should the function look like and where to set conditions? Here is my (wrong) suggestion:
FUNCTION test(year integer, location text)
RETURNS SETOF json AS
$BODY$
SELECT ARRAY_TO_JSON(ARRAY_AGG(T))
FROM table t
IF $1 IS SET THEN
WHERE year = $1
ELSIF $2 THEN
UNION
WHERE location = $2
END IF;
$BODY$
A further challenge is a return of the function for this statements:
test(year := 1584)
-- should return all entries with year 1584
test(location := 'Cambridge')
-- should return all entries with location Cambridge
test(year := 1584, location := 'Cambridge')
-- should return all entries with year 2014 AND location Belo Horizonte
Thanks in advance!
You may try to do something like that, adding default values, and working with OR clauses
FUNCTION test(year integer DEFAULT -1, location text DEFAULT 'noLocation')
RETURNS SETOF json AS
$BODY$
SELECT ARRAY_TO_JSON(ARRAY_AGG(T))
FROM table t
WHERE ($1 = -1 OR year = $1)
AND ($2 = 'noLocation' OR location = $2);
$BODY$