How to add time variables? - plpgsql

I want to sum every song duration and return the outcome (ret variable). The problem is that I can't find a way to add time variables. How to achieve that?
$$
declare
ret time;
listeners record;
duration record;
vc varchar;
listener_id int;
begin
ret:='00:00:00';
for listeners in select * from listener loop
if listeners.nick=$1 then
listener_id :=listeners.id;
for duration in select song.duration as dur from playlist, song where song.id=playlist.song_id and listeners.id=playlist.listener_id loop
ret := ret + duration.dur; //how to add time variables?
end loop;
end if;
end loop;
return ret;
end;
$$

You would use data type interval for time intervals. But you have yet to learn SQL: write a join instead of procedural code implementing a nested loop:
SELECT sum(song.dur)
FROM listeners AS l
JOIN playlist AS p
ON l.id = p.listener_id
JOIN song
ON song.id = p.song_id;
I assume that utwor and song are the same, otherwise your code does not make much sense.

Related

How to code an atomic transaction in PL/pgSQL

I have multiple select, insert and update statement to complete a transaction, but I don't seem to be able to ensure all statements to be successful before committing the changes to table.
The transaction doesn't seem to be atomic.
I do have begin and end in my function but transaction doesn't seem to be atomic.
CREATE FUNCTION public.testarray(salesid integer, items json) RETURNS integer
LANGUAGE plpgsql
AS $$
declare
resu text;
resu2 text := 'TH';
ssrow RECORD;
oserlen int := 0;
nserlen int := 0;
counter int := 0;
begin
select json_array_length(items::json->'oserial') into oserlen;
while counter < nserlen loop
select items::json#>>array['oserial',counter::text] into resu;
select * into strict ssrow from salesserial where fk_salesid=salesid and serialnum=resu::int;
insert into stockloct(serialnum,fk_barcode,source,exflag) values(ssrow.serialnum,ssrow.fk_barcode,ssrow.fk_salesid,true);
counter := counter + 1;
end loop;
counter := 0;
select json_array_length(items::json->'nserial') into nserlen;
while counter < nserlen loop
select items::json#>>array['nserial',counter::text,'serial'] into resu2;
select * into ssrow from stockloc where serialnum=resu2::int;
insert into salesserial(fk_salesid,serialnum,fk_barcode) values(salesid,ssrow.serialnum,ssrow.fk_barcode);
counter := counter + 1;
end loop;
select items::json#>'{nserial,0,serial}' into resu2;
return resu;
end;
$$;
Even when the first insert fails, the second insert seems to be able to succeed.
I see that by “fail” you mean “does not insert any rows”.
That is not surprising since the first loop is never executed: both counter and nserlen are always 0.
Perhaps you mean the first WHILE condition to be counter < oserlen?
You also seem to br confused by PL/pgSQL's BEGIN: while it looks like the BEGIN that starts a transaction, it is quite different. It is just the “opening parenthesis” in a PL/pgSQL block.

Update table every 1000 rows

I am trying to do an update on a specific record every 1000 rows using Postgres. I am looking for a better way to do that. My function is described below:
CREATE OR REPLACE FUNCTION update_row()
RETURNS void AS
$BODY$
declare
myUID integer;
nRow integer;
maxUid integer;
BEGIN
nRow:=1000;
select max(uid_atm_inp) from tab into maxUid where field1 = '1240200';
loop
if (nRow > 1000 and nRow < maxUid) then
select uid from tab into myUID where field1 = '1240200' and uid >= nRow limit 1;
update tab
set field = 'xxx'
where field1 = '1240200' and uid = myUID;
nRow:=nRow+1000;
end if;
end loop;
END; $BODY$
LANGUAGE plpgsql VOLATILE
How can I improve this procedure? I think there is something wrong. The loop does not end and takes too much time.
To perform this task in SQL, you could use the row_number window function and update only those rows where the number is divisible by 1000.
Your loop doesn't finish because there is no EXIT or RETURN in it.
I doubt you could ever rival the performance of a standard SQL update with a procedural loop. Instead of doing it a row at a time, just do it all as a single statement:
with t2 as (
select
uid, row_number() over (order by 1) as rn
from tab
where field1 = '1240200'
)
update tab t1
set field = 'xxx'
from t2
where
t1.uid = t2.uid and
mod (t2.rn, 1000) = 0
Per my comment, I am presupposing what you mean by "every 1000th row," as without some designation of how to determine what tuple is what row number. That is easily edited by changing the "order by" criteria.
Adding a second where clause on the update (t1.field1 = '1240200') can't hurt but might not be necessary if these are nested loop.
This might be notionally similar to what Laurenz has in mind.
I solved this way:
declare
myUID integer;
nRow integer;
rowNum integer;
checkrow integer;
myString varchar(272);
cur_check_row cursor for select uid , row_number() over (order by 1) as rn, substr(fieldxx,1,244)
from table where field1 = '1240200' and uid >= 1000 ORDER BY uid;
BEGIN
open cur_check_row;
loop
fetch cur_check_row into myUID, rowNum, myString;
EXIT WHEN NOT FOUND;
select mod(rowNum, 1000) into checkrow;
if checkrow = 0 then
update table
set fieldxx= myString||'O'
where uid in (myUID);
end if;
end loop;
close cur_check_row;

