postgresql - loop doesn't work properly - postgresql

I wrote function to update or insert into log table some information.
Every query should return around 100 records, but below function always updating or inserting only last row.I thing that I should put RETURN NEXT before END LOOP but it is not working.
CREATE OR REPLACE FUNCTION plugins.update_log()
RETURNS void AS
$BODY$
DECLARE
rec record;
BEGIN
FOR rec IN
SELECT na1_nr::integer,
CASE WHEN c.record_status_id::integer = 8 THEN c.niezainteresowany_powod::text ELSE ots.name::text END reason
FROM temp_id c
LEFT JOIN plugins.outbounds_telcom_statuses ots ON (ots.telcom_status_id::integer=c.telcom_status_id::integer AND ots.outbound_id::integer = 33)
LOOP
UPDATE plugins.boss_release_log
SET count = count::integer +1
WHERE na1_nr::integer = rec.na1_nr::integer
AND reason::text = rec.reason::text;
IF found THEN
RETURN;
END IF;
BEGIN
INSERT INTO plugins.boss_release_log(na1_nr,reason,count)
VALUES (rec.na1_nr, rec.reason, 1);
RETURN;
EXCEPTION WHEN unique_violation THEN
END;
END LOOP;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION plugins.update_log()
OWNER TO ss0;

Move your RETURN statement to the end of your function. Your function MUST have a RETURN statement in it, but when it is hit it exits (for set-returning functions you use RETURN NEXT but that is not applicable here).

Related

Postgresql: fetch all not showing any data

Data is not showing when I tried to fetch data from cursor. dbo.show_cities_multiple2 is procedure which returns two cursors.
DO $$
<<first_block>>
DECLARE
counter refcursor := 0;
ca_cur refcursor:=null;
tx_cur refcursor:=null;
BEGIN
call dbo.show_cities_multiple2(ca_cur, tx_cur);
EXECUTE 'FETCH ALL from "' || tx_cur || '"';
RAISE NOTICE 'The current value of counter is %', ca_cur;
END first_block $$;
Your problem here is that you aren't actually doing anything with the results of your EXECUTE FETCH ... EXECUTE merely runs the statement that comes after it. A plpgsql function does not return anything unless you explicitly specify RETURN ...,RETURN NEXT ... or RETURN QUERY .... Furthermore, an anonymous DO $$ block cannot return any results. So if you want to actually retrieve rows from the cursor, you need to name your cursors and execute your fetch outside of the do block:
DO $$
<<first_block>>
DECLARE
counter integer := 0;
ca_cur refcursor:='ca_cur'; --name your cursors first, otherwise they will be anonymous!
tx_cur refcursor:='tx_cur';
BEGIN
call dbo.show_cities_multiple2(ca_cur, tx_cur);
...
END first_block $$;
--Fetch
FETCH ALL FROM tx_cur;
Alternatively, you can define a standard function and use RETURN NEXT OR RETURN QUERY to return the result sets from the cursors:
CREATE FUNCTION return_city_data() RETURNS SETOF cities AS $$
DECLARE
counter integer = 0;
tx_cur refcursor = 'tx_cur';
ca_cur refcursor = 'ca_cur';
rec RECORD;
BEGIN
CALL dbo.show_cities_multiple(tx_cur,ca_cur);
-- Iterate over each record in the cursor
FOR rec in EXECUTE 'FETCH ALL FROM ' || tx_cur LOOP
counter = counter + 1;
-- RETURN NEXT means "Append each value to the function's result set".
RETURN NEXT rec;
END LOOP;
FOR rec in EXECUTE 'FETCH ALL FROM ' || ca_cur LOOP
counter = counter + 1;
RETURN NEXT rec;
END LOOP;
--All records have been appended to the result set. the cursors can now be closed.
CLOSE tx_cur;
CLOSE ca_cur;
RAISE NOTICE 'The current value of counter is %', "counter";
END
$$ LANGUAGE plpgsql;
OR
CREATE FUNCTION return_city_data() RETURNS SETOF cities AS $$
DECLARE
counter integer = 0;
tx_cur refcursor = 'tx_cur';
ca_cur refcursor = 'ca_cur';
rec RECORD;
BEGIN
CALL dbo.show_cities_multiple(tx_cur,ca_cur);
-- RETURN QUERY means "append all rows from the query to the function's result set"
RETURN QUERY EXECUTE 'FETCH ALL FROM ' || tx_cur;
RETURN QUERY EXECUTE 'FETCH ALL FROM ' || ca_cur;
CLOSE tx_cur;
CLOSE ca_cur;
END
$$ LANGUAGE plpgsql;
You can use either of these forms with a simple SELECT statement:
SELECT * FROM return_city_data();
Note that unlike RETURN, RETURN NEXT and RETURN QUERY do not terminate your function and you can use them multiple times in your blocks. The function will then naturally terminate at the end of the last block.

Check Results Count in Postgres Function Before Returning

