PostgreSQL - Function with conditional local variable - sql

I want to create a PostgreSQL function that will filter out table based on selected parameters.
However, I also need to perform some more complex logic based on the argument supplied. So I wanted to declare a local variable which would be based on some conditional logic.
What I have:
CREATE OR REPLACE FUNCTION get_something(parameter1 INT, parameter2 VARCHAR[])
DECLARE
conditional_variable := (
IF $1 = 50 THEN
'result-1'
ELSIF $1 = 100 THEN
ARRAY['result-2', 'result-3']
END IF;
)
RETURNS TABLE (
"time" BIGINT,
some_column NUMERIC
) AS $$
SELECT
time,
some_column
FROM "SomeNiceTable"
WHERE time = $1
AND some_dimension = ANY($2::VARCHAR[])
AND some_other_dimension = ANY(conditional_variable::VARCHAR[]);
$$ LANGUAGE SQL;
But it does not work this way. Is there a way how to achieve such thing?

You can not have DECLARE block and variables in a language sql function.
So you need to switch to language plpgsql and adjust the structure to be valid PL/pgSQL
CREATE OR REPLACE FUNCTION get_something(parameter1 INT, parameter2 VARCHAR[])
RETURNS TABLE ("time" BIGINT, some_column NUMERIC)
AS
$$
declare
conditional_variable text[];
begin
IF parameter1 = 50 THEN
conditional_variable := array['result-1'];
ELSIF parameter1 = 100 THEN
conditional_variable := ARRAY['result-2', 'result-3']
ELSE
????
END IF;
return query
SELECT
time,
some_column
FROM "SomeNiceTable"
WHERE time = $1
AND some_dimension = ANY($2::VARCHAR[])
AND some_other_dimension = ANY(conditional_variable);
END;
$$
LANGUAGE plpgsql;

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;
$$

Postgres function to return Table into variables

How can I capture different columns into different variables like so (note this is only pseudocode so I am assuming it will cause errors. Example taken from here)
create or replace function get_film (
p_pattern varchar
)
returns table (
film_title varchar,
film_release_year int
)
language plpgsql
as $$
begin
return query
select
title,
release_year::integer
from
film
where
title ilike p_pattern;
end;$$
create or replace function get_film_into_variables (
p_pattern varchar
)
returns null
language plpgsql
as $$
declare
v_title varchar,
v_release_year integer
begin
SELECT
get_film (p_pattern)
INTO
v_title,
v_release_year;
end;$$
Assuming you have some purpose for the variables after retrieving them not just ending the function your "get_film_into_variables" is almost there. But first let's backup just a bit. A function that returns a table does just that, you can use the results just like a table stored on disk (it just goes away after query or calling function ends). To that end only a slight change to the "get_film_into_variables" function is required. The "get_film" becomes the object of the FROM clause. Also change the returns null, to returns void. So
create or replace function get_film_into_variables (
p_pattern varchar
)
returns void
language plpgsql
as $$
declare
v_title varchar;
v_release_year integer;
begin
select *
from get_film (p_pattern)
INTO
v_title,
v_release_year;
end;
$$;
The above works for a single row returned by a function returning table. However for a return of multiple rows you process the results of the table returning function just lake you would an actual table - with a cursor.
create or replace
function get_film_into_variables2(p_pattern varchar)
returns void
language plpgsql
as $$
declare
k_message_template constant text = 'The film "%s" was released in %s.';
v_title varchar;
v_release_year integer;
v_film_message varchar;
c_film cursor (c_pattern varchar) for
select * from get_film (c_pattern);
begin
open c_film (p_pattern);
loop
fetch c_film
into v_title
, v_release_year;
exit when not found;
v_film_message = format( k_message_template,v_title,v_release_year::text);
raise notice using
message = v_film_message;
end loop;
end;
$$;
BTW: the get_film function can be turned into a SQL function. See fiddle here. For demo purposes get_film_into_variable routines return a message.

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;

Insert using a function that returns two values per row

This function:
CREATE OR REPLACE FUNCTION fn_test1()
RETURNS SETOF date AS
$BODY$
declare
i int;
begin
i:=0;
while i<5 loop
return next '2001-01-02'::date;
i:=i+1;
end loop;
end
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 1000;
This table:
CREATE TABLE teste1
(
teste1_id serial NOT NULL,
num integer,
fn_date date)
An INSERT like this works just fine (inserting 5 rows):
Insert into teste1(num,fn_date)
select 1, fn_test1();
But if I want to have a function that returns two dates in a row, and a table that has 2 columns of dates how should I do that? I've made this so far:
CREATE OR REPLACE FUNCTION fn_test2()
RETURNS TABLE(a date, b date) AS
$BODY$
declare
_start_date date;
_end_date date;
begin
_start_date:='2001-01-01'::date;
_end_date:='2002-01-01'::date;
i:=0;
while i < 5 loop
return query(select _start_date,_end_date);
i:=i+1;
end loop;
end
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 1000;
And this table:
CREATE TABLE teste2
(
teste2_id serial NOT NULL,
num integer,
start_date date,
end_date date)
Now, I can't do this:
INSERT INTO teste2(num,start_date,end_date)
SELECT 1, fn_test2();
I've made the function return setof mytype (creating a type with two dates) but it seems to do the same thing.
How should I modify the INSERT query or the function to make this work?
To access fields of a (well known) composite type, you need to wrap the identifier in parentheses. Without parenthesis the identifier before the dot would be taken to be a table name per SQL syntax rules. This would work:
SELECT 1, (fn_test2()).*
BTW, your dummy function could be simpler:
CREATE OR REPLACE FUNCTION fn_test2()
RETURNS TABLE(a date, b date) AS
$func$
BEGIN
a := '2001-01-01'::date;
b := '2002-01-01'::date;
FOR i in 0 .. 4 LOOP
RETURN NEXT;
END LOOP;
END
$func$ LANGUAGE plpgsql;
Or use this simple SQL statement with generate_series() to the same effect:
SELECT 1, '2001-01-01'::date AS a, '2002-01-01'::date AS b
FROM generate_series(0,4);
Try using:
INSERT INTO teste2(num,start_date,end_date)
SELECT 1, f.a, f.b FROM fn_test2() AS f;
since you've declared a and b as columns of the table being returned.

cannot pass dynamic query to sql-function

I cannot seem to find a way to pass my query as a parameter to my sql-function. My problem is table 'my_employees1' could be dynamic.
DROP FUNCTION function_test(text);
CREATE OR REPLACE FUNCTION function_test(text) RETURNS bigint AS '
DECLARE ret bigint;
BEGIN
SELECT count(mt.id) INTO ret
FROM mytable as mt
WHERE mt.location_id = 29671
--and mt.employee_id in (SELECT id from my_employees1);
--and mt.employee_id in ($1);
$1;
RETURN ret;
END;
' LANGUAGE plpgsql;
select function_test('and mt.employee_id in (SELECT id from my_employees1)');
select function_test('SELECT id from my_employees1');
It must be dynamically built:
DROP FUNCTION function_test(text);
CREATE OR REPLACE FUNCTION function_test(text) RETURNS bigint AS $$
DECLARE
ret bigint;
BEGIN
execute(format($q$
SELECT count(mt.id) INTO ret
FROM mytable as mt
WHERE mt.location_id = 29671
%s; $q$, $1)
);
RETURN ret;
END;
$$ LANGUAGE plpgsql;
The $$ and $q$ are dollar quotes. They can be nested as long as the inner identifier is different. In addition to the obvious advantages of permitting the use of unquoted quotes and being nestable it also let the syntax highlighting do its work.