Some error about cursor in Pl/pgsql

I know the right way to insert value from a table:
insert into city (pop) select pop+2 from city
I just want to know how cursor works in Pl/pgsql .
I wish to use cursor in loop to insert some value:
create or replace function test( ) returns void as
$$
declare
cur cursor for select pop+2 from city order by pop;
b int;
row record;
begin
for row in cur
LOOP
fetch cur into b;
insert into city(pop) select b;
end loop;
end;
$$ language plpgsql
However, when I type select test() and the result table is:
It's very strange that only two rows are inserted. So I want to know what lead to this result?
Update my question in 04/05/2016:
I revise the function like this:
create or replace function test( ) returns void as
$$
declare
cur cursor for select * from city order by pop;
b record;
begin
for b in cur
LOOP
insert into city(pop) select b.pop+2;
RAISE NOTICE ' % IS INSERTED' , b;
end loop;
end;
$$ language plpgsql
Then I get the correct result:
But I still wonder why in the first function, only two rows are inserted.
I finally figure out that why the result is wrong, just like Abelisto's comment . I did two fetchers in loop at each step:
at for row in cur LOOP ,
at fetch cur into b
So the first row where pop=500 and the third row where pop =1000 have already been fetched in for loop, and it can't be fetched by b.

Using a row as a table in a query within a function PLpgSQL

