Syntax error when calling one function from another - postgresql

I am trying to create a function which calls 2 other functions.
Below is the calling function's code from where I am trying to call 2 another functions, schema1.func1() and schema1.func2().
But it is throwing error at the line SELECT schema1.func1(temp_val); saying:
syntax error at or near "SELECT".
I tried to figure out the correct syntax but couldn't resolve.
I am using Postgres version 1.14.3
DECLARE
temp_val int;
cursor1 CURSOR
FOR
SELECT col1 from schema1.table1;
BEGIN
OPEN cursor1;
LOOP
FETCH cursor1 INTO temp_val;
EXIT WHEN NOT FOUND;
SELECT CASE
WHEN NOT EXISTS (SELECT col2 FROM schema1.table2 WHERE col2 = temp_val)
THEN
BEGIN
SELECT schema1.func1(temp_val);
SELECT schema1.func2(temp_val);
END;
END CASE;
END LOOP;
CLOSE cursor1;
END;

There is a ; missing at:
SELECT CASE WHEN NOT EXISTS (SELECT col2 FROM schema1.table2 WHERE col2 = temp_val)
so..
SELECT CASE WHEN NOT EXISTS (SELECT col2 FROM schema1.table2 WHERE col2 = temp_val);

You can't mix PL/pgSQL BEGIN ... END blocks with SQL statement - even if that SQL statement is part of a PL/pgSQL function. So the BEGIN inside the THEN part of the CASE expression is invalid. A SQL CASE expression ends with just an END. A PL/pgSQL CASE statement would end with END CASE. As you are trying to use a CASE expression, it would require just END, not end case.
You also need to use perform in order to call a function where you want to discard the result.
It's not clear to me what exactly you want to achieve. If you only want to call those two functions if a row does not exist in table2 then you can do this with a single loop:
DECLARE
t1_rec record;
BEGIN
FOR t1_rec IN select t1.col1
from table1 t1
where not exists (select *
from table2 t2
where t2.col2 = t1.col1)
LOOP
perform schema1.func1(t1_rec.col1);
perform schema1.func2(t1_rec.col1);
END LOOP;
END;

Related

How to get a function to work within a DO statement in Postgresql

I have a long and complex plpgsql function that creates a bunch of temporary tables nested within a while statement to get the optimal result. When the condition has been met I insert the result into an existing table, the function is far to long to post here but this is an example:
CREATE OR REPLACE FUNCTION public.test_function(id_input integer, val_input numeric)
RETURNS VOID AS
$BODY$
DECLARE
id_input numeric = $1;
val_input numeric = $2;
BEGIN
WHILE test_val < 0
LOOP
CREATE TEMP TABLE temp_table AS
SELECT a.existing_val - val_input AS new_val
FROM existing_table a
WHERE a.id = id_input;
test_val := (SELECT new_val FROM temp_table);
val_input := val_input + 1;
END LOOP;
INSERT INTO output_table (id, new_val)
SELECT a.id, a.new_val
FROM temp_table a;
END;
$BODY$
LANGUAGE plpgsql;
The function works if I call it like this SELECT test_function(1, 1000) However I would like run this function on a table with 60,000+ rows, like this:
SELECT test_function(a.id, a.val_input)
FROM data_table a;
It works when I use a subset of the data_table, say 1000 rows. However when I run it on the full table (60,000+ rows) I get the following error "AbortTransaction while in COMMIT state". After some reading I found out COMMITS, so in my case the inserts do not occur until the function has finished running which takes about 4 hours. So does anyone know what is going on?
As a workaround I tried nesting the function in a DO statement so the inserts are committed straight away:
DO
$do$
DECLARE
r data_table%rowtype;
BEGIN
FOR r IN
SELECT * FROM data_table
LOOP
SELECT public.test_function(r.id, r.val_input);
END LOOP;
END
$do$;
However then I get the following error "ERROR: query has no destination for result data", which I guess means I need to rewrite the function to use PERFORM instead of SELECT. However I have not had any luck with this as yet.
Any ideas?
Since you are not interested in the function result, you should use
PERFORM public.test_function(r.id, r.val_input);
instead of
SELECT public.test_function(r.id, r.val_input);
The latter syntax would only work if you add INTO some_variable as a destination for the query result.
Thank you all for your suggestions. I ended up using Jim Jones's suggestion and converting the function to a procedure which allowed me to use COMMIT after I did the INSERT. I also followed Jeremy's suggestion and moved from temp tables to CTE's. This solved the problem for me.

