Declare a variable of temporary table in stored procedure in PL/pgSQL - postgresql

I receive this error to begin with:
ERROR: syntax error at or near "conference"
LINE 19: FOR conference IN conferenceset
Here's the function:
CREATE OR REPLACE FUNCTION due_payments_to_suppliers_previous_month()
RETURNS TABLE(supplier varchar,due_amount numeric)
AS $$
DECLARE
BEGIN
CREATE TABLE conferenceset AS -- temporary table, so I can store the result set
SELECT
conference.conference_supplier_id,
conference.id AS conferenceid,
conference.price_per_person,
0 AS participants_count,
400 AS deduction_per_participant,
0 AS total_amount
FROM Conference WHERE --- date_start has to be from the month before
date_start >= date_trunc('month', current_date - interval '1' month)
AND
date_start < date_trunc('month', current_date);
FOR conference IN conferenceset
LOOP
---fill up the count_participants column for the conference
conference.participants_count :=
SELECT COUNT(*)
FROM participant_conference JOIN conferenceset
ON participant_conference.conference_id = conferenceset.conferenceid;
---calculate the total amount for that conference
conference.total_amount := somerec.participants_count*(conference.price_per_person-conference.deduction_per_participant);
END LOOP;
----we still don't have the name of the suppliers of these conferences
CREATE TABLE finalresultset AS -- temporary table again
SELECT conference_supplier.name, conferenceset.total_amount
FROM conferenceset JOIN conference_supplier
ON conferenceset.conference_supplier_id = conference_supplier.id
----we have conference records with their amounts and suppliers' names scattered all over this set
----return the result with the suppliers' names extracted and their total amounts calculated
FOR finalrecord IN (SELECT name,SUM(total_amount) AS amount FROM finalresultset GROUP BY name)
LOOP
supplier:=finalrecord.name;
due_amount:=finalrecord.amount;
RETURN NEXT;
END LOOP;
END; $$
LANGUAGE 'plpgsql';
I don't know how and where to declare the variables that I need for the two FOR loops that I have: conference as type conferenceset and finalrecord whose type I'm not even sure of.
I guess nested blocks will be needed as well. It's my first stored procedure and I need help.
Thank you.

CREATE OR REPLACE FUNCTION due_payments_to_suppliers_previous_month()
RETURNS TABLE(supplier varchar,due_amount numeric)
AS $$
DECLARE
conference record;
finalrecord record;
BEGIN
CREATE TABLE conferenceset AS -- temporary table, so I can store the result set
SELECT
conference.conference_supplier_id,
conference.id AS conferenceid,
conference.price_per_person,
0 AS participants_count,
400 AS deduction_per_participant,
0 AS total_amount
FROM Conference WHERE --- date_start has to be from the month before
date_start >= date_trunc('month', current_date - interval '1' month)
AND
date_start < date_trunc('month', current_date);
FOR conference IN (select * from conferenceset)
LOOP
---fill up the count_participants column for the conference
conference.participants_count = (
SELECT COUNT(*)
FROM participant_conference JOIN conferenceset
ON participant_conference.conference_id = conferenceset.conferenceid
);
---calculate the total amount for that conference
conference.total_amount = somerec.participants_count*(conference.price_per_person-conference.deduction_per_participant);
END LOOP;
----we still don't have the name of the suppliers of these conferences
CREATE TABLE finalresultset AS -- temporary table again
SELECT conference_supplier.name, conferenceset.total_amount
FROM conferenceset JOIN conference_supplier
ON conferenceset.conference_supplier_id = conference_supplier.id
----we have conference records with their amounts and suppliers' names scattered all over this set
----return the result with the suppliers' names extracted and their total amounts calculated
FOR finalrecord IN (SELECT name,SUM(total_amount) AS amount FROM finalresultset GROUP BY name)
LOOP
supplier = finalrecord.name;
due_amount = finalrecord.amount;
RETURN NEXT;
END LOOP;
END; $$
LANGUAGE 'plpgsql';

Related

Optimizing an insert/update loop in a stored procedure

