Trigger function for table with multiple joins PostgreSQL - postgresql

I'm new to PostgreSQL and I didn't find any questions specifically for this issue.
I'm trying to create a PostgreSQL trigger with a table which contains joins.
Table A
Salesman_id
Salesman name
1
John Snow
2
Jennifer
3
Cleiton
Table B
Salesman_id
order value
item
order_id
1
500
Bananas
1
2
1000
tomatos
20
3
5000
Tigers
200
Joined Table A-B (Selected couple columns)
Salesman name
Order Value
John Snow
500
Jennifer
1000
Cleiton
5000
my code for this trigger:
CREATE OR REPLACE FUNCTION atualiza_tabela()
RETURNS TRIGGER AS $$
BEGIN
if new.order_id<>old.order_id then
INSERT INTO Table A-B (Salesman_id, Salesman name, order value, item, order id)
VALUES (NEW.Salesman_id, NEW.Salesman name, NEW.order value, NEW.item, NEW.order id);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
Is there any syntax error or another kind of error in my code?
It returns:
ERROR: syntax error at or near ";"
LINE 35: END;
^
SQL state: 42601
Character: 748

Related

How do I cap number of rows in based on category using postgres policies?

I have a database of orders and each order has a time_slot (of type TIME).
select id, time_slot from orders limit 5;
10 | 13:00:00
11 | 12:00:00
13 | 11:00:00
14 | 12:30:00
15 | 11:30:00
I want to make sure that only a certain number of orders can be placed in a time slot; for example, let's say I only want 8 orders per time slot.
I am using Supabase, so I would like to implement using RLS policies.
I've been through several iterations, but none of them have worked. Some complain of infinite recursion. My current approach is the following: I have created a view of time slot load.
create or replace view time_slot_load as
select time_slot, count(*)
from orders
group by time_slot;
select * from time_slot_load limit 5;
11:00:00 | 1
12:30:00 | 1
11:30:00 | 1
13:00:00 | 1
12:00:00 | 7
I can then create a policy that checks against this view.
ALTER POLICY "Only 8 orders per time slot"
ON public.orders
WITH CHECK (
(SELECT (load.count <= 8)
FROM time_slot_load load
WHERE (load.time_slot = orders.time_slot))
);
But this is not working.
Is there some way I can do this using constraints or RLS policies?
Any help is appreciated.
demo
table structure:
begin;
create table orders_count(
time_slot tstzrange ,
order_count integer default 0,
constraint order_ct_max1000 check(order_count <=4));
create table
orders(orderid int primary key, realtimestamp timestamptz , order_info text);
with a as(
SELECT x as begin_,x + interval '1 hour' as end_
FROM generate_series(
timestamp '2022-01-01',
timestamp '2022-01-02',
interval '60 min') t(x))
insert into orders_count(time_slot)
select tstzrange(a.begin_, a.end_,'[]') from a;
commit;
two table, one table for all the orders info, another table is track the timeslot's count, also make one constraint, within one timeslot no more than 4 orderid.
Then create trigger for delete/update/insert action on table orders.
for table orders_count, 20 years only 175200 rows (one hour one row).
main trigger function:
begin;
create or replace function f_trigger_orders_in()
returns trigger as
$$
begin
update orders_count set order_count = order_count + 1
where time_slot #> NEW.realtimestamp;
return null;
end;
$$ language plpgsql;
create or replace function f_trigger_orders_up()
returns trigger as
$$
begin
if OLD.realtimestamp <> NEW.realtimestamp THEN
update orders_count
set order_count = order_count -1
where time_slot #> OLD.realtimestamp;
update orders_count
set order_count = order_count + 1
where time_slot #> NEW.realtimestamp;
end if;
return null;
end
$$ language plpgsql;
create or replace function f_trigger_order_delaft()
returns trigger as
$$
BEGIN
update orders_count set order_count = order_count -1 where time_slot #> OLD.realtimestamp;
return null;
end;
$$
language plpgsql;
triggers action:
create trigger trigger_in_orders
AFTER INSERT ON public.orders FOR EACH ROW execute procedure f_trigger_orders_in();
create trigger trigger_up_orders
after update on public.orders for each row execute procedure f_trigger_orders_up();
create trigger trigger_del_orders
AFTER DELETE ON public.orders FOR EACH ROW execute procedure f_trigger_order_delaft();
I want to make sure that only a certain number of orders can be placed
in a time slot; for example, let's say I only want 8 orders per time
slot.
You cannot do that in PostgreSQL.
https://www.postgresql.org/docs/current/sql-createpolicy.html
check_expression:
Any SQL conditional expression (returning boolean). The conditional
expression cannot contain any aggregate or window functions. This
expression will be used in INSERT and UPDATE queries against the table
if row-level security is enabled. Only rows for which the expression
evaluates to true will be allowed. An error will be thrown if the
expression evaluates to false or null for any of the records inserted
or any of the records that result from the update. Note that the
check_expression is evaluated against the proposed new contents of the
row, not the original contents.
Why view won't work:
https://www.postgresql.org/docs/current/sql-createview.html
see Updatable Views section:
A view is automatically updatable if it satisfies all of the following
conditions:
The view's select list must not contain any aggregates, window functions or set-returning functions.

