How to iterate over results of query - sql

I am creating a function in pgsql script language, and what I want to do in this point is iterate over the results of a query and for each row do something specific. My current try is the following, where temprow is declared as temprow user_data.users%rowtype. The code in question is the following:
FOR temprow IN
SELECT * FROM user_data.users ORDER BY user_seasonpts DESC LIMIT 10
LOOP
SELECT user_id,user_seasonpts INTO player_idd,season_ptss FROM temprow;
INSERT INTO user_data.leaderboards (season_num,player_id,season_pts) VALUES (old_seasonnum,player_idd,season_ptss);
END LOOP;
However I get the following error from this: ERROR: relation "temprow" does not exist. If it's clear what I want to be done, could you point to me the right way to do it?

temprow is a record variable which is bound in turn to each record of the first SELECT.
So you should write:
FOR temprow IN
SELECT * FROM user_data.users ORDER BY user_seasonpts DESC LIMIT 10
LOOP
INSERT INTO user_data.leaderboards (season_num,player_id,season_pts) VALUES (old_seasonnum,temprow.userd_id,temprow.season_ptss);
END LOOP;
This loop could be further simplified as a single query:
INSERT INTO user_data.leaderboards (season_num,player_id,season_pts)
SELECT old_seasonnum,player_idd,season_ptss FROM user_data.users ORDER BY user_seasonpts DESC LIMIT 10

A function that loop through the select and use loop item values to filter and calculate other values,
CREATE FUNCTION "UpdateTable"() RETURNS boolean
LANGUAGE plpgsql
AS
$$
DECLARE
TABLE_RECORD RECORD;
BasePrice NUMERIC;
PlatformFee NUMERIC;
MarketPrice NUMERIC;
FinalAmount NUMERIC;
BEGIN
FOR TABLE_RECORD IN SELECT * FROM "SchemaName1"."TableName1" -- can select required fields only
LOOP
SELECT "BasePrice", "PlatformFee" INTO BasePrice, PlatformFee
FROM "SchemaName2"."TableName2" WHERE "UserID" = TABLE_RECORD."UserRID";
SELECT "MarketPrice" / 100 INTO MarketPrice FROM "SchemaName3"."TableName3" WHERE "DateTime" = TABLE_RECORD."DateTime";
FinalAmount = TABLE_RECORD."Qty" * (BasePrice + PlatformFee - MarketPrice);
UPDATE "SchemaName1"."TableName1" SET "MarketPrice" = MarketPrice, "Amount" = CFDAmount
WHERE "ID" = CFD_RECORD."ID"; -- can update other schema tables also
END LOOP;
RETURN TRUE;
END
$$;

For future reference, I want to emphasise Thushara comment on the accepted answer
On Postgres#12 the following would work:
DO $$
DECLARE temprow RECORD;
BEGIN FOR temprow IN
SELECT * FROM user_data.users ORDER BY user_seasonpts DESC LIMIT 10
LOOP
INSERT INTO user_data.leaderboards (season_num,player_id,season_pts) VALUES (old_seasonnum,temprow.userd_id,temprow.season_ptss);
END LOOP;
END $$

Related

How to check if row exists before SELECT INTO statement in Oracle SQL

