Send whole column to the function and check row by row - sql

I want send whole columns to my function! Something like function min() or max(), is it possible?
How to check query results row by row? I wrote something like:
CREATE OR REPLACE FUNCTION gowno.kiki(temppp INTEGER ) RETURNS INTEGER AS
$$
DECLARE
val INTEGER := 0;
i tyczka%ROWTYPE;
BEGIN
FOR i IN (SELECT adres FROM tyczka)
LOOP
RETURN CAST(i.adres AS INTEGER);
IF CAST(i.adres AS INTEGER) > val THEN
val = CAST(i.adres_ AS INTEGER);
END IF;
END LOOP;
RETURN val;
END
$$
LANGUAGE 'plpgsql'
For example, I have something like the following table. And I want to calculate the differences between the field in column poziom_wody where id is the same.

The syntax of your function would work like this:
CREATE OR REPLACE FUNCTION foo(temppp int)
RETURNS INTEGER AS
$func$
DECLARE
val int := 0;
i tyczka;
BEGIN
FOR i IN
SELECT * FROM tyczka
LOOP
RETURN i.adres::int;
IF i.adres::int > val THEN
val := i.adres::int;
END IF;
END LOOP;
RETURN val;
END
$func$ LANGUAGE plpgsql;
Depending on your table definition this could be further simplified.
Depending on what you want to achieve exactly, there is probably a superior set-based approach. Looping is typically a measure of last resort in a relational database.
There are occasions where it's the best one, but people coming from procedural languages tend to over-use loops.
Added example
To get the difference between the maximum and minimum poziom_wody with the same id_rzeki:
SELECT max(poziom_wody) - min(poziom_wody) AS diff
FROM tbl
WHERE id_rzeki = 2;
This works for any number of rows.

Related

Break update operation when function returns NULL value in PostgreSQL

Let' assume I have a table named mytable:
I have one function which returns text and sometime it can return NULL also. ( this is just demo function in real use case function is complex )
CREATE OR REPLACE FUNCTION parag_test (id text)
RETURNS text
LANGUAGE plpgsql
AS
$$
DECLARE
--- variables
BEGIN
IF(id= 'Ram') THEN
RETURN 'shyam';
ELSE
RETURN NULL;
END IF;
END
$$
I want to update mytable till the time when my function returns non NULL values. if it returns NULL value I want to break update operation that time.
if we use below update query it will not stops updating when function returns NULL
update mytable SET id = parag_test (id) ;
Table after triggering above query looks like :
But what my expectation of output is :
because when we try to update second row function parag_test will return NULL and I want to stop update operation there.
So is there any way in PostgreSQL for achieving that ?
If you do have a primary key (say row number) to your table, this approach could work. - https://dbfiddle.uk/?rdbms=postgres_14&fiddle=0350562961be16333f54ebbe0eb5d5cb
CREATE OR REPLACE FUNCTION parag_test()
RETURNS void
LANGUAGE plpgsql
AS
$$
DECLARE
a int;
i varchar;
BEGIN
FOR a, i IN SELECT row_num, id FROM yourtable order by row_num asc
LOOP
IF(i = 'Ram') THEN
UPDATE yourtable set id = 'shyam' where row_num = a;
END IF;
IF (i is null) then
EXIT;
END IF;
END LOOP;
END;
$$

Do multiple regex changes in one update with the help of plpgsql

I'm looking for some tips to get this PostgreSQL plpgsql function to work. I would like to do a couple of regex's on each record and update only one time as doing them one by one takes 15 min. ...
CREATE OR REPLACE FUNCTION clean_column() RETURNS void AS $$
DECLARE
r record;
reg text[] := array[
['search','replace'],
['search','replace'],
['search','replace'],
['search','replace']
];
var text[];
tmp text;
BEGIN
for r in
select column from mytable
loop -- loop over all records
tmp = r;
FOREACH var SLICE 1 IN ARRAY reg
LOOP -- loop over all changes
tmp = regexp_replace(tmp,var[1],var[2]);
END LOOP;
UPDATE mytable SET r = tmp;
end loop;
END
$$ LANGUAGE plpgsql;
... there is a problem with r as it is not assigned. Probably my lack of understanding of how plpgsql works.
Maybe there is an other way to do multiple changes on a record field?
Your function would update the row repeatedly, writing a new row version every time. That would still be hugely inefficient.
The point must be to update every row only once. And only if anything actually changes.
CREATE OR REPLACE FUNCTION clean_column(INOUT _text text)
LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE AS
$func$
DECLARE
_reg CONSTANT text[] := ARRAY[
['search','replace']
, ['search','replace']
, ['search','replace']];
_var text[];
BEGIN
FOREACH _var SLICE 1 IN ARRAY _reg
LOOP
_text := regexp_replace(_text, _var[1], _var[2]);
END LOOP;
END
$func$;
The function does not run the UPDATE itself, just the string processing. Use that function in your UPDATE like so:
UPDATE tbl
SET col = clean_column(col)
WHERE col IS DISTINCT FROM clean_column(col) -- ① !
AND col IS NOT NULL -- ② ?
① Skip updates that would not change anything.
② Skip rows with NULL early (without even evaluating the function). Only relevant if column can be NULL, of course.
Performance will differ by orders of magnitude.

PostgreSQL add each item to list - DB functions

