postgres functions and transactions with BEGIN and blocks - postgresql

I have some questions about how postgres functions and transactions work. I am beginners to transactions and functions . please help me to understand the following code.
Currently my function looks like this:
CREATE OR REPLACE FUNCTION what_do_i_do(
arg_i jsonb
)
RETURNS json
LANGUAGE plpgsql
AS
$function$
DECLARE
rcd RECORD;
msg text;
error json;
b boolean;
i integer;
response json;
BEGIN
--block 1
i = arg_i->>'i';
--block 2
a_i = arg_i->>'a_i';
IF arg_i ? 'm' THEN
b = Not (arg_i->>'m')::boolean;
ELSE
b = true;
END IF;
--block 3
select p.aa abc into rcd
from p
where p.pi = i
limit 1;
--block 4
IF NOT FOUND THEN
RAISE '%', msg USING ERRCODE = 'foreign_key_violation';
Return Null;
END IF;
--block 5
IF b = true and rcd.pa = false THEN
--5.1
UPDATE p set aa = true where pi = i;
--5.2
UPDATE e
SET aa = true, ab = false
WHERE aai in (select aai from aa where pi = i and zzz = false)
and dda = false;
--5.3
get diagnostics cnt = row_count;
--6
RETURN response;
--7
EXCEPTION
WHEN OTHERS THEN
GET STACKED DIAGNOSTICS stack_msg = MESSAGE_TEXT, stack = PG_EXCEPTION_CONTEXT;
RAISE WARNING '%: --- Error Stack ---:
message: %
context: %
', module, stack_msg, stack;
RAISE;
END;
$function$;
The statements can be UPDATE,EXCEPTION or plain SELECT queries based on some_id. As I understand from postgre documentation, all statements in this function are executed as a single transaction and committed at the END

Related

PostgreSql 12 JDBC multiple resultset, can only get the first resultset

Want JDBC to get multiple result-sets from PostgreSql 12 function. JDBC version org.postgresql/postgresql/42.2.9. The PostgreSql function is here:
create or replace function test()
returns setof refcursor as $$
declare
ret1 refcursor;
ret2 refcursor;
begin
open ret1 for
select 10 as a;
return next ret1;
open ret2 for
select 20 as b;
return next ret2;
end; $$ language plpgsql;
Java code is like this:
stmt = getConnection().prepareCall("{ ? = call test() }");
stmt.registerOutParameter(1, Types.OTHER);
stmt.execute();
rs = (ResultSet)stmt.getObject(1);
if (rs.next()) {
int a = rs.getInt("a");
if (rs.next()) {
int b = rs.getInt("b");
}
}
"a" is successfully retrieved, but the second "rs.next()" returns false. Googled a lot but couldn't make it work. How can I get the 2nd result-set?
This link helped me a lot and the problem was resolved.
...
out ret1 refcursor,
out ret2 refcursor)
returns record as $$
begin
open ret1 for select 10;
open ret2 for select 20;
end; $$ language plpgsql;
Java:
stmt = getConnection().prepareCall("{call test(?,?)}");
stmt.registerOutParameter(1, Types.OTHER);
stmt.registerOutParameter(2, Types.OTHER);
stmt.execute();
ResultSet rs1 = (ResultSet)stmt.getObject(1);
ResultSet rs2 = (ResultSet)stmt.getObject(2);
int a = 0, b = 0;
if (rs1.next()) {
a = rs1.getInt(1);
}
if (rs2.next()) {
b = rs2.getInt(1);
}

Postgresql Simple IF ELSE Statement

In MS SQL I can execute a statement like:
Declare #foo int = 1, #foo1 int = 42
IF #foo <> 0
BEGIN
SELECT #foo
END
ELSE
BEGIN
SELECT #foo, #foo1
END
Does anyone have any idea how to run this statement on postgresql?
EDIT: MS SQL Example like :
CREATE PROCEDURE dbo.spIFtest
#p1 int = 1,
#p2 int = 10,
#isFilter bit = 0
AS
BEGIN
IF #isFilter = 1
BEGIN
SELECT idx FROM rw.octest where idx between #p1 and #p2
END
ELSE
BEGIN
SELECT idx FROM rw.octest
END
END
GO
Using DO With caveats:
DO $$
DECLARE
foo integer := 1;
foo1 integer := 42;
BEGIN
IF foo <> 0 THEN
PERFORM foo;
ELSE
PERFORM foo, foo1;
END IF;
END;
$$
;
DO cannot return anything.
You can fake a return:
DO $$
DECLARE
foo integer := 0;
foo1 integer := 42;
BEGIN
IF foo <> 0 THEN
SELECT INTO foo 1;
RAISE NOTICE 'foo is %', foo;
ELSE
SELECT INTO foo, foo1 1, 42 ;
RAISE NOTICE 'foo is %, foo1 is %', foo, foo1;
END IF;
END;
$$
;
NOTICE: foo is 1, foo1 is 42
DO
In PostgreSQL DO Block can execute the queries but they can not return any value.
So the first part of your question is not possible directly in postgresql.
For second part of your question: In PostgreSQL you can use Function (which is very powerful and effective) like below:
create or replace function spiftest()
returns table(idx_ int)
as $$
declare
p1 int := 1;
p2 int := 10;
isfilter boolean := 0;
begin
if isfilter then
return query
SELECT idx FROM octest where idx between p1 and p2;
else
return query
SELECT idx FROM octest ;
end if;
end;
$$
language plpgsql
calling above function for result:
select * from spiftest()
You can write it with parameters also like below:
create or replace function spiftest(p1 int, p2 int, isfilter boolean)
returns table(idx_ int)
as $$
begin
if isfilter then
return query
SELECT idx FROM octest where idx between p1 and p2;
else
return query
SELECT idx FROM octest ;
end if;
end;
$$
language plpgsql
to call above function
select * from spiftest(1,10,'t')