I'm using Oracle SQL and have a procedure that is doing some operations on tables. During the procedure there is a "SELECT x INTO y FROM TABLE z WHERE..." statement inside a loop. Unfortunatly during the procedure I can't guarante that there is always a row to the corresponding where condition because it changes dynamically.
Is it possible to check if a row exists before the statement? I was thinking of sth like "if exists(select ...) then SELECT X INTO y..."
Thanks for the help!
Jack
Well, there's no point in checking it first, and re-using the same statement again.
You could handle the exception (possibly in an inner BEGIN-EXCEPTION-END block):
declare
y number;
begin
begin --> inner block starts here
select x into y from z where ...
insert into ...
exception
-- handle it, somehow; I chose not to do anything
when no_data_found then
null;
end; --> inner block ends here
end;
Or, if you used cursor FOR loop, you wouldn't have to handle it because - if select returns x, insert would run. Otherwise, nothing in that loop would ever be executed:
begin
for cur_r in (select x from z where ...) loop
insert into ...
end loop;
end;
An exception handler as in Littlefoot's answer is the most correct and explicit approach, however just for completeness you might also consider using an aggregate.
Value 'X' exists in the table:
declare
p_someparam varchar2(1) := 'X';
l_somevalue varchar2(1);
l_check number;
begin
select max(dummy), count(*) into l_somevalue, l_check
from dual d
where d.dummy = p_someparam;
dbms_output.put_line('Result: '||l_somevalue);
dbms_output.put_line(l_check||' row(s) found');
end;
Result: X
1 row(s) found
Value 'Z' does not exist in the table:
declare
p_someparam varchar2(1) := 'Z';
l_somevalue varchar2(1);
l_check number;
begin
select max(dummy), count(*) into l_somevalue, l_check
from dual d
where d.dummy = p_someparam;
dbms_output.put_line('Result: '||l_somevalue);
dbms_output.put_line(l_check||' row(s) found');
end;
Result:
0 row(s) found
You can add logic to handle the cases where the count check is 0 or greater than 1.
If you are having procedure then I should say use if statement and then write the sql:
select some_column into some_variable from tablename where condition
IF somevariable not in (<list of values separated by comma>)THEN
{statements to execute }
END IF;

In PostgreSQL procedure need to update some columns by adding them or subtracting same calculated value, but do the calculation once for row

HERE some code that I tried on PostgreSQL 14.2:
CREATE PROCEDURE update_table_data(IN zipCode integer, IN studyYears integer, IN calculationData json[])
language plpgsql
AS
$$
DECLARE additional_progress DOUBLE PRECISION
BEGIN
UPDATE some_table
SET
additional_progress = someCalculationFunction(calculationData, some_table.formula),
current_progress = progress + additional_progress,
total_progress = total_points + additional_progress,
progress_left = progress_left - additional_progress
WHERE zip_code = zipCode AND study_years = studyYears
COMMIT;
END;
$$;
As a result I want to make someCalculationFunction to work only once for each row, because I don't need it called for each column that will be updated separately.
I found out a solution for this after many tries:
CREATE PROCEDURE update_table_data(IN zipCode integer, IN studyYears integer, IN calculationData json[])
language plpgsql
as
$$
BEGIN
UPDATE some_table AS s
SET
current_progress = progress + mod_s.additional_progress,
total_progress = total_points + mod_s.additional_progress,
progress_left = progress_left - mod_s.additional_progress
FROM ( SELECT id, someCalculationFunction(calculationData, formula) AS additional_progress FROM some_table) mod_s
WHERE mod_s.additional_progress > 0 AND s.id = mod_s.id;
COMMIT;
END;
$$;

multiple update pgsql using cursor

this code not error but nothing data change please help me. I want to multiple update quantity from some stock table
drop FUNCTION SP_PROSES_STOCK(noresep bigint, p_post_cd varchar);
CREATE or replace FUNCTION SP_PROSES_STOCK(noresep bigint, p_post_cd varchar)
RETURNS void
LANGUAGE plpgsql
as $function$
DECLARE cursorData refcursor;
v_item_cd varchar;
v_quantity numeric;
begin
open cursorData FOR
select A.item_cd, A.quantity from trx_medical_resep B
inner join trx_resep_data A on A.medical_resep_seqno = B.medical_resep_seqno
where B.medical_resep_seqno = noresep;
fetch next from cursorData into v_item_cd,v_quantity;
while (found)
loop
update inv_pos_item set quantity = quantity - v_quantity
where item_cd = v_item_cd and pos_cd = p_post_cd;
end loop;
close cursorData;
END
$function$
I mopdify your function to make it shorter and use FOR. Also I add RAISE NOTICE for debugging. Can you try it:
CREATE or replace FUNCTION SP_PROSES_STOCK(noresep bigint, p_post_cd varchar)
RETURNS void
LANGUAGE plpgsql
as $function$
DECLARE v_cursor record;
BEGIN
FOR v_cursor IN
SELECT A.item_cd,
A.quantity
FROM trx_medical_resep B
JOIN trx_resep_data A ON A.medical_resep_seqno = B.medical_resep_seqno
WHERE B.medical_resep_seqno = noresep
LOOP
RAISE NOTICE 'Changes for %', v_curosr;
UPDATE inv_pos_item
SET quantity = quantity - v_cursor.quantity
WHERE item_cd = v_cursor.item_cd
AND pos_cd = p_post_cd;
END LOOP;
END
$function$
After debugging you can remove RAISE NOTICE.
You don't need a loop for this. A single UPDATE statement will be a lot faster:
CREATE or replace FUNCTION SP_PROSES_STOCK(noresep bigint, p_post_cd varchar)
RETURNS void
as
$function$
begin
update inv_pos_item
set quantity = quantity - v.quantity
from (
select A.item_cd, A.quantity
from trx_medical_resep B
join trx_resep_data A on A.medical_resep_seqno = B.medical_resep_seqno
where B.medical_resep_seqno = noresep
) v
where item_cd = v.item_cd and pos_cd = p_post_cd;
END;
$function$
LANGUAGE plpgsql;
There are scenarios where a cursor is needed. For example lets say you are updating 10 million rows and an exception is thrown at the 6th million record the the update will fail and then it will rollback all of the rows that were updated prior to the failure. If a cursor is used it will be slower but you will have greater control over the process and be able to bypass the error and continue with the posting of the updates. But then again your milage may vary...