Execute select statement conditionally

I'm using PostgreSQL 9.6 and I need to create a query that performs a select depending on the logic of an if
Basically I've tried:
DO $$
BEGIN
IF exists ( SELECT 1 FROM TABLE WHERE A = B ) THEN
SELECT *
FROM A
ELSE
SELECT *
FROM B
END IF
END $$
And that returns me an error:
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM
instead.
CONTEXT: PL/pgSQL function inline_code_block line 15 at SQL statement
Then I switched "SELECT" for "PERFORM", but that don't actually execute the SELECT statement for me.
I read that I need to call a void function to perform a "dynamic" query, but I couldn't make that work either. I'm new to writing queries on PostgreSQL. Is there any better way of doing that?
DO statements do not take parameters nor return anything. See:
Returning values for Stored Procedures in PostgreSQL
You may want a function instead. Create once:
CREATE FUNCTION foo()
RETURNS SETOF A -- or B, all the same
LANGUAGE plpgsql AS
$func$
BEGIN
IF EXISTS (SELECT FROM ...) THEN -- some meaningful test
RETURN QUERY
SELECT *
FROM A;
ELSE
RETURN QUERY
SELECT *
FROM B;
END IF;
END
$func$
Call:
SELECT * FROM foo();
But the function has one declared return type. So both tables A and B must share the same columns (at least columns with compatible data types in the same order; names are no problem).
The same restriction applies to a plain SQL statement. SQL is strictly typed.
Anonymous code blocks just can't return anything - you would need a function instead.
But I think you don't need pl/pgsql to do what you want. Assuming that a and b have the same count of columns and datatypes, you can use union all and not exists:
select a.* from a where exists (select 1 from mytable where ...)
union all
select b.* from b where not exists (select 1 from mytable where ...)

"ERROR: query has no destination for result data" when returning SETOF

I'm having problems with this function:
The error I get is ERROR: query has no destination for result data.
What am I doing wrong?
CREATE OR REPLACE FUNCTION list_top_Patients_Visit()
RETURNS SETOF PatientsList AS $$
DECLARE
_Pat_Number visit.Pat_Number%type;
_Patient_Name patient.NAme%type;
_Doc_Number doctor.Doc_Number%type;
_Doc_Name doctor.Name%type;
_Specialty doctor.Specialty%type;
_Total_visit INTEGER;
_Total_price visit.Price%type;
_Max_price visit.Price%type;
BEGIN
FOR _Pat_Number, _Total_visit IN (
SELECT v.pat_number _Pat_Number, count((v.pat_number)) _Total_visit
FROM visit v, patient p
GROUP BY v.pat_number, p.Name
ORDER BY _Total_visit DESC, v.pat_number
LIMIT 5)
LOOP
SELECT count((visit.pat_number)) _Total_visit, patient.Name _Patient_Name, visit.doc_number _Doc_Number, count((visit.doc_number)) max_metge, sum(visit.price) _Total_price, max(visit.price) _Max_price, doctor.name _Doc_Name, doctor.specialty _Specialty
FROM visit, doctor, patient
WHERE visit.pat_number=_Pat_Number and visit.doc_number=doctor.doc_number and patient.pat_number=_Pat_Number
GROUP BY visit.doc_number, doctor.name, specialty, patient.Name
ORDER BY max_metge DESC, visit.doc_number
LIMIT 1;
RETURN NEXT _Patient_Name, _Doc_Name, _Specialty, _Total_visit, _Total_price, _Max_price;
END LOOP;
END;
$$LANGUAGE plpgsql;
You must use SELECT ... INTO ... to set variables in PL/PgSQL from a query.
SELECT without INTO is not permitted. If you want to discard the result, use PERFORM instead. If you wish to store the result in variables, use SELECT ... INTO .... See the manual for details.
In this case you can use RETURN NEXT QUERY SELECT ... instead of separate SELECT and RETURN steps.

