Trigger function to insert into different table with a join - postgresql

Basically I've got 3 tables, table1 with line geometry, table2 with polygon geometry and table3 which is supposed to be a mashup of both
I'm trying to create a trigger that will insert into table3 whenever someone inserts into table1 with a join on table2 but my trigger isn't doing anything
table1
cid integer
type text
geom geometry linestring
table2
pid integer
name text
geom geometry polygon
table3
cid, type,pid,name
The easiest solution would be to have a view between table1 and table2 if they were small datasets, however they are both quite large so that's why I need to use table3. Problem is that my current query isnt doing anything
Current function
CREATE OR REPLACE FUNCTION insert_newstuff()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
DECLARE
BEGIN
INSERT INTO table3 (cid, type,pid,name)
select NEW.cid, new.type, pid, p.name
from table1 c left join table2 p ON st_intersects(new.geom, p.geom)
;
RETURN NEW;
END;
$function$
;
The trigger query
`create trigger insert_newstuff_trigger before
insert on table1 for each row execute function insert_newstuff()
Problem is when inserting, nothing happens. Although it did trigger a vaccuum on table3 for some reason. How to fix the function?

Related

PostGIS update trigger

I have table_1 having column point_id and geometry (Point) ,
table_2 has got columns: area_id, geometry ( Polygon) and a table_3 which contains list of all the point_id from the table_1 and area_id from table_2 , if point within the polygon.
I am trying to update table_3 every time new polygon is added to table_2, so the point_id and area_id are populated for every point from table_1 which is within new polygon from table_2
CREATE OR REPLACE FUNCTION locations_update()
RETURNS TRIGGER AS $func$
DECLARE
BEGIN
insert into table_3 (point_id,area_id)
select
table_1.point_id , new.table_2.area_id
from
table_1
join table_2
on
ST_WITHIN(table_1.geometry , new.table_2.geometry) ;
RETURN new;
END;$func$
language plpgsql;
CREATE TRIGGER locations_update_tr AFTER INSERT ON table_2
FOR EACH ROW EXECUTE PROCEDURE locations_update();
What updates to the code above needed to avoid unique constraint error
ERROR: duplicate key value violates unique constraint
DETAIL: Key (point_id, area_id)=(1, 1) already exists.
Thanks
You are joining the two tables, but the join condition is only considering the 1st table so it ends up being a cross-join.
In fact, you don't need to join at all since all the required information is in the NEW object.
SELECT
table_1.point_id, NEW.table_2.area_id
FROM
table_1
WHERE
ST_WITHIN(table_1.geometry, NEW.table_2.geometry) ;
PS: and of course you have another trigger to remove entry from table 3 when one is deleted in table 1 or 2.

Postgres - SELECT FOR UPDATE with union all

I am trying to put row level lock on a table in one postgres function.
do $$
declare tabname text :='locktest' ;
begin
execute 'create temp table temp1 as
select v.* from (
select row_number() over (partition by a.id) as row_num,a.*
from '||tabname||' a,locktest2 b where a.id=b.id and b.val=111
union all
select row_number() over (partition by a.id) as row_num,a.*
from '||tabname||' a,locktest2 b where a.id=b.id and b.val=222
)v where v.row_num=1 for update';
raise notice 'Completed';
end $$;
But while compiling it , getting below error.
ERROR: FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT
Please suggest.
The way the statement is written, the database does not know in which table to lock the rows.
Rewrite the query along these lines:
SELECT ... FROM
(SELECT ...
FROM tab1
WHERE ...
FOR NO KEY UPDATE) AS t1
UNION ALL
(SELECT ...
FROM tab2
WHERE ...
FOR NO KEY UPDATE) AS t2;
FOR NO KEY UPDATE is the correct lock if you plan to update. FOR UPDATE is the lock if you intend to delete.

Postgres INSERT ON CONFLICT behavior with INSTEAD OF trigger

Suppose I have the following tables and view:
CREATE TABLE table_a(
field_x INTEGER PRIMARY KEY,
id SERIAL UNIQUE
);
CREATE TABLE table_b(
a_id INTEGER PRIMARY KEY REFERENCES table_a(id),
field_y INTEGER NOT NULL
);
CREATE VIEW v AS SELECT * FROM table_a JOIN table_b ON table_a.id=table_b.a_id;
I want to be able to insert into the view, so I created the following function and trigger:
CREATE FUNCTION insert_into_view()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $function$
DECLARE new_id INTEGER;
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO table_a (field_x) VALUES (NEW.field_x) ON CONFLICT DO NOTHING RETURNING id INTO new_id;
IF new_id IS NULL THEN
SELECT id FROM table_a WHERE field_x=NEW.field_x INTO new_id;
END IF;
INSERT INTO table_b (a_id, field_y) VALUES (new_id, NEW.field_y);
END IF;
RETURN NEW;
END;
$function$;
CREATE TRIGGER view_insert_trigger
INSTEAD OF INSERT ON
v FOR EACH ROW EXECUTE PROCEDURE insert_into_view();
Now I want to insert values into the view only if there does not exist a row for field_x yet in the view, e.g.:
INSERT INTO v (field_x, field_y) VALUES (5,6);
INSERT INTO v (field_x, field_y) VALUES (5,8) ON CONFLICT DO NOTHING;
Where I want the second insert to silently do nothing. However, I get this:
ERROR: duplicate key value violates unique constraint "table_b_pkey"
DETAIL: Key (a_id)=(2) already exists.
CONTEXT: SQL statement "INSERT INTO table_b (a_id, field_y) VALUES (new_id, NEW.field_y)"
I know why I'm getting this error: the function insert_into_view does not specify ON CONFLICT behavior when inserting into table_b and by default the query fails. Thus my question: can I make the ON CONFLICT behavior to ripple from the view insert into the table insert? (I may want to specify different conflict behavior at a later time, so I don't want to hard-code this in the trigger function if I can avoid it.)
Thanks!
I'm still not sure If I understand you right. But I try:
if you change
INSERT INTO table_b (a_id, field_y) VALUES (new_id, NEW.field_y)
to
INSERT INTO table_b (a_id, field_y) VALUES (new_id, NEW.field_y) ON CONFLICT DO NOTHING
in function it will start working silently.
Regarding
INSERT INTO v (field_x, field_y) VALUES (5,8) ON CONFLICT DO NOTHING;
I think you can use ON CONFLICT only on tables with unique constraint, so and foreign table, and instead rule will ignore ON CONFLICT DO NOTHING and fail when you specify target_name of constraint_name

Can't drop temp table in Postgres function: "being used by active queries in this session"

It is expected to now take in a table called waypoints and follow through the function body.
drop function if exists everything(waypoints);
create function everything(waypoints) RETURNS TABLE(node int, xy text[]) as $$
BEGIN
drop table if exists bbox;
create temporary table bbox(...);
insert into bbox
select ... from waypoints;
drop table if exists b_spaces;
create temporary table b_spaces(
...
);
insert into b_spaces
select ...
drop table if exists b_graph; -- Line the error flags.
create temporary table b_graph(
...
);
insert into b_graph
select ...
drop table if exists local_green;
create temporary table local_green(
...
);
insert into local_green
...
with aug_temp as (
select ...
)
insert into b_graph(source, target, cost) (
(select ... from aug_temp)
UNION
(select ... from aug_temp)
);
return query
with
results as (
select id1, ... from b_graph -- The relation being complained about.
),
pkg as (
select loc, ...
)
select id1, array_agg(loc)
from pkg
group by id1;
return;
END;
$$ LANGUAGE plpgsql;
This returns cannot DROP TABLE b_graph because it is being used by active queries in this session
How do I go about rectifying this issue?
The error message is rather obvious, you cannot drop a temp table while it is being used.
You might be able to avoid the problem by adding ON COMMIT DROP:
Temporary table and loops in a function
However, this can probably be simpler. If you don't need all those temp tables to begin with (which I suspect), you can replace them all with CTEs (or most of them probably even with cheaper subqueries) and simplify to one big query. Can be plpgsql or just SQL:
CREATE FUNCTION everything(waypoints)
RETURNS TABLE(node int, xy text[]) AS
$func$
WITH bbox AS (SELECT ... FROM waypoints) -- not the fct. parameter!
, b_spaces AS (SELECT ... )
, b_graph AS (SELECT ... )
, local_green AS (SELECT ... )
, aug_temp AS (SELECT ... )
, b_graph2(source, target, cost) AS (
SELECT ... FROM b_graph
UNION ALL -- guessing you really want UNION ALL
SELECT ... FROM aug_temp
UNION ALL
SELECT ... FROM aug_temp
)
, results AS (SELECT id1, ... FROM b_graph2)
, pkg AS (SELECT loc, ... )
SELECT id1, array_agg(loc)
FROM pkg
GROUP BY id1
$func$ LANGUAGE sql;
Views are just storing a query ("the recipe"), not the actual resulting values ("the soup").
It's typically cheaper to use CTEs instead of creating temp tables.
Derived tables in queries, sorted by their typical overall performance (exceptions for special cases involving indexes). From slow to fast:
CREATE TABLE
CREATE UNLOGGED TABLE
CREATE TEMP TABLE
CTE
subquery
UNION would try to fold duplicate rows. Typically, people really want UNION ALL, which just appends rows. Faster and does not try to remove dupes.

postgresql column names as variable in a subquery

table1
id col1 col2 col3...
table2
col_id col_name
3432 col1
5342 col2
6756 col3
Now I want to generate table 3 like this:
id col_name col_value col_id
Please note that col1, col2,col3... are not in order. Therefore I have to query table2 to obtain col_id ( I think pivot does not work here)
How can I do it in SQL?
It appears that you want a select like this:
SELECT t2.id,
CASE
WHEN t2.col_name='col1' THEN t1.col1
WHEN t2.col_name='col2' THEN t1.col2
WHEN t2.col_name='col2' THEN t1.col2
-- ... more columns
ELSE NULL
END
FROM table2 t2 LEFT JOIN table2 t1 ON t2.col_id = t1.id
You could also create a function, though this will be slower in practice:
CREATE OR REPLACE FUNCTION table1_col(id integer, name text) RETURNS text as $$
DECLARE
col_val text;
BEGIN
EXECUTE format('SELECT %s FROM table1 WHERE id=$1', name)
INTO col_val
USING id;
RETURN col_val;
END;
$$ LANGUAGE plpgsql;
SELECT table1_col(col_id,col_name) FROM table2;