How to insert rows to table in a loop

I have the following plpgsql function in PostgreSQL:
CREATE OR REPLACE FUNCTION func1()
RETURNS SETOF type_a AS
$BODY$
declare
param text;
sqls varchar;
row type_a;
begin
code.....
sqls='select * from func3(' || param || ') ';
for row in execute sqls LOOP
return next row;
END LOOP;
end if;
return;
end
$BODY$
LANGUAGE plpgsql VOLATILE
I want to add an insert statment into the loop, so that the loop will work as it is now but also all rows will be saved in a table.
for row in execute sqls LOOP
INSERT INTO TABLE new_tab(id, name)
return next row;
the thing is that I don't know how to do that... the insert statment normaly has syntax of:
INSERT INTO new_tab(id, name)
SELECT x.id, x.name
FROM y
but this syntax doesn't fit here. There is no query to select rows from.... the rows are in the loop.
Basic insert with values looks like this:
INSERT INTO table_name (column1,column2,column3,...)
VALUES (value1,value2,value3,...);
Based on the additional comments you need to use cursor instead of execute sqls.
No need for a loop, you can use insert .. select ... returning in dynamic SQL just as well:
create or replace function func1()
returns table (id integer, name text)
as
$$
declare
param text;
begin
param := ... ;
return query execute
'insert into new_tab (id, name)
select id, name
from func3($1)
returning *'
using param;
end;
$$
language plpgsql;
Note that I used a parameter placeholder and the USING clause instead of concatenating the parameter into the query - much more robust.

Looping through a postgres table and returning the wekid

I have a requirement to loop through a table having all the reporting periods right from 2010.
I need to loop through this table so that i when I pass the date string, it takes at and compares it if it is less than the reporting_periods_o and returns back the reporting_period_string.
CREATE OR REPLACE FUNCTION rpt.reportingperiods(IN v_date text)
RETURNS SETOF text AS
$BODY$
DECLARE
v_in_date INT := CAST(v_date AS INT);
i INT;
v_sk INT;
BEGIN
For i IN SELECT rpt_sk FROM rpt.REPORTING_PERIODS LOOP
SELECT rpt_sk INTO v_sk FROM rpt.REPORTING_PERIODS where rpt_sk = i;
IF v_in_date <= (SELECT reporting_periods_o FROM rpt.REPORTING_PERIODS
WHERE rpt_sk = i)
THEN RETURN QUERY SELECT MIN(reporting_periods)reporting_periods FROM rpt.REPORTING_PERIODS WHERE rpt_sk = v_sk AND CAST(reporting_periods AS DATE) > CAST(now() AS DATE);
END IF;
END LOOP;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 1000;
My reporting periods has three fields:
rpt_sk, reporting_periods, reporting_periods_o
1 2014-03-08 20140308
I have implemented a function but when I execute I am getting multiple records, I need to be able to get back one record.