What is equivalent of Firebird SQL 'for do' and 'select into' for postgresql

In a Firebird SQL stored procedure I use a 'select into' in a 'for do' loop and I don't find the equivalent for pg function.
for select purchase.quantity, purchase.purchasevalue, purchase.purchased, purchase.id from purchase
join cellarbook cb on purchase.fk_cellarbook_id = cb.id
join bottle bot on cb.fk_bottle_id = bot.id
where bot.id = :bottleid
order by purchase.purchased ASC
into :purquantity, :purvalue, :purdate, :purid
do
begin
/* calculate quantity on hand at point of purchase
here come some more 'select' and calculations and
then and 'update' */
select sum(psum.quantity) as purquantitysum from purchase
join cellarbook cb on psum.fk_cellarbook_id = cb.id
join bottle bot on cb.fk_bottle_id = bot.id
where bot.id = bottleid and psum.purchased <= pur.purchased and psum.id <> pur.id
into :purquantitysum
end
I think it is a 'for in loop' but I am hung up on what the equivalent for the 'select into' is.
You need to use a record variable for this:
declare
r record;
begin
for r in
select col_1, col_2 from some_table;
loop
select sum(x)
from other_table
where id = r.col_1;
end loop;
end;
More examples are in the manual:
http://www.postgresql.org/docs/current/static/plpgsql-control-structures.html#PLPGSQL-RECORDS-ITERATING
When you run update or select statements inside a loop is usually code-smell ("row-by-row processing"). In most of the cases it is much more efficient to do a bulk processing of everything in a single statement.
I'd use a cursor. There are several variations on the same theme available. I normally use this:
declare mycursor cursor for select a, b from c;
declare d, e bigint;
begin
loop
fetch from mycursor into d, e
exit when not found;
-- do your thing here
end loop;
close mycursor;
-- maybe do some other stuff
end;

Using prepared statement in stored function