Run DELETE query in trigger function

I'm trying to run a DELETE and UPDATE query inside a trigger function in PL/pgSQL.
CREATE OR REPLACE FUNCTION trg_delete_order_layer()
RETURNS trigger AS
$BODY$
DECLARE index_ INTEGER;
DECLARE a RECORD;
BEGIN
IF EXISTS (SELECT * FROM map_layers_order WHERE id_map = OLD.id_map)
THEN index_ := position FROM map_layers_order WHERE id_map = OLD.id_map AND id_layer = OLD.id_layer AND layer_type = 'layer';
ELSE index_ := 0;
END IF;
RAISE NOTICE 'max_index % % %', index_, OLD.id_map, OLD.id_layer;
EXECUTE 'DELETE FROM map_layers_order WHERE id_map = $1 AND id_layer = $2 AND layer_type = $3' USING OLD.id_map, OLD.id_layer, 'layer';
EXECUTE 'UPDATE map_layers_order SET position = position -1 WHERE id_map = $1 AND position > $2' USING OLD.id_map, index_;
VALUES (OLD.id_map, OLD.id_layer, 'layer', index_);
RETURN OLD;
END;
$BODY$
LANGUAGE plpgsql;
I don't know why i'm getting this error in the line where it runs the DELETE query:
Query has no destination for result data
Why is this error happen and how can I solve this?
Obviously if I use EXECUTE...INTO, I get a more reasonable error saying that the query has no result.

Postgres unassigned record, is there a way to test for null?

Is there some way to test an unassigned record for null? (Sorry, sqlfiddle doesn't like my DO block.) Thanks.
DO
$$
DECLARE
r record;
BEGIN
r := null;
if r is null -- ERROR: record "r" is not assigned yet
then
end if;
END
$$;
The error can be avoided by writing:
select null into r;
or alternatively:
r:=row(null);
such that r gets initialized with a tuple structure.
Still, be aware that record variables are unintuitive in other of ways concerning NULLs, and more generally hard to work with outside of their base use case (cursor-style iterating).
If you wrap the test with an exception handler you can use the "not initialized" error to do the check for you.
DO $$
DECLARE
r record;
BEGIN
r := null;
BEGIN
IF r IS NOT NULL THEN
raise notice 'R IS INITIALIZED';
END IF;
EXCEPTION
WHEN OTHERS THEN
raise notice 'R IS NOT INITIALIZED';
END;
END
$$;
select *
from t
where id = p_id
into r;
if found then
...
else
...
end if;
https://www.solvingsoftware.dev/testing-for-an-empty-record-variable-in-postgresql/
I would couple that var with the bool variable and set bool variable whenever record is set:
DO
$$
DECLARE
r record;
rSet bool;
BEGIN
if rSet then -- it is false at the beginning
end if;
-- whenever r is set do this as well
r := select a,b;
rSet = true;
-- whenever that value was consumed, set it to "NULL" again:
return next r.a, r.b;
rSet = false;
END
$$;

translate plpgsql recursive function back to pg8.1

I have the following plpgsql function that does work great on pg 8.3 and above but I need to translate it back to a pg 8.1 database and I can't seam to get it right.
Any tips? I need to get rid of the "RETURN QUERY" as it was not yet introduced in 8.1...
CREATE OR REPLACE FUNCTION specie_children (specie_id INT, self BOOLEAN)
RETURNS SETOF specie AS
$BODY$
DECLARE
r specie%ROWTYPE;
BEGIN
IF self THEN
RETURN QUERY SELECT * FROM specie WHERE specieid = specie_id;
END IF;
FOR r IN SELECT * FROM specie WHERE parent = specie_id
LOOP
RETURN NEXT r;
RETURN QUERY SELECT * FROM specie_children(r.specieid, FALSE);
END LOOP;
RETURN;
END
$BODY$
LANGUAGE 'plpgsql';
How do I translate this ?
RETURN QUERY SELECT * FROM specie_children(r.specieid, FALSE);
could be rewritten as
for r2 in select * from specie_children(r.specieid, FALSE)
loop
return next r2
end loop
Quick demo. Basically #maniek already provided the answer.
Test table:
CREATE TEMP TABLE specie(specieid int, parent int);
INSERT INTO specie VALUES
(1,0), (2,0), (3,0)
,(11,1), (12,1), (13,1)
,(111,11), (112,11), (113,11);
Rewritten function:
CREATE OR REPLACE FUNCTION specie_children (specie_id INT, self BOOLEAN)
RETURNS SETOF specie AS
$BODY$
DECLARE
r specie%ROWTYPE;
BEGIN
IF self THEN
FOR r IN SELECT * FROM specie WHERE specieid = $1
LOOP
RETURN NEXT r;
END LOOP;
END IF;
FOR r IN SELECT * FROM specie WHERE parent = $1
LOOP
RETURN NEXT r;
FOR r IN SELECT * FROM specie_children(r.specieid, FALSE)
LOOP
RETURN NEXT r;
END LOOP;
END LOOP;
RETURN;
END;
$BODY$
LANGUAGE plpgsql;
Call:
SELECT * FROM specie_children (1, true);
Returns:
specieid | parent
---------+-------
1 | 0
11 | 1
111 | 11
112 | 11
113 | 11
12 | 1
13 | 1