I have a postgres function that I'd like to return the result of a query, but I'd like it to return nothing if that query matches more than 1 record.
So, something like:
CREATE OR REPLACE FUNCTION myFunc(_a text, _b text)
RETURNS yy
LANGUAGE plpgsql
STABLE
PARALLEL SAFE
AS $$
BEGIN
RETURN QUERY
SELECT *
FROM yy
WHERE a = x
AND b = y;
END;
$$;
Except, it should return nothing if that query matches more than 1 record.
CREATE OR REPLACE FUNCTION myFunc(_a text, _b text)
RETURNS SETOF yy -- To be able to return "nothing"
LANGUAGE plpgsql
STABLE
PARALLEL SAFE
AS $$
DECLARE
result yy;
BEGIN
SELECT *
INTO STRICT result -- STRICT allows to check that exactly one row returned
FROM yy
WHERE a = x
AND b = y;
RETURN NEXT result; -- RETURN NEXT - return yet another row for "RETURNS SETOF" function
EXCEPTION
WHEN no_data_found OR too_many_rows THEN -- When no data or more then one rows
RETURN; -- Nothing to return, just exit
END;
$$;
i guess this can help you out.
CREATE OR REPLACE FUNCTION database.myFunction(
IN text,IN text)
RETURNS TABLE(firstField, secondField, lastField) AS
$BODY$
--sql string is the variable containing the final sql code
declare sql_string text;
declare regs numeric;
begin
--this is what happens in case count<1
sql_string = 'select 0,0,0';
--now we count them
regs = (select count(firstField) from mytable where a=b)::numeric;
--if >=1, then whe get the whole data
if (regs>=1) then
sql_string = 'select firstField,secondField, lastField from mytable where a=b';
end if;
--and return to you...
return query EXECUTE sql_string;
end;

query result based loop syntax in if condition

I am new to PGSQL and trying to start a loop in database function that iterates on the basis of query result as shown below. I am using 8.2 version.
CREATE OR REPLACE FUNCTION demo(text)
RETURNS SETOF activityhistoryview
LANGUAGE plpgsql STABLE
AS $_$
DECLARE
tilldate ALIAS for $1;
actrec revpro_500.activity%ROWTYPE;
BEGIN
IF tilldate != '' THEN
FOR actrec IN
SELECT activity.* from revpro_500.activity WHERE activity.householdid = 950
LOOP
ELSE
FOR actrec IN
SELECT activity.* from revpro_500.activity WHERE activity.householdid = 500
LOOP
END IF;
BEGIN
/* rest code goes here */
END
END LOOP;
RETURN;
END;$_$;
After executing above function, I am getting below error.
ERROR: syntax error at or near "ELSE"
What I am missing here?
You can not nest loop queries like that. Instead, first evaluate what you want to do with tilldate, then make a single loop query:
CREATE OR REPLACE FUNCTION demo(tilldate text) RETURNS SETOF activityhistoryview
LANGUAGE plpgsql STABLE AS $_$
DECLARE
actrec revpro_500.activity%ROWTYPE;
hhid integer;
BEGIN
IF tilldate != '' THEN
hhid = 950;
ELSE
hhid = 500;
END IF;
FOR actrec IN
SELECT * from revpro_500.activity WHERE householdid = hhid
LOOP
BEGIN -- Do you really need a transaction block? If not, remove BEGIN/END
-- rest code goes here
END
END LOOP;
RETURN;
END;$_$;
Like in most languages you can't have your control structures overlapping, so on the line before the ELSE you open a LOOP, but do not close it before the ELSE, so the ELSE doesn't have an attached IF paired to it.
You can put the IF/ELSE block inside the loop, or outside it, but not overlapping.
Example:
-- Good
LOOP
-- some computations
IF tilldate != '' THEN
EXIT; -- exit loop
ELSE
-- some computations
END IF;
END LOOP;
-- Good
IF tilldate != '' THEN
LOOP
-- some computations
END LOOP;
ELSE
LOOP
-- some computations
END LOOP;
END IF;
-- Bad
IF
LOOP
-- some computations
ELSE
-- some computations
END IF;
END LOOP;

Move/fetch cursor in loop (PostgreSQL)

I need to create trigger to check if new inserted element's id isn't repeated. The problem is in the LOOP statement, console spit out the error:
CONTEXT: SQL statement in PL/PgSQL function "foo" near line 7
LINE 1: move forward 1 from $1
Here is my function:
create function foo() returns trigger as'
declare xyz cursor for select id from accounts;
begin
LOOP
if NEW.id = xyz then
raise notice ''Id is just used!'';
else
move forward 1 from xyz;
end if;
END LOOP;
return NEW;
close xyz;
end;
' language 'plpgsql';
create trigger foo before insert on accounts for each
row execute procedure foo();
Your example has no sense (you can't compare scalar value with cursor). Cursor self is like pointer without any value.
if NEW.id = xyx then
Why you don't do
BEGIN
IF EXISTS(SELECT * FROM accounts a WHERE a.id = NEW.id) THEN
RAISE NOTICE ''Id is used''; -- better RAISE EXCEPTION
END IF;
RETURN NEW;
END;
second nonsense
RETURN NEW;
CLOSE xyz; -- no statement is executed after RETURN

How to return updated rows from function

i'm quite new to postgres. i want to create a function (like stored procedure) that updates multiple rows and selects affected rows.
here is my statement:
CREATE or replace FUNCTION set_val(
_val character varying(100) ) --5
RETURNS setof "table_test" AS
$body$
declare results "table_test"%rowtype;
begin
update "table_test"
set "value" = $1
where "gender" = 'm'
returning * into results;
if not found then
insert into "table_test"("value")
values($1)
returning * into results;
end if;
return next results;
end;
$body$
LANGUAGE 'plpgsql' ;
it works fine as long as only 1 row was affected. but when more rows are affected, it doesn't.
When a PL/pgSQL function has to return setof it has to use "RETURN NEXT" or "RETURN QUERY".
i finally got it
i use for loop with return next.
thanks
here's my code
declare result table1%rowtype;
begin
for result in update table1 set ... where ... returning * loop
return next result;
end loop;
end;