Find Min Or Max in custom aggregate function postgresql - sql

I'm new to Postgresql (I'm programming in PL/pgSQL, there's no much difference with sql).
I wrote my custome aggregate function, which has to find the min value, or max in an array of numeric.
This is the function aggregate code
CREATE OR REPLACE FUNCTION searchMaxValue (numeric[]) RETURNS numeric AS $$
DECLARE
i numeric;
maxVal numeric;
BEGIN
maxVal = $1[1];
IF ARRAY_LENGHT($1,1) > 0 THEN --Checking whether the array is empty or not
<<confrontoMassimo>>
FOREACH i IN ARRAY $1 LOOP --Looping through the entire array, passed as parameter
IF maxVal <= $1[i] THEN
maxVal := $1[i];
END IF;
END LOOP confrontoMassimo;
ELSE
RAISE NOTICE 'Invalid parameter % passed to the aggregate function',$1;
--Raising exception if the parameter passed as argument points to null.
RAISE EXCEPTION 'Cannot find Max value. Parameter % is null', $1
USING HINT = 'You cannot pass a null array! Check the passed parameter';
END IF;
RETURN maxVal;
END;
$$ LANGUAGE plpgsql;
CREATE AGGREGATE searchMaxValueArray (numeric)
(
sfunc = array_append,
stype = numeric[],
finalfunc = searchMaxValue,
initCond = '{}'
);
The problem is, that it doesn't work as expected. What is the problem?

As mentioned above, there's a small typo in your function; ARRAY_LENGHT should be ARRAY_LENGTH.
Aside from that, the only issue I can see is here:
FOREACH i IN ARRAY $1 LOOP
IF maxVal <= $1[i] THEN
...
In a FOREACH loop, the target variable i isn't the array index, it's the array element itself. In other words, the loop should be:
FOREACH i IN ARRAY $1 LOOP
IF maxVal <= i THEN
maxVal := i;
END IF;
END LOOP
With those changes, it seems to work as expected: https://rextester.com/FTWB14034

Related

ORACLE: Missing IN or OUT parameter at index:: 1

Can someone tell me what's wrong in my code. I need to create function that displays the number of digits given a number but I keep getting missing in and out parameter. Im am using Oracle SQL. Thank you
SET SERVEROUTPUT ON;
CREATE OR REPLACE FUNCTION Digit (n1 IN OUT INTEGER) RETURN INTEGER IS
Counter INTEGER := 0;
BEGIN
WHILE (n1 != 0 ) LOOP
n1 := n1 /10;
Counter := Counter + 1;
END LOOP;
RETURN Counter;
END;
Test block:
DECLARE
n1 INTEGER := 0;
BEGIN:
n1 := &n1;
DBMS_OUTPUT.PUT_LINE('The number of digit = ' ||Digit(Counter));
END;
The error is probably because of the stray : character after begin in your test block.
I would write it like this:
create or replace function digits
( p_num integer )
return integer
as
pragma udf;
i simple_integer := p_num;
l_digits simple_integer := 0;
begin
while i <> 0 loop
i := i / 10;
l_digits := l_digits + 1;
end loop;
return l_digits;
end digits;
I made the parameter in only, instead of in out. This means you can use it in SQL queries, and also in PL/SQL code without needing to pass in a variable whose value will get changed to 0 by the function.
pragma pdf tells the compiler to optimise the function for use in SQL.
I used simple_integer as in theory it's slightly more efficient for arithmetic operations, although I doubt any improvement is measurable in the real world (and I'm rather trusting the optimising compiler to cast my literal 10 as a simple_integer, as otherwise the overhead of type conversion will defeat any arithmetic efficiency).

Improve aggregate function in PL/pgSQL

