In a Details table (Product ID, Receipt ID) only products with a stock greater than 0 can be added.
Warehouse (Product ID, Stock). How can I implement this constraint in Postgresql?
-- CREATE THE FUNCTION
CREATE FUNCTION trg_product_stock_check()
RETURNS trigger AS
$func$
BEGIN
if exists (select * from warehouse w where w.product_id = new.product_id and stock <= 0) then
raise NOTICE 'Product must have a stock of greater than 0';
return null;
end if;
return new;
END
$func$ LANGUAGE plpgsql;
-- CREATE THE TRIGGER
CREATE TRIGGER product_stock_check
BEFORE INSERT ON "orders"
FOR EACH ROW EXECUTE PROCEDURE trg_product_stock_check();
The solution is to create a trigger function that's set up on insert of the order's table. First create your function. You want to check your warehouse table for the product's stock quantity in the function. If the product's stock quantity is less than or equal to 0, return null with a log message. Otherwise, return the newly created row (eg: the new keyword in the function is the row being inserted). After you create the function, you can set the trigger up to run on the orders table before insert.
You can read more about creating a trigger/function here and here.
Related
I want to create a trigger function, which copies certain columns of an recent updated row and deletes the old data. After that I want to insert the copied columns in exact the same table in the same row (overwrite). I need the data to be INSERTED because this function will be embedded in an existing program, with predefined Triggers.
That's what I have so far:
CREATE OR REPLACE FUNCTION update_table()
RETURNS TRIGGER AS
$func$
BEGIN
WITH tmp AS (DELETE FROM table
WHERE table.id = NEW.id
RETURNING id, geom )
INSERT INTO table (id, geom) SELECT * FROM tmp;
END;
$func$ language plpgsql;
CREATE TRIGGER T_update
AFTER UPDATE OF geom ON table
EXECUTE PROCEDURE update_table();
But I get the Error message:
ERROR: cannot perform DELETE RETURNING on relation "table"
HINT: You need an unconditional ON DELETE DO INSTEAD rule with a RETURNING clause.
Why I should use a rule here?
I'm using PostgreSQL 9.6
UPDATE:
A little bit of clarification. When I have two columns in my table (id, geom), after I updated geom I want to make a copy of this (new)row and insert it into the same table, while overwriting the updated row. (I'm not interested in any value before the update) I know that this is odd but I need this row to be inserted again because the program i embed this function in, listens to a INSERT statement and cannot be changed by me.
Right after you update a row, its old values will no longer be available. So, if you simply want to preserve the old row in case of an update you need to create a BEFORE UPDATE trigger, so that you can still access the OLD values and create a new row, e.g.
CREATE TABLE t (id int, geom geometry(point,4326));
CREATE OR REPLACE FUNCTION update_table() RETURNS TRIGGER AS $$
BEGIN
INSERT INTO t (id, geom) VALUES (OLD.id,OLD.geom);
RETURN NEW;
END; $$ LANGUAGE plpgsql;
CREATE TRIGGER t_update
BEFORE UPDATE OF geom ON t FOR EACH ROW EXECUTE PROCEDURE update_table();
INSERT INTO t VALUES (1,'SRID=4326;POINT(1 1)');
If you update the record 1 ..
UPDATE t SET geom = 'SRID=4326;POINT(2 2)', id = 2 WHERE id = 1;
UPDATE t SET geom = 'SRID=4326;POINT(3 3)', id = 3 WHERE id = 2;
.. you get a new record in the same table as you wished
SELECT id, ST_AsText(geom) FROM t;
id | st_astext
----+------------
1 | POINT(1 1)
2 | POINT(2 2)
3 | POINT(3 3)
Demo: db<>fiddle
Unrelated note: consider upgrading your PostgreSQL version! 9.6 will reach EOL in November, 2021.
First thanks to #JimJones for the answer. I´d like to post his answer modified for this purpose. This code "overwrites" the updated row by inserting a copy of itself and then deleting the old duplicate. That way I can Trigger on INSERT.
CREATE TABLE t (Unique_id SERIAL,id int, geom geometry(point,4326));
CREATE OR REPLACE FUNCTION update_table() RETURNS TRIGGER AS $$
BEGIN
INSERT INTO t (id, geom) VALUES (NEW.id,NEW.geom);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER t_update
BEFORE UPDATE OF geom ON t FOR EACH ROW EXECUTE PROCEDURE update_table();
CREATE OR REPLACE FUNCTION delete_table() RETURNS TRIGGER AS $$
BEGIN
DELETE FROM t a
USING t b
WHERE a.Unique_id < b.Unique_id
AND a.geom = b.geom;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER t_delete
AFTER UPDATE OF geom ON t FOR EACH ROW EXECUTE PROCEDURE delete_table();
INSERT INTO t VALUES (1,1,'SRID=4326;POINT(1 1)');
UPDATE t SET geom = 'SRID=4326;POINT(2 2)' WHERE id = 1;
I am looking for an elegant solution to this situation:
I have created a trigger function that updates the table supply with the sum of some detail rows, whenever a row is inserted or updated on warehouse_supplies.
PostgreSQL insert or update syntax allowed me to share the same function sync_supply_stock() for the insert and update conditions.
However, when I try to wire the after delete condition to the function it cannot be reused (although it is logically valid), for the returning object must be old instead of new.
-- The function I want to use for the 3 conditions (insert, update, delete)
create or replace function sync_supply_stock ()
returns trigger
as $$
begin
-- update the supply whose stock just changed in warehouse_supply with
-- the sum its stocks on all the warehouses.
update supply
set stock = (select sum(stock) from warehouse_supplies where supply_id = new.supply_id)
where supply_id = new.supply_id;
return new;
end;
$$ language plpgsql;
-- The (probably) unnecessary copy of the previous function, this time returning old.
create or replace function sync_supply_stock2 ()
returns trigger
as $$
begin
-- update the supply whose stock just changed in warehouse_supply with
-- the sum its stocks on all the warehouses.
update supply
set stock = (select sum(stock) from warehouse_supplies where supply_id = old.supply_id)
where supply_id = old.supply_id;
return old;
end;
$$ language plpgsql;
-- The after insert/update trigger
create trigger on_warehouse_supplies__after_upsert after insert or update
on warehouse_supplies for each row
execute procedure sync_supply_stock ();
-- The after delete trigger
create trigger on_warehouse_supplies__after_delete after delete
on warehouse_supplies for each row
execute procedure sync_supply_stock2 ();
Am I missing something or is there any fixing to duplicating sync_supply_stock2() as sync_supply_stock2()?
EDIT
For the benefit of future readers, following #bergi answer and discusion, this is a possible factorized solution
create or replace function sync_supply_stock ()
returns trigger
as $$
declare
_supply_id int;
begin
-- read the supply_id column from `new` on insert/update conditions and from `old` on delete conditions
_supply_id = coalesce(new.supply_id, old.supply_id);
-- update the supply whose stock just changed in of_warehouse_supply with
-- the sum its stocks on all the warehouses.
update of_supply
set stock = (select sum(stock) from of_warehouse_supplies where supply_id = _supply_id)
where supply_id = _supply_id;
-- returns `new` on insert/update conditions and `old` on delete conditions
return coalesce(new, old);
end;
$$ language plpgsql;
create trigger on_warehouse_supplies__after_upsert after insert or update
on of_warehouse_supplies for each row
execute procedure sync_supply_stock ();
create trigger on_warehouse_supplies__after_delete after delete
on of_warehouse_supplies for each row
execute procedure sync_supply_stock ();
for the returning object must be old instead of new.
No. The return value is only relevant for BEFORE ROW or INSTEAD OF triggers. From the docs: "The return value of a row-level trigger fired AFTER or a statement-level trigger fired BEFORE or AFTER is always ignored; it might as well be null".
So you can just make your sync_supply_stock trigger function RETURN NULL and it can be used on all operations.
I have two tables.Tickets which has the columns: ticketid,startdate , enddate
and transactions which has the columns: transactionid, ticketid (fk to tickets), ticketcost.I want to create a trigger on tickets that makes a discount to ticketcost of transactions(of the table transactions) whenever the enddate of a ticket is updated.Multiple transactions might have the same ticket.
I was able to make a trigger that did what i described however not only at one ticket, the one that the date was changed, but every ticket of the tickets table.
first attempt:
create or replace function changeDate() returns trigger as $changeDate$
BEGIN
IF new.enddate != old.enddate THEN
update transactions
set ticketcost = ticketcost - ticketcost*0.1
from tickets
where tickets.ticketid= transactions.ticketid;
END IF;
return new;
END
$changeDate$ LANGUAGE plpgsql;
CREATE TRIGGER changeDate after UPDATE ON tickets
FOR EACH ROW EXECUTE FUNCTION changeDate();
This obviously failed because it is done for every row so every ticket is updated.
What i have now is this:
create or replace function changeDate() returns trigger as $changeDate$
Declare
arg1 integer;
BEGIN
IF new.enddate != old.enddate THEN
update transactions
set ticketcost = ticketcost - ticketcost*0.1
from tickets
where arg1 = transactions.ticketid;
END IF;
return new;
END
$changeDate$ LANGUAGE plpgsql;
CREATE TRIGGER changeDate after UPDATE ON tickets
FOR EACH ROW
WHEN (new.enddate != old.enddate)
EXECUTE FUNCTION changeDate(tickets.ticketid);
I have been trying to pass only the id of a ticket that has different new and old dates.The query works but nothing is changed.Basically i m trying to find a way to pass the id of the ticket that has had its enddate field changed.In the above example i m trying to pass it as a variable when the condition i described occurs.Any help would be appreciated as i cant really find a solution.
You can restrict the trigger to fire only when the date has been updated by specifying the column name and by ensuring the updated value is not the same as the old one:
CREATE TRIGGER changeDate after UPDATE OF enddate ON tickets
FOR EACH ROW
WHEN (OLD.enddateIS DISTINCT FROM NEW.enddate )
EXECUTE FUNCTION changeDate();
Inside the trigger function, you can refer to the NEW.ticketID directly
create or replace function changeDate() returns trigger as $changeDate$
BEGIN
update transactions
set ticketcost = ticketcost - ticketcost*0.1
where transactions.ticketid = NEW.ticketid;
return new;
END
$changeDate$ LANGUAGE plpgsql;
PS: since the function is not changing the date, a better name would be setDiscount
I am trying to learn Postgres triggers, using some simple examples. I have created a simple table:
create table emp (empname text, salary integer, last_user text);
My goal is to replace the old salary with a new salary computed as the salary inserted (new) + the old salary. I could not get them to sum even when I did not put a condition (i.e. empname is the same)
Here is my code:
-- this table returns a new row instead of summing
CREATE OR REPLACE FUNCTION emp_stamp() RETURNS trigger
AS $emp_stamp$
BEGIN
new.salary = new.salary + old.salary ;
RETURN NEW;
END;
$emp_stamp$ LANGUAGE plpgsql;
CREATE TRIGGER emp_stamp
BEFORE UPDATE on emp
FOR EACH ROW
EXECUTE PROCEDURE emp_stamp();
When I insert into the table, I get a new row added and no existing rows follow the formula:
INSERT INTO EMP VALUES('BR',39970,'BR')
I have also unsuccessfully tried the UPDATE command.
Your CREATE TRIGGER script says BEFORE UPDATE. So this trigger is not fired at all for INSERT commands.
Moreover, the same trigger function would raise an error for INSERT commands anyway because, obviously, there is no "old" version for newly inserted rows.
It should work just fine as is for UPDATE, though. I just cleaned it up a bit:
CREATE OR REPLACE FUNCTION emp_stamp()
RETURNS trigger AS
$func$
BEGIN
NEW.salary := NEW.salary + OLD.salary;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
CREATE TRIGGER emp_stamp
BEFORE UPDATE ON emp
FOR EACH ROW EXECUTE PROCEDURE emp_stamp();
Just a proof of concept, I fail to see the point of adding up old an new value.
For starters, your table needs a proper PRIMARY KEY, a serial column for instance (empname is hardly unique):
CREATE TABLE emp (
emp_id serial PRIMARY KEY
, empname text
, salary integer
, last_user text);
Then the UPDATE could work reliably:
UPDATE EMP
SET salary = 39970
WHERE emp_id = 123;
I still don't see how the mentioned trigger would make sense. You could increase an existing salary like this, no trigger involved:
UPDATE EMP
SET salary = salary + 39970
WHERE emp_id = 123;
In my application we are using postgresql,now it has one million records in summary table.
When I run the following query it takes 80,927 ms
SELECT COUNT(*) AS count
FROM summary_views
GROUP BY question_id,category_type_id
Is there any efficient way to do this?
COUNT(*) in PostgreSQL tends to be slow. It's a feature of MVCC. One of the workarounds of the problem is a row counting trigger with a helper table:
create table table_count(
table_count_id text primary key,
rows int default 0
);
CREATE OR REPLACE FUNCTION table_count_update()
RETURNS trigger AS
$BODY$
begin
if tg_op = 'INSERT' then
update table_count set rows = rows + 1
where table_count_id = TG_TABLE_NAME;
elsif tg_op = 'DELETE' then
update table_count set rows = rows - 1
where table_count_id = TG_TABLE_NAME;
end if;
return null;
end;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
Next step is to add proper trigger declaration for each table you'd like to use it with. For example for table tab_name:
begin;
insert into table_count values
('tab_name',(select count(*) from tab_name));
create trigger tab_name_table_count after insert or delete
on tab_name for each row execute procedure table_count_update();
commit;
It is important to run in a transaction block to keep actual count and helper table in sync in case of delete or insert between initial count and trigger creation. Transaction guarantees this. From now on to get current count instantly, just invoke:
select rows from table_count where table_count_id = 'tab_name';
Edit: In case of your group by clause, you'll need more sophisticated trigger function and count table.