I have a table in the database:
create table store (
...
n_status integer not null,
t_tag varchar(4)
t_name varchar,
t_description varchar,
dt_modified timestamp not null,
...
);
In my stored function I need to execute the same select against this table multiple times:
select * from store
where n_place_id = [different values]
and t_tag is not null
and n_status > 0
and (t_name ~* t_search or t_description ~* t_search)
order by dt_modified desc
limit n_max;
Here, t_search and n_max are parameters into the stored function. I thought it would make sense to use a prepared statement for this, but I'm running into strange problems. Here's what I have:
create or replace function fn_get_data(t_search varchar, n_max integer)
returns setof store as
$body$
declare
resulter store%rowtype;
mid integer;
begin
prepare statement prep_stmt(integer) as
select *
from store
where n_place_id = $1
and (t_name ~* t_search or t_description ~* t_search)
order by dt_modified
limit n_max;
for mid in
(select n_place_id from ... where ...)
loop
for resulter in
execute prep_stmt(mid)
loop
return next resulter;
end loop;
end loop;
end;$body$
language 'plpgsql' volatile;
However when I actually run the function with
select * from fn_get_data('', 30)
I receive this error:
ERROR: column "t_search" does not exist
LINE 3: and (t_name ~* t_search or t_description ~* t_search)
^
QUERY: prepare prep_stmt(integer) as
select * from store where n_status > 0 and t_tag is not null and n_museum = $1
and (t_name ~* t_search or t_description ~* t_search)
order by dt_modified desc limit maxres_free
Ok, maybe it doesn't like external variables in the prepared statement, so I changed this to be
prepare prep_stmt(integer, varchar, integer) as
select * from store where n_status > 0 and t_tag is not null and n_museum = $1
and (t_name ~* $2 or t_description ~* $2)
order by dt_modified desc limit $3
...
for resulter in
execute prep_stmt(mid, t_search, n_max)
...
This time I get a different error:
ERROR: function prep_stmt(integer, character varying, integer) does not exist
LINE 1: SELECT prep_stmt(mid, t_search, n_max)
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
QUERY: SELECT prep_stmt(mid, t_search, n_max)
What am I missing here?
EDIT I added the relevant table structure at the top.
Looks to me like the PL/PgSQL EXECUTE for dynamic SQL trumps the regular SQL EXECUTE for prepared statements.
Code:
create or replace function prep_test() returns void as $$
begin
PREPARE do_something AS SELECT 1;
EXECUTE do_something;
end;
$$ LANGUAGE 'plpgsql';
Test:
regress=# select prep_test(1);
ERROR: column "do_something" does not exist
LINE 1: SELECT do_something
^
QUERY: SELECT do_something
CONTEXT: PL/pgSQL function "prep_test" line 4 at EXECUTE statement
outside PL/PgSQL it works fine:
regress=# EXECUTE do_something;
?column?
----------
1
(1 row)
I'm not sure how you'd execute a prepared statement within PL/PgSQL.
Out of interest, why are you trying to use prepared statements within PL/PgSQL? Plans are prepared and cached for PL/PgSQL anyway, it happens automatically.
There is a way to EXECUTE a prepared statement in a function, but like the accepted answer said, you typically don't wan't to do this in a function because the function already stores its plan.
That being said, there are still use cases where you do need to use a prepared statement in a function. My use case for this is when using multiple schemas for different users where the schemas contain tables that are similarly named and you want to use the same function to access one of these tables based off of what the search_path is set to. In this situation, because of the way the function stores its plan, using the same function after changing the search_path causes things to break. There are two solutions to this problem that I've stated. The first is to use EXECUTE '<Your query as a string here>'. But this can get very ugly for large queries, hence the reason to use the second method, which involves a PREPARE.
So with the background as to 'why' you'd want to do this out of the way, here is the how:
CREATE OR REPLACE FUNCTION prep_test()
RETURNS void AS $$
BEGIN
PREPARE do_something AS SELECT 1;
EXECUTE 'EXECUTE do_something;';
END;
$$ LANGUAGE plpgsql;
Though it will probably be in your best interests to add some protections to keep it from breaking. Something like:
CREATE OR REPLACE FUNCTION prep_test()
RETURNS void AS $$
BEGIN
IF (SELECT count(*) FROM pg_prepared_statements WHERE name ilike 'do_something') > 0 THEN
DEALLOCATE do_something;
END IF;
PREPARE do_something AS SELECT 1;
EXECUTE 'EXECUTE do_something;';
DEALLOCATE do_something;
END;
$$ LANGUAGE plpgsql;
Again, those who think that they want to do this, usually probably shouldn't, but for those cases where it is needed, this is how you do it.
You could use an EXECUTE statement like this in PLPGSQL:
select magicvalue into str_execute from magicvalues where magickey = ar_requestData[2];
EXECUTE str_execute into str_label USING ar_requestData[3], ar_requestData[4]::boolean, ar_requestData[5]::int, ar_requestData[6];
This is code I use in my application. ar_requestData is an array with text values.
In the table magicvalues do I store things like prepared statements.
The select statement is for example:
insert into classtypes(label, usenow, ranking, description) values($1,$2,$3,$4) returning label'
With kind regards,
Loek Bergman
PREPARE statement is not allowed within plpgsql. You can splice all the statements inside a function and use dynamic execute after then. Here is an example.
create or replace function sp_test(f_total int) returns void as $ytt$
declare v_sql text;
declare i int;
begin
v_sql:='prepare ytt_s1 (int,timestamp) as select * from tbl1 where id = $1 and log_time = $2;';
while i < f_total
loop
v_sql:=v_sql||'execute ytt_s1('||i||',now());';
i := i + 1;
end loop;
v_sql:=v_sql||'deallocate ytt_s1;';
execute v_sql;
end;
$ytt$ language plpgsql;