PL/pgSQL: Comparing Successive Rows - postgresql

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

Related

Return entire row from table and columns from other tables

I'm using postgresql 14 and trying to return an entire record from a table in addition to columns from different tables. The catch is, that I don't want to write all the titles in the record. I tried working with the guide lines here [1], but I'm getting different errors. Can anyone tell me what I'm doing wrong?
CREATE OR REPLACE FUNCTION public.get_license_by_id(license_id_ integer)
RETURNS TABLE(rec tbl_licenses, template_name character varying, company_name character varying)
LANGUAGE 'plpgsql'
COST 100
VOLATILE SECURITY DEFINER PARALLEL UNSAFE
ROWS 1000
AS $BODY$
DECLARE
BEGIN
CREATE TEMPORARY TABLE tempTable AS (
SELECT
(rec).*, B.company_name, C.template_name
-- Tried using A instead of (rec).* with error: column "a" has pseudo-type record
FROM
(
(SELECT * FROM tbl_licenses) A
LEFT JOIN
(SELECT * FROM tbl_customers) B on A.customer_id = B.customer_id
LEFT JOIN
(SELECT * FROM tbl_templates) C on A.template_id = C.template_id
)
);
UPDATE tempTable
SET license = '1'
WHERE tempTable.license IS NOT NULL;
RETURN QUERY (
SELECT * FROM tempTable
);
DROP TABLE tempTable;
RETURN;
END;
$BODY$;
I'm calling the function like SELECT rec FROM get_license_by_id(1);
but getting:
ERROR: structure of query does not match function result type
DETAIL: Returned type integer does not match expected type tbl_licenses in column 1.
You need to cast the A alias to the correct record type. However the nested derived tables are not necessary for the other tables. If you use a coalesce() for the license column in the SELECT, then you get get rid of the inefficient creation and update of the temp table as well.
CREATE OR REPLACE FUNCTION get_license_by_id(license_id_ integer)
RETURNS TABLE(rec tbl_licenses, template_name character varying, company_name character varying)
LANGUAGE sql
STABLE
AS $BODY$
SELECT a::tbl_licenses, -- this is important
B.company_name, C.template_name
FROM (
select license_id, customer_id, ... other columns ...,
coalesce(license, '1') as license -- makes the temp table unnecessary
from tbl_licenses A
) a
LEFT JOIN tbl_customers B on A.customer_id = B.customer_id
LEFT JOIN tbl_templates C on A.template_id = C.template_id
where a.license_id = license_id_;
$BODY$
;

ERROR: structure of query does not match function result type, Returned type text does not match expected type geometry

Trying to write a function that returns the geometries of points interpolated in a line from a DEM and elevations but getting this error ERROR: structure of query does not match function result type. The query in the end of points should return the geometries of the startpoint, endpoint and interpolated points of an interval. This is my function:
test_get_slope_profile
CREATE OR REPLACE FUNCTION test_get_slope_profile(ways_id bigint, interval_ float default 10, way_table TEXT DEFAULT 'ways')
RETURNS TABLE(p_geom geometry, elevation float)
LANGUAGE plpgsql
AS $function$
DECLARE
way_geom geometry;
length_meters float;
length_degree NUMERIC;
translation_m_degree NUMERIC;
BEGIN
IF way_table = 'ways' THEN
SELECT geom, length_m, ST_Length(geom)
INTO way_geom, length_meters, length_degree
FROM ways
WHERE id = ways_id;
ELSEIF way_table = 'ways_userinput' THEN
SELECT geom, length_m, ST_Length(geom)
INTO way_geom, length_meters, length_degree
FROM ways_userinput
WHERE id = ways_id;
END IF;
translation_m_degree = length_degree/length_meters;
DROP TABLE IF EXISTS dump_points;
IF length_meters > (2*interval_) THEN
CREATE TEMP TABLE dump_points AS
SELECT (ST_DUMP(ST_Lineinterpolatepoints(way_geom,interval_/length_meters))).geom AS geom;
ELSEIF length_meters > interval_ AND length_meters < (2*interval_) THEN
CREATE TEMP TABLE dump_points AS
SELECT ST_LineInterpolatePoint(way_geom,0.5) AS geom;
interval_ = length_meters/2;
ELSE
CREATE TEMP TABLE dump_points AS
SELECT NULL::geometry AS geom;
END IF;
RETURN query
WITH points AS
(
SELECT ROW_NUMBER() OVER() cnt, geom, length_meters
FROM (
SELECT st_startpoint(way_geom) AS geom
UNION ALL
SELECT geom FROM dump_points
UNION ALL
SELECT st_endpoint(way_geom)
) x
)
SELECT 'geom', SUM(idw.val/(idw.distance/translation_m_degree))/SUM(1/(idw.distance/translation_m_degree))::real AS elev
FROM points p, get_idw_values(geom) idw
WHERE p.geom IS NOT NULL
GROUP BY cnt
ORDER BY cnt;
END;
$function$;
ERROR: structure of query does not match function result type
DETAIL: Returned type text does not match expected type geometry in column 1.
CONTEXT: PL/pgSQL function test_get_slope_profile(bigint,double precision,text) line 38 at RETURN QUERY
SQL state: 42804
In the RETURN query statement, don't do SELECT 'geom', SUM(...))::real AS elev but rather SELECT geom, SUM(...))::real AS elev
The former returns the text 'geom' while the later returns the column content, i.e. the geometry

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$;

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

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';