Postgres handle constraint conflict then insert excluded row

If I have a table something like:
MyTable
id date value replaced_by_id
1 2020-01-01 10
2 2020-01-02 20 3
3 2020-01-02 21
With a unique constraint on date and replaced_by
e.g.
CREATE UNIQUE INDEX my_table_null_test ON my_table (date, (replaced_by_id is null))
WHERE replaced_by_id IS NULL;
How can I create an insert statement that sets the conflicting row's replaced_by_id to the the new rows id and then inserts the new row afterwards?
Along the lines of
insert into my_table (id, date, value) values (gen_id(), '2020-01-02', 21)
on conflict (date, (replaced_by_id is null) ) where replaced_by_id is null
do update
set replaced_by_id = excluded.id
**now insert the new row (insert the excluded row)**
for say the file the values are coming from a file that had many values for the same date. e.g.
date value
2020-01-01 10
2020-01-02 20
2020-01-02 21
2020-01-02 22
2020-01-02 23
2020-01-02 24
2020-01-02 22
would result in
MyTable
id date value replaced_by_id
1 2020-01-01 10
2 2020-01-02 20 3
3 2020-01-02 21 4
4 2020-01-02 22 5
5 2020-01-02 23 6
6 2020-01-02 24 7
7 2020-01-02 22
I think you can not use UPSERT for this requirement because UPSERT will perform only update operation on same table or DO Nothing.
Reason is:
You have added unique index which will prevent the insertion with combination of existing date and null of replace_by_id field.
You are trying to get the ID to update the existing records before insertion which is not possible.
So you have to use some workaround for it:
Using Triggers - You have to use 2 triggers on your table one is before insert and one is after insert like below:
Before Insert Trigger
Trigger Function
CREATE FUNCTION trig_beforeinsert()
RETURNS trigger
LANGUAGE 'plpgsql'
AS $BODY$
declare
flg int :=0;
begin
flg=(select count(*) from my_table where date_=new.date_ and replaced_by_id is null);
if flg>0 then
new.replaced_by_id=0;
end if;
return new;
end;
$BODY$;
Trigger on Table
CREATE TRIGGER my_table_before_insert
BEFORE INSERT
ON my_table
FOR EACH ROW
EXECUTE PROCEDURE trig_beforeinsert();
After Insert Trigger
Trigger Function
CREATE FUNCTION trig_afterinsert()
RETURNS trigger
LANGUAGE 'plpgsql'
COST 100
VOLATILE NOT LEAKPROOF
AS $BODY$
declare
flg int :=0;
begin
if new.replaced_by_id = 0 then
UPDATE my_table set replaced_by_id=new.id where date_=new.date_ and replaced_by_id is null;
UPDATE my_table set replaced_by_id=null where id=new.id;
end if;
return new;
end;
$BODY$;
Trigger on Table
CREATE TRIGGER my_table_after_insert
AFTER INSERT
ON public.my_table
FOR EACH ROW
EXECUTE PROCEDURE public.trig_afterinsert();
DEMO

Postgres trigger function to update aggregated result in another table