I've tried to create an aggregate function, which finds the minimum value in a column, then it adds the Laplacian noise.
I am using Postregres PL/pgSQL language.
The aggregate works perfectly, but I'd like to know if there is any way to improve the code that I wrote.
/*
* PLpgSQL function which behaves to aggregate the MIN(col) function then adds the laplacian noise.
* For the sensivity (which is the upper bound of the query), We use the halfed maximum value of the column called.
* Passing the array which contains the entire column values, that will be compared, to establish which one is the minimum.
* Then we compute Laplacian distribution (sensivity/epsilon). This given value is added to the minimum Value that will disturb
* the final result
*/
CREATE OR REPLACE FUNCTION addLaplacianNoiseMin (real[]) RETURNS real AS $$
DECLARE
i real;
minVal real; --minimum value which is found in the column and then disturbed
laplaceNoise real; --laplacian distribution which is computed finding the halfed maximum value, divided by an arbitrary epsilon (small value)
epsilon real := 1.2;
sensivity real; --our computed upper bound
maxVal real;
BEGIN
minVal := $1[1];
maxVal := $1[1];
IF ARRAY_LENGTH($1,1) > 0 THEN --Checking whether the array is empty or not
<<confrontoMinimo>>
FOREACH i IN ARRAY $1 LOOP --Looping through the entire array, passed as parameter
IF minVal >= i THEN
minVal := i;
ELSE
maxVal := i;
END IF;
END LOOP confrontoMinimo;
ELSE
RAISE NOTICE 'Invalid parameter % passed to the aggregate function',$1;
--Raising exception if the parameter passed as argument points to null.
RAISE EXCEPTION 'Cannot find MIN value. Parameter % is null', $1
USING HINT = 'You cannot pass a null array! Check the passed parameter';
END IF;
sensivity := maxVal/2;
laplaceNoise := sensivity/(epsilon);
RAISE NOTICE 'minVal: %, maxVal: %, sensivity: %, laplaceNoise: %', minVal, maxVal, sensivity,laplaceNoise;
minVal := laplaceNoise + minVal;
RETURN minVal;
END;
$$ LANGUAGE plpgsql;
CREATE AGGREGATE searchMinValueArray (real)
(
sfunc = array_append,
stype = real[],
finalfunc = addLaplacianNoiseMin,
initCond = '{}'
);
Yes, you can improve that by not using an array as state for the aggregate, but a composite type like:
CREATE TYPE aggstate AS (minval real, maxval real);
Then you can perform the operation from your loop in the SFUNC and don't have to keep an array in memory that can possibly become very large. The FINALFUNC would then become very simple.

Simple word replacement in output from SQL

I'm running PostgreSQL 9.4.
Is there a replace string function which can take an array of words, or other similar function?
Ex.
SELECT REPLACE(my_column, ['blue', 'red'], ['ColorBlue', 'ColorRed']);
So blue becomes ColorBlue, and red becomes ColorRed?
It's not only such simple replacements, but for the example I'm using this.
One way is create it:
create or replace function rep_arr(str text, src text[], rep text[])
returns text as $$
begin
for i in 1..array_length(src, 1) loop
str := replace(str, src[i], rep[i]);
end loop;
return str;
end; $$ language plpgsql
Call:
select rep_arr('bla bla blue bla red bla', '{blue,red}' , '{ColorBlue,ColorRed}');
I agree with #OtoShavadze that you can write your own function.
Here is my solution:
I use generate_subscripts(array anyarray, dim int) function as suggested in Searching in Arrays documentation.
CREATE OR REPLACE FUNCTION translate(string text, from_array text[], to_array text[])
RETURNS text AS
$BODY$
DECLARE
output text;
BEGIN
SELECT INTO output
to_array[idx]
FROM
generate_subscripts(from_array, 1) AS idx
WHERE
from_array[idx] = string; -- here you can change the search condition
IF FOUND THEN
RETURN output;
ELSE
RETURN string;
END IF;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
It finds and replaces whole words but you can change it (line marked in the code) to find only a substring, match case-insensitive, etc...
You should also add parameter checking: arrays should not be null, multidimensional nor differ in size:
IF from_array IS NULL OR to_array IS NULL THEN
RAISE EXCEPTION 'NULL parameters';
END IF;
IF array_ndims(from_array) != 1 OR array_ndims(to_array) != 1 THEN
RAISE EXCEPTION 'Multidimensional parameters';
END IF;
IF array_length(from_array, 1) != array_length(to_array, 1) THEN
RAISE EXCEPTION 'Parameters size differ';
END IF;
SELECT translate('red', ARRAY['blue', 'red'], ARRAY['ColorBlue', 'ColorRed']);
returns
ColorRed

How get all matching positions in a string?