I have two tables wholesaler_catalog and wholesaler_catalog_prices. The latter has a foreign key reference to the former.
wholesaler_catalog_prices has a column called cost_type which can be either RETAIL or DISCOUNT.
Consider row Foo in wholesaler_catalog. Foo has two entries in wholesaler_catalog_prices - one for RETAIL and one for DISCOUNT. I want to split up Foo into Foo1 and Foo2, such that Foo1 points to RETAIL and Foo2 points to DISCOUNT. (The reasons for doing this are complex which I won't go into - it's part of a larger migration)
I have made a stored procedure that looks like this:
do
$$
declare
f record;
new_id int;
begin
for f in select catalog_id from
(select catalog_id, cost_type, row_number() over (partition by catalog_id) from wholesaler_catalog_prices
group by catalog_id, cost_type
order by catalog_id) as x
where row_number > 1
loop
insert into wholesaler_catalog
(item_number, name, catalog_log_id)
select item_number, name, catalog_log_id from wholesaler_catalog
where id = f.catalog_id
returning id into new_id;
-- RAISE NOTICE '% copied to %', f.catalog_id, new_id;
update wholesaler_catalog_prices set catalog_id = new_id where catalog_id = f.catalog_id and cost_type = 'RETAIL';
end loop;
end;
$$
The problem is that there are about 100k such records and it takes a very long time to run (I cancelled the run after 30 minutes). Is there anyway I can optimize the procedure to run faster?

How to compare two table value using if condition in function of Postgres

create or replace function trace.get_latest_exception_custom_msg(id varchar)
returns varchar
language plpgsql
as $$
declare
msg varchar ;
begin
perform t1.message, t1.created_time from table_1 t1 where t1.id = id order by t1.created_time desc limit 1;
perform t2.message, t2.created_time from table_2 t2 where t2.id = id order by t2.created_time desc limit 1;
if date(t1.created_time ) >= date(t2.created_time) then msg= t1.message;
elsif d date(t1.created_time ) < date(t2.created_time) then msg= t1.message;
else msg =t1.message;
end if;
return msg;
end;
while i call this function it give error ERROR: missing FROM-clause entry for table "t_1
You need to store the result of the two SELECT queries into variables in order to be able to be able to use them in an IF statement.
Your IF statement is also a bit confusing as all three parts assign the same value to msg. I assume that you want to use t2.message at least in one case.
create or replace function trace.get_latest_exception_custom_msg(p_id varchar)
returns varchar
language plpgsql
as
$$
declare
t1_msg varchar;
t1_created date;
t2_msg varchar;
t2_created date;
msg varchar;
begin
select t1.message, t1.created_time::date
into t1_msg, t1_created
from table_1 t1
where t1.id = p_id
order by t1.created_time desc
limit 1;
select t2.message, t2.created_time::date
into t2_msg, t2_created
from table_2 t2
where t2.id = p_id
order by t2.created_time desc
limit 1;
if t1_created >= t2_created then
msg := t1_msg;
elsif t1_created < t2_created then
msg := t2_msg; --<< ???
else
-- this can only happen if one (or both) of the DATEs is NULL.
msg := t1_msg;
end if;
return msg;
end;
$$

POSTGRES UPDATE ON CONFLICT. Error cannot affect a row a second time (dupes) ONLY OCCURS IN FUNCTION. Not as query