I'd like to create a DB function that will accept a list of numbers and return a list of numbers. For each item in the list that was passed to the function, it should check some condition and add it to the response list. However, I don't think the way I am trying to do it is really a correct one. What I tried writing is basically some pseudo code here.
CREATE OR REPLACE FUNCTION map_numbers(numbers integer[])
returns integer[]
AS
$BODY$
DECLARE return_list integer[];
FOREACH field IN ARRAY numbers LOOP
CASE
WHEN field = 3 THEN -- add 43 (this was a random thought, but I am basically trying to map a few of the numbers to different values)
END
END LOOP;
RETURN QUERY SELECT * FROM return_list;
$BODY$
LANGUAGE sql VOLATILE
COST 100;
You need to put this into an IF statement. To append a value to an array use ||
CREATE OR REPLACE FUNCTION map_numbers(numbers integer[])
returns integer[]
AS
$BODY$
DECLARE
return_list integer[] := integer[];
BEGIN
FOREACH field IN ARRAY numbers LOOP
if field = 3 then
return_list := return_list || 43;
elsif field = 15 then
return_list := return_list || 42;
else
return_list := return_list || field;
end if;
END LOOP;
return return_list; --<< no SELECT required, just return the variable
END;
$BODY$
LANGUAGE plpgsql --<< you need PL/pgSQL, not SQL for the above
STABLE;
This can also be done using SQL rather than PL/pgSQL which usually is more efficient:
CREATE OR REPLACE FUNCTION map_numbers(numbers integer[])
returns integer[]
AS
$BODY$
select array_agg(field order by idx)
from (
select case
when field = 3 then 43
when field = 15 then 42
else field
end as field,
idx
from unnest(numbers) with ordinality as t(field, idx)
) x;
$BODY$
LANGUAGE sql
STABLE;

PostgreSQL - ERROR: query has no destination for result data

Why am I getting the following error?:
ERROR: query has no destination for result data
This is my function:
CREATE OR REPLACE FUNCTION public.SumASCII(
value character varying)
RETURNS int
LANGUAGE 'plpgsql'
COST 100.0
VOLATILE NOT LEAKPROOF
AS $function$
DECLARE finalResult int;
DECLARE tempChar character;
DECLARE valueLength int;
DECLARE tempResult int;
BEGIN
SELECT LENGTH(value) INTO valueLength;
SELECT finalResult = 0;
SELECT tempResult = 0;
DO
$do$
BEGIN
FOR i IN 1..valueLength LOOP
SELECT SUBSTRING(value, i, 1) INTO tempChar;
SELECT ASCII(tempChar) INTO tempResult;
SELECT finalResult += tempResult;
END LOOP;
END
$do$;
RETURN finalResult;
END;
$function$;
I've looked at other questions with the same error, but they don't seem to be related to my problem. I'm sure the answer is simple, but I just can't seem to see what the issue is here.. I'm declaring an int and I am returning an int..
Calling the function as follows:
SELECT SumASCII('abc')
I was able to get it working by simplifying a lot. I believe DECLARE just needs to be used once (examples at https://www.postgresql.org/docs/9.6/static/plpgsql-declarations.html), and I'm not sure what you were trying to do with the DO...END block but I just took it out.
CREATE OR REPLACE FUNCTION public.SumASCII(
value character varying)
RETURNS int
LANGUAGE 'plpgsql'
COST 100.0
VOLATILE NOT LEAKPROOF
AS $function$
DECLARE
finalResult int := 0;
BEGIN
FOR i IN 1..LENGTH(value) LOOP
finalResult := finalResult + ASCII(SUBSTRING(value, i, 1));
END LOOP;
RETURN finalResult;
END;
$function$;
SELECT SumASCII('abc') is returning 294
The Fortran style is style in PLpgSQL is bad style - any expression is SELECT. More embedded SELECTs, more slow. What can be done by simple one SQL should be done by simple SQL (it is not true in 100% cases (can depends on individual expressions) - for example #mike.k code is 3x faster than my: there is only one simple expression in cycle, my code has one generic query and very slow regexpr function):
CREATE OR REPLACE FUNCTION public.sumascii(varchar)
RETURNS bigint AS $$
SELECT sum(ascii(c)) FROM regexp_split_to_table($1,'') g(c);
$$ LANGUAGE SQL IMMUTABLE;
The identifier in SQL (and PostgreSQL too) are not case sensitive, so is not good to use camel notation.
When the result of function is immutable in time (for given argument), then the function should be marked as IMMUTABLE.

Write a PL/pgSQL function so that FOUND is not set when "nothing" is found?

I am just starting out on functions in PostgreSQL, and this is probably pretty basic, but how is this done?
I would like to be able to use the following in a function:
PERFORM id_exists();
IF FOUND THEN
-- Do something
END IF;
where the id_exists() function (to be used with SELECT and PERFORM) is:
CREATE OR REPLACE FUNCTION id_exists() RETURNS int AS $$
DECLARE
my_id int;
BEGIN
SELECT id INTO my_id
FROM tablename LIMIT 1;
RETURN my_id;
END;
$$ LANGUAGE plpgsql;
Currently, even when my_id does not exist in the table, FOUND is true, presumably because a row is still being returned (a null integer)? How can this be re-written so that an integer is returned if found, otherwise nothing at all is?
Your assumption is correct, FOUND is set to TRUE if the last statement returned a row, regardless of the value (may be NULL in your case). Details in the manual here.
Rewrite to, for instance:
IF id_exists() IS NOT NULL THEN
-- Do something
END IF;
Or rewrite the return value of your function with SETOF so it can return multiple rows - or no row! Use RETURN QUERY like I demonstrate. You can use this function in your original setting.
CREATE OR REPLACE FUNCTION id_exists()
RETURNS SETOF int LANGUAGE plpgsql AS
$BODY$
BEGIN
RETURN QUERY
SELECT id
FROM tablename
LIMIT 1;
END;
$BODY$;
Or, even simpler with a language SQL function:
CREATE OR REPLACE FUNCTION id_exists()
RETURNS SETOF int LANGUAGE sql AS
$BODY$
SELECT id
FROM tablename
LIMIT 1;
$BODY$;