I have two tables. Table x and table y. Table x gets updated everyday. I wish to update the table y as soon as new data is inserted in Table x. Table y contains the aggregated value of all the update in Table x each day.
Date is in Date type and the rest of the two column are of real type.
Table_x can be updated daily and table_y should be updated automatically.
Table x:
Date Sales product
12/12/2017 4000 2
12/12/2017 3000 1
12/12/2017 2000 1
12/12/2017 5000 3
11/12/2017 1000 3
11/12/2017 2000 4
Table y (to be as updated as shown below):
Date Sales product
12/12/2017 14000 7
11/12/2017 3000 7
I wrote the trigger function as shown below but it updates each item rather than aggregated value.
CREATE OR REPLACE FUNCTION public.rec_insert_table_y()
RETURNS trigger AS
$BODY$
BEGIN
INSERT INTO table_y ("Date","Sales","product")
SELECT NEW."Date",(sum(NEW."Sales")),(sum(NEW."product"))
GROUP BY NEW."Date";
RETURN NEW;
Trigger Function :
CREATE TRIGGER insert_into_table_y
AFTER INSERT
ON public.table_x
FOR EACH ROW
EXECUTE PROCEDURE public.rec_insert_table_y();
You can write a trigger that updates the aggregate value if exists or inserts if not.
Also you must be aware updates and deletes in the 'x' table:
create function y_x_trg() returns trigger
language plpgsql
as
$body$
declare
row_exists boolean;
begin
if tg_op<>'INSERT' then
update y
set sales = y.sales - old.sales,
product = y.product - old.product
where y.date = old.date
returning true into row_exists;
end if;
if tg_op<>'DELETE' then
update y
set sales = y.sales + new.sales,
product = y.product + new.product
where y.date = new.date
returning true into row_exists;
if row_exists is not true then
insert into y values (new.date, new.sales, new.product);
end if;
return new;
else
return null;
end if;
end;
$body$;
create trigger y_x_trg AFTER INSERT OR UPDATE OR DELETE ON x
FOR EACH ROW EXECUTE PROCEDURE y_x_trg();
You can see a running example at http://rextester.com/FVR79644

Trigger function does not update the first row of the table

I am working in PostgreSQL 9.5 where I have these two tables:
Table incident:
oid integer
description integer,
geom geometry(Point,4326),
And table grid:
oid integer,
number integer,
geom geometry(Polygon,4326)
Everytime a point is added to table incident I want the field description to be filled with the grid number that the point is intersecting. To achieve this I created this trigger:
CREATE OR REPLACE FUNCTION get_ortofoto_incidentes()
RETURNS TRIGGER AS
$BODY$
BEGIN
SELECT g.number INTO NEW.description
FROM incident AS i, grid AS g
WHERE st_intersects(new.geom, g.geom) ; --
RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsq;
And added it to the table incident:
CREATE TRIGGER get_ortofoto_incidentes
BEFORE INSERT OR UPDATE ON incidentestest
FOR each row
EXECUTE PROCEDURE get_ortofoto_incidentes();
The problem is that when the trigger function is called the attribute description remains null for the first row, but after the trigger works as expected. This is what I am getting:
oid | description
1 | null
2 | 236541
3 | 695489
4 | 325687
... | ....
Any idea why the trigger is not updating for the first row of the table?
Thank you
I'd say that it is a coincidence that this happens only for the first inserted row. It is completely ok for description to be set to NULL if the first matching row in grid has NULL in number.
I also want to warn you that the query in your trigger function builds a Cartesian product between the matching rows from grid and the table incident, since there is no WHERE condition restricting the latter. Remove the incident AS i from the query.

get multiple columns of different table in function by using joins in postgres

Here is the example with table with table name 'company'
id name age address
1 Paul 32 California
Ans second table name "department"
id dept_name dept_id
1 engineering 5
From the above table if I do joins by combining id column and retrieving the fields as name, age,,address,dept.
Here wat I did is:-
create type tmp_comp as(name text,age integer, address character, dept character);
create or replace function comp_detail (in_id integer)
returns tmp_comp as
$$
declare
out_put tmp_comp%rowtype;
begin
select name, age, address,dept from company as c inner join department as d on c.id=d.id where c.id=in_id;
return out_put;
end;$$
LANGUAGE plpgsql
If I execute this it'll run success.
but if I want to call the function, it ll show error...
select * from comp_detail(1)
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT: PL/pgSQL function comp_detail(integer) line 7 at SQL statement
You can use RETURN QUERY to return a SETOF A_TYPE as described here
create or replace function comp_detail (in_id integer) returns setof tmp_comp as $$
declare out_put tmp_comp%rowtype;
begin
return query(select name, age, address,dept from company as c inner join department as d on c.id=d.id where c.id=in_id);
end;$$
LANGUAGE plpgsql
And you can call your function as follow:
select * from comp_detail (1);