I have a query in a function that does not seem to be returning any duplicates from my checks and if ran as a separate query... it works! If ran within a stored function, it gives the error ON CONFLICT DO UPDATE command cannot affect row a second time.
This makes no sense.
CREATE OR REPLACE FUNCTION rollups.compute_daily_rollups_every_hour(
start_time timestamp without time zone,
end_time timestamp without time zone)
RETURNS void
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
BEGIN
RAISE NOTICE 'Computing daily rollups from % to % (excluded)', start_time, end_time;
RAISE NOTICE 'Aggregating data into daily_rollup';
EXECUTE $$
INSERT INTO rollups.daily_rollup
SELECT
COALESCE(visitors, 0) AS visitors, COALESCE(total_dwell_time, 0) AS total_dwell_time, d.datestamp::date, doorway_id, customer_id, centre_id, postcode, gender, age_group, house_income, no_children, no_cars, shopping_frequency, marital_status, employment_status
FROM (
Select date_trunc('day', (current_date - offs)) AS datestamp
FROM generate_series(0, 365, 1) AS offs
) AS D
LEFT OUTER JOIN (
SELECT cv.datestamp,
round((date_part('epoch'::text, sum(cv.dwell_time)) / 60::double precision)::numeric, 2) AS total_dwell_time,
count(cv.sensor_id) AS visitors,
cv.doorway_id,
cv.customer_id,
cv.centre_id,
cv.gender,
cv.postcode,
cv.age_group,
cv.no_children,
cv.no_cars,
cv.marital_status,
cv.employment_status,
cv.shopping_frequency,
cv.house_income
FROM rollups.some_rollup cv
WHERE cv.dwell_time > '00:00:30'::interval
GROUP BY cv.datestamp, cv.doorway_id, cv.customer_id, cv.centre_id, cv.gender, cv.postcode, cv.age_group, cv.no_children, cv.no_cars, cv.marital_status, cv.employment_status, cv.shopping_frequency, cv.house_income
) AS t1
ON d.datestamp::date = t1.datestamp::date
WHERE d.datestamp >= $1 AND d.datestamp < $2
ORDER BY d.datestamp
ON CONFLICT (datestamp, doorway_id, customer_id, centre_id, gender, postcode, age_group, no_children, no_cars, marital_status, employment_status, shopping_frequency, house_income)
DO UPDATE SET visitors=excluded.visitors, total_dwell_time = excluded.total_dwell_time;$$
USING start_time, end_time;
END;
$BODY$;

Transpose generate series date postgresql

