how to have a trigger work only for certain columns (they are chosen by their id) after updating - postgresql

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

Related

How to use the same trigger function for insert/update/delete triggers avoiding the problem with new and old objects

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.

How to implement a constraint in postgresql

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.

How to modify Trigger to update a single attribute in PostgreSQL

Here is my sample table.
CREATE TABLE employee_test(
idTst SERIAL PRIMARY KEY,
monthDownload VARCHAR(6),
changeDate DATE);
I am trying to create a function and trigger that would update changeDate attribute with a current date when monthDownload attribute is updated.
The function I have it works with one problem. It updates all records instead of the one that was updated.
CREATE OR REPLACE FUNCTION downloadMonthChange()
RETURNS TRIGGER AS
$$
BEGIN
IF NEW.monthDownload <> OLD.monthDownload THEN
UPDATE employee_test
SET changeDate = current_date
where OLD.idTst = NEW.idTst;
END IF;
RETURN NEW;
END;
$$
Language plpgsql;
Trigger
Create TRIGGER dataTest
AFTER UPDATE
ON employee_test
FOR EACH ROW
EXECUTE PROCEDURE downloadMonthChange();
When I execute the following Update statement:
UPDATE employee_test SET monthDownload = 'oct12'
WHERE idTst = 1;
All changeDate rows get update with a current date.
Is there a way to have only a row with changed record to have a current date updated.
If you use a before trigger you can write directly to NEW
CREATE OR REPLACE FUNCTION downloadMonthChange()
RETURNS TRIGGER AS
$$
BEGIN
IF NEW.monthDownload <> OLD.monthDownload THEN
NEW.changeDate = current_date;
END IF;
RETURN NEW;
END;
$$
Language plpgsql;
the other option when you must use an after trigger is to include the primary key in the where clause. It appears that you were trying to do this, but you had a spurious OLD in the query. beause of that the where clause was only looking at the record responsible for the trigger call, and not limiting which records were to be updated.
IF NEW.monthDownload <> OLD.monthDownload THEN
UPDATE employee_test
SET changeDate = current_date
where idTst = NEW.idTst;

Accessing row values in a trigger

Here is my code so far:
CREATE OR REPLACE FUNCTION updateDegreeStatus()
RETURNS TRIGGER as $updateDegreeStatus$
BEGIN
IF(new.degreeName = old.degreeName and old.endDate = NULL)
THEN
UPDATE degree
SET old.endDate = CURRENT_DATE
WHERE degree.degreeID = old.degreeID;
END IF;
RETURN NEW;
END;
$updateDegreeStatus$ LANGUAGE plpgsql;
CREATE TRIGGER updateDegreeStatus
BEFORE INSERT ON degree
FOR EACH ROW
EXECUTE PROCEDURE updateDegreeStatus();
What I having trouble with is accessing the current values of the row the trigger is currently on. How my stored procedure should work, is if the new insert has the same degree name as the current row and the current row value for end date is null then do this. But the keyword old is not how you access the current row values. And I have been reading through documentation and I still can not find the answer. If anyone has any input on how to fix this issue, I would love your help.
Here is my current table:
And my insert is this:
INSERT INTO degree(degreeID, degreeName, type, startDate, endDate)
VALUES(3, 'Computer Science with a Concentration in Software Development', 'Concentration', CURRENT_DATE, null)
Now what I want to happen is when the insert command is issued it will fire off my trigger, and will go and check if the New degreeName equals the current row degreeName and if the current row endDate is null. If it is then it will issue the Update on the current row, which just sets the endDate to the CURRENT_Date.

PostgreSQL how to write trigger

Say I have a following table like this:
http://i.stack.imgur.com/qU3gh.png
where sid stands for sailor ID, and bid stands for boat ID.
I want to write a trigger and a function to detect whether the new input is invalid. Look at the table, the 3rd record shouldn't be there because the renting time has overlap with the former one, one boat can't be rented before it is returned. That is to say, I want a trigger and a function to stop input like the 3rd one, when someone tries to give a 3rd input, it will just stop you from doing so.
Currently I wrote the following code, but as I am new to this, I am really not sure whether it is correct:
CREATE FUNCTION update() RETURNS TRIGGER AS $logupdate$
DECLARE
judge boolean;
BEGIN
judge := EXECUTE ( 'SELECT starttime,endtime,NEW.starttime,NEW.endtime FROM reserves WHERE bid = NEW.bid AND startdate = NEW.startdate AND (starttime,endtime) overlaps(NEW.starttime,NEW.endtime) IS NULL');
IF judge = f THEN RAISE EXCEPTION 'failed due to some reasons';
END IF;
RETURN NEW;
END;
$logupdate$ LANGUAGE plpgsql;
CREATE TRIGGER logupdate
before UPDATE ON reserves
FOR EACH ROW
EXECUTE PROCEDURE update();
How can I correct this?
consider using a timestamp field instead of seperate date and time fields for simplicity. you can easily format a timestamp into either a date or time.
update and insert triggers must return NEW on success, or FALSE/NULL
in order to enforce a constraint with a trigger you must run a BEFORE UPDATE trigger, otherwise the row's already added.
CREATE TRIGGER logupdate
BEFORE UPDATE ON reserves
FOR EACH ROW EXECUTE PROCEDURE update();
CREATE FUNCTION update() RETURNS TRIGGER AS $$
BEGIN
minstart := EXECUTE ( 'SELECT MAX(endtime) FROM reserves WHERE bid = NEW.bid AND startdate = NEW.startdate' ) ;
IF minstart < NEW.starttime THEN RETURN FALSE;
RETURN NEW;
END
$$