I am trying to write a plpgsql function that loops through a table. On each loop, it pulls a row from the table, stores it in a record, then uses that record in the join clause of a query. Here is my code:
CREATE OR REPLACE FUNCTION "testfncjh2" () RETURNS int
IMMUTABLE
SECURITY DEFINER
AS $dbvis$
DECLARE
counter int;
tablesize int;
rec1 record;
tablename text;
rec2 record;
BEGIN
counter = 0;
for rec1 in SELECT * FROM poilocations_sridconv loop
raise notice 'here';
execute $$ select count(*) from $$||rec1||$$ $$ into tablesize;
while counter < tablesize loop
counter = counter + 1;
raise notice 'hi';
execute $$ select count(*) from cities_sridconv $$ into tablesize;
end loop;
end loop;
return counter;
END;
$dbvis$ LANGUAGE plpgsql;
Each time I run this, I get the following error:
ERROR: could not find array type for data type record
Is there a way to use the row as a table in the query within the nested loops?
My end goal is to build a function that loops through a table, pulling a row from that table on each loop. In each loop, a number COUNTER is computed using the row, then a query is executed depending on the row and COUNTER. Knowing that this code is currently very flawed, I am posting it below to give an idea of what I am trying to do:
CREATE OR REPLACE FUNCTION "testfncjh" () RETURNS void
IMMUTABLE
SECURITY DEFINER
AS $dbvis$
DECLARE
counter int;
tablesize int;
rec1 record;
tablename text;
rec2 record;
BEGIN
for rec1 in SELECT * FROM poilocations_sridconv loop
counter = 0;
execute $$ select count(*)
from $$||rec1||$$ a
join
cities_srid_conv b
on right(a.geom_wgs_pois,$$||counter||$$) = right(b.geom_wgs_pois,$$||counter||$$) $$ into tablesize;
raise notice 'got through first execute';
while tablesize = 0 loop
counter = counter + 1;
execute $$ select count(*)
from '||rec1||' a
join
cities_srid_conv b
on right(a.geom_wgs_pois,'||counter||') = right(b.geom_wgs_pois,'||counter||') $$ into tablesize;
raise notice 'hi';
end loop;
EXECUTE
'select
poiname,
name as cityname,
postgis.ST_Distance(postgis.ST_GeomFromText(''POINT(poilat poilong)''),
postgis.ST_GeomFromText(''POINT(citylat citylong)'')
) as distance
from (select a.poiname,
a.latitude::text as poilat,
a.longitude::text as poilong,
b.geonameid,
b.name,
b.latitude as citylat,
b.longitude as citylong
from '||rec1||' a
join cities_srid_conv b
on right(a.geom_wgs_pois,'||counter||') = right(b.geom_wgs_pois,'||counter||'))
) x
order by distance
limit 1'
poi_cities_match (poiname, cityname, distance); ------SQL STATEMENT TO INSERT CLOSEST CITY TO TABLE POI_CITIES_MATCH
end loop;
END;
$dbvis$ LANGUAGE plpgsql;
I am running on a PostgreSQL 8.2.15 database.
Also, sorry for reposting. I had to remove some data from the original.
I think you should be able to use composite types for what you want. I simplified your top example and used composite types in the following way.
CREATE OR REPLACE FUNCTION "testfncjh2" () RETURNS int
IMMUTABLE
SECURITY DEFINER
AS $dbvis$
DECLARE
counter int;
tablesize int;
rec1 poilocations_sridconv;
tablename text;
rec2 record;
BEGIN
counter = 0;
for rec1 in SELECT * FROM poilocations_sridconv loop
raise notice 'here';
select count(*) FROM (select (rec1).*)theRecord into counter;
end loop;
return counter;
END;
$dbvis$ LANGUAGE plpgsql;
The main changes being the rec1 poilocations_sridconv; line and using (select (rec1).*)
Hope it helps.
EDIT: I should note that the function is not doing the same thing as it does in the question above. This is just as an example of how you could use a record as a table in a query.
You have a few issues with your code (apart, perhaps, from your logic).
Foremost, you should not use a record as a table source in a JOIN. Instead, filter the second table for rows that match some field from the record.
Second, you should use the format() function instead of assembling strings with the || operator. But you can't because you are using the before-prehistoric version 8.2. This is from the cave-painting era (yes, it's that bad). UPGRADE!
Thirdly, don't over-complicate your queries. The sub-query is not necessary here.
Put together, the second dynamic query from your real code would reduce to this:
EXECUTE format(
'SELECT b.name,
postgis.ST_Distance(postgis.ST_SetSRID(postgis.ST_MakePoint(%1$I.longitude, %1$I.latitude), 4326),
postgis.ST_SetSRID(postgis.ST_MakePoint(b.longitude, b.latitude), 4326))
FROM cities_srid_conv b
WHERE right(%1$I.geom_wgs_pois, %2$L) = right(b.geom_wgs_pois, %2$L)
ORDER BY distance
LIMIT 1', rec1, counter) INTO cityname, distance;
poi_cities_match (rec1.poiname, cityname, distance); ------SQL STATEMENT TO INSERT CLOSEST CITY TO TABLE POI_CITIES_MATCH
Here %1$I refers to the first parameter after the string, which is an idenifier: rec1; %2$L is the second parameter, being a literal value: counter. I leave it to yourself to re-work this to a pre-8.4 string concatenation. The results from the query are stored in a few additional variables which you can then use in the following function call.
Lastly, you had longitude and latitude reversed. In PostGIS longitude always comes first.

Plpgsql - Iterate over a recordset multiple times

I have a table with series of months with cumulative activity e.g.
month | activity
Jan-15 | 20
Feb-15 | 22
I also have a series of thresholds in another table e.g. 50, 100, 200. I need to get the date when the threshold is reached i.e. activity >= threshold.
The way I thought of doing this is to have a pgsql function that reads in the thresholds table, iterates over that cursor and reads in the months table to a cursor, then iterating over those rows working out the month where the threshold is reached. For performance reasons, rather than selecting all rows in the months table each time, I would then go back to the first row in the cursor and re-iterate over with the new value from the thresholds table.
Is this a sensible way to approach the problem? This is what I have so far - I am getting a
ERROR: cursor "curs" already in use error.
CREATE OR REPLACE FUNCTION schema.function()
RETURNS SETOF schema.row_type AS
$BODY$
DECLARE
rec RECORD;
rectimeline RECORD;
notification_threshold int;
notification_text text;
notification_date date;
output_rec schema.row_type;
curs SCROLL CURSOR FOR select * from schema.another_function_returning_set(); -- this is months table
curs2 CURSOR FOR select * from schema.notifications_table;
BEGIN
OPEN curs;
FOR rec IN curs2 LOOP
notification_threshold := rec.threshold;
LOOP
FETCH curs INTO rectimeline; -- this line seems to be the problem - not sure why cursor is closing
IF notification_threshold >= rectimeline.activity_total THEN
notification_text := rec.housing_notification_text;
notification_date := rectimeline.active_date;
SELECT notification_text, notification_date INTO output_rec.notification_text, output_rec.notification_date;
MOVE FIRST from curs;
RETURN NEXT output_rec;
END IF;
END LOOP;
END LOOP;
RETURN;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
select distinct on (t.threshold) *
from
thresholds t
inner join
months m on t.threshold < m.activity
order by t.threshold desc, m.month