Questions about transpose are asked many times before, but I cannot find any good answer when using generate_series and dates, because the columns may vary.
WITH range AS
(SELECT to_char(generate_series('2015-01-01','2015-01-05', interval '1 day'),'YYYY-MM-DD'))
SELECT * FROM range;
The normal output from generate series is:
2015-12-01
2015-12-02
2015-12-03
... and so on
http://sqlfiddle.com/#!15/9eecb7db59d16c80417c72d1e1f4fbf1/5478
But I want it to be columns instead
2015-12-01 2015-12-02 2015-12-03 ...and so on
It seems that crosstab maybe should do the trick, but I only get errors:
select * from crosstab('(SELECT to_char(generate_series('2015-01-01','2015-01-05', interval '1 day'),'YYYY-MM-DD'))')
as ct (dynamic columns?)
How do I get crosstab to work with generate_series(date-date) and different intervals dynamically?
TIA
Taking Reference from link PostgreSQL query with generated columns.
you can generate columns dynamically:
create or replace function sp_test()
returns void as
$$
declare cases character varying;
declare sql_statement text;
begin
drop table if exists temp_series;
create temporary table temp_series as
SELECT to_char(generate_series('2015-01-01','2015-01-02', interval '1 day'),'YYYY-MM-DD') as series;
select string_agg(concat('max(case when t1.series=','''',series,'''',' then t1.series else ''0000-00-00'' end) as ','"', series,'"'),',') into cases from temp_series;
drop table if exists temp_data;
sql_statement=concat('create temporary table temp_data as select ',cases ,'
from temp_series t1');
raise notice '%',sql_statement;
execute sql_statement;
end;
$$
language 'plpgsql';
Call function in following way to get output:
select sp_test(); select * from temp_data;
Updated Function which takes two date paramaeters:
create or replace function sp_test(start_date timestamp without time zone,end_date timestamp without time zone)
returns void as
$$
declare cases character varying;
declare sql_statement text;
begin
drop table if exists temp_series;
create temporary table temp_series as
SELECT to_char(generate_series(start_date,end_date, interval '1 day'),'YYYY-MM-DD') as series;
select string_agg(concat('max(case when t1.series=','''',series,'''',' then t1.series else ''0000-00-00'' end) as ','"', series,'"'),',') into cases from temp_series;
drop table if exists temp_data;
sql_statement=concat('create temporary table temp_data as select ',cases ,'
from temp_series t1');
raise notice '%',sql_statement;
execute sql_statement;
end;
$$
language 'plpgsql';
Function call:
select sp_test('2015-01-01','2015-01-10'); select * from temp_data;

PL/pgSQL: Comparing Successive Rows

I have the function, get_untracked_moves, below. My goal is to, for all data between two date ranges, find successive events which are farther than p_separation_distance apart.
E.g.:
If event 1 and event 2 are 40 m apart when p_separation_distance is 100m, a record would be returned with event 1's associated cont_name as the source_name and event 2's cont_name as the target_name.
CREATE FUNCTION get_untracked_moves(IN p_since_date TIMESTAMP WITHOUT TIME ZONE, IN p_before_date TIMESTAMP WITHOUT TIME ZONE, IN p_separation_distance INTEGER)
RETURNS TABLE ( id INTEGER,
asset_name CHARACTER VARYING,
source_name CHARACTER VARYING,
target_name CHARACTER VARYING,
source_time TIMESTAMP WITHOUT TIME ZONE,
target_time TIMESTAMP WITHOUT TIME ZONE,
source_lat DOUBLE PRECISION,
source_lon DOUBLE PRECISION,
target_lat DOUBLE PRECISION,
target_lon DOUBLE PRECISION ) AS $$
DECLARE
d_previous_location GEOMETRY;
d_previous_name CHARACTER VARYING;
d_previous_time TIMESTAMP WITHOUT TIME ZONE;
d_cur record;
BEGIN
-- Begin # 0,0
d_previous_location := st_setsrid(st_makepoint(0,0), 4326);
d_previous_name := '';
d_previous_time := NULL;
FOR d_cur
IN
SELECT
rank() OVER (PARTITION BY events.asset_id ORDER BY events.event_time) AS idx,
tags.id asset_id,
tags.name asset_name,
d_previous_name,
conts.name cont_name,
events.position,
events.event_time evt_time
FROM
events
JOIN
assets tags ON tags.id = events.asset_id
JOIN
assets conts ON conts.id = events.container_asset_id
WHERE
events.event_time >= p_since_date
AND
events.event_time <= p_before_date
LOOP
IF (d_previous_time = NULL) THEN
d_previous_time := events.event_time;
END IF;
IF (st_distancesphere(events.position, d_previous_location)>=p_separation_distance) THEN
RETURN NEXT;
END IF;
d_previous_location := events.position;
d_previous_name := conts.name;
d_previous_time := events.event_time;
END LOOP;
END;
$$
LANGUAGE plpgsql VOLATILE;
The function creates fine, but when I go to run it with:
select * from get_untracked_moves('2015-11-1', '2015-12-1', 10000);
I get:
ERROR: missing FROM-clause entry for table "events"
LINE 1: SELECT (st_distancesphere(events.position, d_previous_locati...
^
QUERY: SELECT (st_distancesphere(events.position, d_previous_location)>=p_separation_distance)
CONTEXT: PL/pgSQL function "get_untracked_moves" line 41 at IF
********** Error **********
ERROR: missing FROM-clause entry for table "events"
SQL state: 42P01
Context: PL/pgSQL function "get_untracked_moves" line 41 at IF
What am I missing here? I thought the inclusion of FROM events in my SELECT statement was enough.
Each pass of the loop is given the value of the record containing the corresponding row of the select result set. So events is not visible inside the loop. In instead use d_cur.position to refer to that column.
BTW, as commented to your question, you should really use the lag window function and get rid of the messy loop.
As a suggestion check this query:
select idx, asset_id, asset_name, previous_name, cont_name, position, evt_time
from (
select
rank() over (partition by e.asset_id order by e.event_time) as idx,
st_distancesphere(
e.position,
lag(e.position, 1, e.position) over (order by e.event_time)
) >= p_separation_distance as b,
t.id as asset_id,
t.name as asset_name,
lag(c.name, 1) as previous_name,
c.name as cont_name,
e.position,
e.event_time as evt_time
from
events e
inner join
assets tags on t.id = e.asset_id
inner join
assets c on c.id = e.container_asset_id
where
e.event_time >= p_since_date
and
e.event_time <= p_before_date
) s
where b