I have a column flag_acumu in a table in PostgreSQL with values like:
'SSNSSNNNNNNNNNNNNNNNNNNNNNNNNNNNNSNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN'
I need to show all positions with an 'S'. With this code, I only get the first such position, but not the later ones.
SELECT codn_conce, flag_acumu, position('S' IN flag_acumu) AS the_pos
FROM dh12
WHERE position('S' IN flag_acumu) != 0
ORDER BY the_pos ASC;
How to get all of them?
In Postgres 9.4 or later you can conveniently use unnest() in combination with WITH ORDINALITY:
SELECT *
FROM dh12 d
JOIN unnest(string_to_array(d.flag_acumu, NULL))
WITH ORDINALITY u(elem, the_pos) ON u.elem = 'S'
WHERE d.flag_acumu LIKE '%S%' -- optional, see below
ORDER BY d.codn_conce, u.the_pos;
This returns one row per match.
WHERE d.flag_acumu LIKE '%S%' is optional to quickly eliminate source rows without any matches. Pays if there are more than a few such rows.
Detailed explanation and alternatives for older versions:
PostgreSQL unnest() with element number
Since you didn't specify your needs to a point in which one could answer properly, I'm going with my assumption that you want a list of positions of occurence of a substring (can be more than 1 character long).
Here's the function to do that using:
FOR .. LOOP control structure,
function substr(text, int, int).
CREATE OR REPLACE FUNCTION get_all_positions_of_substring(text, text)
RETURNS text
STABLE
STRICT
LANGUAGE plpgsql
AS $$
DECLARE
output_text TEXT := '';
BEGIN
FOR i IN 1..length($1)
LOOP
IF substr($1, i, length($2)) = $2 THEN
output_text := CONCAT(output_text, ';', i);
END IF;
END LOOP;
-- Remove first semicolon
output_text := substr(output_text, 2, length(output_text));
RETURN output_text;
END;
$$;
Sample call and output
postgres=# select * from get_all_positions_of_substring('soklesocmxsoso','so');
get_all_positions_of_substring
--------------------------------
1;6;11;13
This works too. And a bit faster I think.
create or replace function findAllposition(_pat varchar, _tar varchar)
returns int[] as
$body$
declare _poslist int[]; _pos int;
begin
_pos := position(_pat in _tar);
while (_pos>0)
loop
if array_length(_poslist,1) is null then
_poslist := _poslist || (_pos);
else
_poslist := _poslist || (_pos + _poslist[array_length(_poslist,1)] + 1);
end if;
_tar := substr(_tar, _pos + 1, length(_tar));
_pos := position(_pat in _tar);
end loop;
return _poslist;
end;
$body$
language plpgsql;
Will return a position list which is an int array.
{position1, position2, position3, etc.}

Incrementing a number in a loop in plpgsql

I couldn't find this immediately from the examples. I want to increment a variable in a loop, in a function.
For instance:
DECLARE
iterator float4;
BEGIN
iterator = 1;
while iterator < 999
.....
iterator ++;
END;
How would this be done?
I was looking at this document about flow control:
http://www.postgresql.org/docs/8.4/static/plpgsql-control-structures.html
And none of them seem to be relevant for me, unless these are absolutely the only ways to simulate incrementing a variable.
To increment a variable in plpgsql:
iterator := iterator + 1;
There is no ++ operator.
About the assignment operator in plpgsql:
The forgotten assignment operator "=" and the commonplace ":="
Correct syntax for loops in PL/pgSQL in the manual.
Your code fragment would work like this:
DECLARE
iterator float4 := 1; -- we can init at declaration time
BEGIN
WHILE iterator < 999
LOOP
iterator := iterator + 1;
-- do stuff
END LOOP;
END;
Simpler, faster alternative with a FOR loop:
FOR i in 1 .. 999 -- i is integer automatically, not float4
LOOP
-- do stuff
END LOOP;
The manual:
The variable name is automatically defined as type integer and exists only inside the loop (any existing definition of the variable name is ignored within the loop).
For a sscce
DO $$
DECLARE
counter INTEGER := 0 ;
BEGIN
WHILE counter <= 5 LOOP
counter := counter + 1 ;
RAISE NOTICE 'Counter: %', counter;
END LOOP ;
END; $$
if you want to avoid declare variable (more concise)
DO $$
BEGIN
FOR counter IN 1..5 LOOP
RAISE NOTICE 'Counter: %', counter;
END LOOP;
END; $$
credits