Exclude updated record from trigger function - sql

I have a table with a column named priority. I created a trigger function that fires after update. I want the function to change the values of the rest of the rows' priority column. For example I have 10 rows and each row has a single value ranging from 1-10, I then want to change the priority of row 10 to 1, then add 1 to the rest of the rows.
I've tried to change the query in many ways and add more/less logic to the function, but I am stuck.
CREATE FUNCTION reorder_priority() RETURNS TRIGGER AS $$
BEGIN
CASE TG_OP
WHEN 'UPDATE' THEN
UPDATE
link
SET
priority = priority + 1
WHERE
link.priority >= NEW.priority AND
NOT link.priority > OLD.priority;
END CASE;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
This function was able to do this, but it seems to trigger twice, adding 1 to every single row again, leaving me with 10 rows, but priority ranging from 2-11.

I figured it out..
Just had to change the comparison to exclude updating the value of OLD.priority, return NEW, and also change the trigger to run BEFORE and not AFTER
BEGIN
CASE TG_OP
WHEN 'UPDATE' THEN
UPDATE
link
SET
priority = priority + 1
WHERE
link.priority >= NEW.priority AND
NOT link.priority >= OLD.priority;
END CASE;
RETURN NEW;
END;

Related

Trigger function updates all rows instead of one (PostgreSQL)

Using PostgreSQL.
What I have is:
3 tables
1.Customer2c with columns: CustomerID,PersonID,Number_Of_Items`.
2.SalesOrderHeader2c with columns: SalesOrderID,CustomerID.
SalesOrderDetail2c with columns: SalesOrderDetailID,SalesOrderID,OrderQty
I want to create a trigger function, that will trigger whenever someone uses
INSERT INTO 'SalesOrderDetail2c' table
and that is going to get the OrderQty that was inserted and update the correspondent Number_Of_Items field with it.
My trigger is working, but the problem is that whenever I insert a new value to the SalesOrderDetail2c, the function gets the OrderQty value and updates all the rows of Number_Of_Items with it, instead of updating just the correspondent one.
Any help appreciated. What I have so far is this (It may be copletely wrong, dont judge please!):
CREATE OR REPLACE FUNCTION FunctionTrigger2c() RETURNS TRIGGER AS
$BODY$
BEGIN
UPDATE Customer2c
SET Number_Of_Items =
(SELECT OrderQty
FROM SalesOrderDetail2c
INNER JOIN SalesOrderHeader2c ON (SalesOrderDetail2c.SalesOrderID = SalesOrderHeader2c.SalesOrderID)
INNER JOIN Customer2c ON (SalesOrderHeader2c.CustomerID = Customer2c.CustomerID)
ORDER BY SalesOrderDetailID DESC LIMIT 1
)
FROM SalesOrderHeader2c
WHERE SalesOrderHeader2c.CustomerID = Customer2c.CustomerID
;
RETURN NEW;
END;
$BODY$
language plpgsql;
CREATE TRIGGER Trigger2c
AFTER INSERT ON SalesOrderDetail2c
FOR EACH ROW
EXECUTE PROCEDURE FunctionTrigger2c();
I had to use .new as #Nicarus mentioned above! Thanks again by the way.
This is the new code and now it changes only the correspondent value.
CREATE OR REPLACE FUNCTION FunctionTrigger2c() RETURNS TRIGGER AS
$BODY$
BEGIN
UPDATE Customer2c
SET Number_Of_Items =
(SELECT new.OrderQty
FROM SalesOrderDetail2c
order by salesorderdetailid desc limit 1
)
FROM SalesOrderheader2c
WHERE (SalesOrderheader2c.salesorderID = new.salesorderID) and (salesorderheader2c.customerid = customer2c.customerid)
;
RETURN NEW;
END;
$BODY$
language plpgsql;
CREATE TRIGGER Trigger2c
AFTER INSERT ON SalesOrderDetail2c
FOR EACH ROW
EXECUTE PROCEDURE FunctionTrigger2c();
I am pointing out why your original trigger did not work. The reason it updated every row is because of your UPDATE statement. After you inserted something, it triggered the update.
When the update ran it got these things:
What table I am gonna update
What value am I gonna set on what column of the given table
What rows I am gonna implement this update on (FROM + WHERE)
The problem lies in the last part, basically calling FROM in an UPDATE statement is like calling a SELECT statement, it tried to process every row from the FROM clause tables with the given WHERE value.
PostgreSQL documentation about UPDATE statement from_list parameter states that "A
list of table expressions, allowing columns from other tables to
appear in the WHERE condition and the update expressions. This is
similar to the list of tables that can be specified in the FROM Clause
of a SELECT statement."
(https://www.postgresql.org/docs/9.1/sql-update.html)
Basically, your update statement went through every row in SalesOrderHeader2c which conveniently matched with every row of Customer2c (with SalesOrderHeader2c.CustomerID = Customer2c.CustomerID), updating the value of every row in Customer2c with the firstly found Number_Of_Items value from the UPDATE SET statement. This is why you got the same value for every row in Customer2c.
The reason the new trigger works is because the update statement is done with the correctly selected rows.
Here is my, quite general trigger that is used do count/sum values in dependent tables. You should take care of all order changes, ie not only INSERT but also DELETE and UPDATE. You also should read #Nicarus comment about using NEW (and OLD) in triggers.
I have altered it to match your schema, but didn't test it...
CREATE OR REPLACE FUNCTION FunctionTrigger2c()
RETURNS trigger AS
$BODY$
DECLARE
BEGIN
CASE TG_OP
WHEN 'INSERT' THEN
UPDATE
Customer2c
SET
Number_Of_Items = Number_Of_Items + NEW.OrderQty
FROM
SalesOrderHeader2c
WHERE
Customer2c.CustomerID = SalesOrderHeader2c.CustomerID AND
SalesOrderHeader2c.SalesOrderID = NEW.SalesOrderID AND
SalesOrderDetailID = NEW.SalesOrderDetailID;
WHEN 'UPDATE' THEN
UPDATE
Customer2c
SET
Number_Of_Items = Number_Of_Items - OLD.OrderQty
FROM
SalesOrderHeader2c
WHERE
Customer2c.CustomerID = SalesOrderHeader2c.CustomerID AND
SalesOrderHeader2c.SalesOrderID = OLD.SalesOrderID AND
SalesOrderDetailID = OLD.SalesOrderDetailID;
UPDATE
Customer2c
SET
Number_Of_Items = Number_Of_Items + NEW.OrderQty
FROM
SalesOrderHeader2c
WHERE
Customer2c.CustomerID = SalesOrderHeader2c.CustomerID AND
SalesOrderHeader2c.SalesOrderID = NEW.SalesOrderID AND
SalesOrderDetailID = NEW.SalesOrderDetailID;
WHEN 'DELETE' THEN
UPDATE
Customer2c
SET
Number_Of_Items = Number_Of_Items - OLD.OrderQty
FROM
SalesOrderHeader2c
WHERE
Customer2c.CustomerID = SalesOrderHeader2c.CustomerID AND
SalesOrderHeader2c.SalesOrderID = OLD.SalesOrderID AND
SalesOrderDetailID = OLD.SalesOrderDetailID;
END CASE;
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
UPDATE also work well if you change the SalesOrderID :)
Notice, that this trigger returns NULL and it should be set as AFTER INSERT OR UPDATE OR DELETE:
CREATE TRIGGER Trigger2c
AFTER INSERT OR UPDATE OR DELETE
ON SalesOrderDetail2c
FOR EACH ROW
EXECUTE PROCEDURE FunctionTrigger2c();

Wondering why my SQL statement is ignoring my SET symbol

This is my SQL trigger and it is suppose to change the reorder value to Y or N depending if ON_HAND is less than or greater than the MINIMUM. The problem is it's ignoring the set statement completely. Do I have to arrange these differently?
CREATE OR REPLACE TRIGGER TRG_REORDER
AFTER UPDATE OF ON_HAND, MINIMUM ON PART
BEGIN
IF ON_HAND <= MINIMUM THEN
SET REORDER = 'Y';
ELSE ON_HAND > MINIMUM
SET REORDER = 'N';
END IF;
END;
Trigger bodies use PL/SQL syntax, not SQL. So for assignment you need := not set.
Your code has some other syntax errors.
You need to refer to the :new values of the columns.
You don't need a condition in the ELSE clause.
In order to modify the values of columns in a trigger, it must be a BEFORE trigger and fire FOR EACH ROW.
So fixing all that, this should now work for you (caveat: untested code).
CREATE OR REPLACE TRIGGER TRG_REORDER
before UPDATE OF ON_HAND, MINIMUM ON PART
for each row
BEGIN
IF :new.ON_HAND <= :new.MINIMUM THEN
:new.REORDER = 'Y';
ELSE
:new.REORDER = 'N';
END IF;
END;
I don't know Oracle triggers but I suspect you want a BEFORE trigger and an assignment that looks something like this.
:new.REORDER := case when :new.ON_HAND <= :new.MINIMUM then 'Y' else 'N' end;
Check here to get started:
http://www.techonthenet.com/oracle/triggers/before_update.php

Validate subquery count is not greater than x

I am trying to create a function and a trigger that verify that the count of the wid and rdate columns inside the responsibility table is less than or equal to 10. I need to raise an exception when its greater than 10.
My subquery count is not working. When COUNT() is greater than 10, no exception is thrown.
What am I doing wrong?
CREATE FUNCTION check_10() RETURNS TRIGGER AS $$
BEGIN
IF (SELECT COUNT(CASE WHEN wid = NEW.wid AND rdate = NEW.rdate THEN 1 ELSE 0 END) AS total FROM resposibility) > 10 THEN
RAISE EXCEPTION 'MAXIMUM OF 10 CAGE RESPONSIBILITES FOR EACH WORKER PER DATE';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
/* I've added update to make sure even when "updating" that row it will run the trigger check to verify */
CREATE TRIGGER insert_resp BEFORE INSERT OR UPDATE ON resposibility
FOR EACH ROW EXECUTE PROCEDURE check_10();
As long as you are raising an EXCEPTION (and don't catch it), that will roll back the whole transaction immediately anyway. The RETURN statement simply doesn't matter in this case. Remove it.
It would be an alternative to use RETURN NULL instead of the exception if you want to silently skip the operation on only the current row and otherwise proceed normally.
The only obvious error in your code was > 10 instead of >= 10 as pointed out by #a_horse. And the typo in resposibility. The rest is a matter of efficiency.
Also, assignments are comparatively expensive in plpgsql, there is really no need here. Simplify:
CREATE OR REPLACE FUNCTION check_10()
RETURNS TRIGGER AS
$func$
BEGIN
IF (SELECT count(*) >= 10
FROM responsibility
WHERE wid = NEW.wid
AND rdate = NEW.rdate) THEN
RAISE EXCEPTION 'WORKER % ALREADY HAS MAX. OF 10 RESPONSIBILITES FOR DATE: %'
, NEW.wid, NEW.rdate;
-- no need for *any* RETURN statement here!
END IF;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
Be sure to have an index on wid and rdate or, ideally, a multicolumn index on (wid, rdate).

Oracle - Is there any way I can get around SELECT INTO on PL/SQL block

I am trying to create a trigger, which automatically updates a student's application state when the application status row in the application table changes. I have been browsing the web for a little over an hour or so now and despite finding a potential work around using EXECUTE IMMEDIATE I cannot achieve my desired result (EXECUTE IMMEDIATE was causing an unbound variable error).
Trigger code
CREATE OR REPLACE TRIGGER trg_applications
BEFORE INSERT OR UPDATE ON applications FOR EACH ROW
BEGIN
IF UPDATING THEN
/* If the status is ACCEPTED, then approve the students application */
SELECT CASE
WHEN get_status(:NEW.status_id) =
LOWER('Applicant Accepted Offer')
THEN student_accept_offer( :NEW.student_id )
END
FROM status;
END IF;
END;
The get status method returns a VARCHAR2 to check whether the new status matches the condition, if so I want to update the student_approved row using the autonomous_transaction below.
student_accept_offer code
CREATE OR REPLACE FUNCTION student_accept_offer( this_stu_id NUMBER )
RETURN VARCHAR2 IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
UPDATE students
SET students.student_on_placement = 1
WHERE students.student_id = this_stu_id;
COMMIT;
RETURN 'Student has approved application';
END student_accept_offer;
This function works as intended when I test it outside of my trigger, however when it is embedded in the trigger an PLS-00428 error gets thrown. Could anyone point me in the right direction as to how can I work around this, to allow me to have this function fire automatically on an update if the status matches.
Thanks for your time
EDIT - Tables I am referencing
Changing your code slightly to remove the SELECT statement (as it seems unnecessary) then does this work?
CREATE OR REPLACE TRIGGER trg_applications
BEFORE INSERT OR UPDATE ON applications FOR EACH ROW
BEGIN
IF UPDATING THEN
/* If the status is ACCEPTED, then approve the students application */
IF get_status(:NEW.status_id) = 'applicant accepted offer' THEN
student_accept_offer( :NEW.student_id );
END IF;
END IF;
END;

Writing an SQL trigger to find if number appears in column more than X times?

I want to write a Postgres SQL trigger that will basically find if a number appears in a column 5 or more times. If it appears a 5th time, I want to throw an exception. Here is how the table looks:
create table tab(
first integer not null constraint pk_part_id primary key,
second integer constraint fk_super_part_id references bom,
price integer);
insert into tab values(1,NULL,100), (2,1,50), (3,1,30), (4,2,20), (5,2,10), (6,3,20);
Above are the original inserts into the table. My trigger will occur upon inserting more values into the table.
Basically if a number appears in the 'second' column more than 4 times after inserting into the table, I want to raise an exception. Here is my attempt at writing the trigger:
create function check() return trigger as '
begin
if(select first, second, price
from tab
where second in (
select second from tab
group by second
having count(second) > 4)
) then
raise exception ''Error, there are more than 5 parts.'';
end if;
return null;
end
'language plpgsql;
create trigger check
after insert or update on tab
for each row execute procedure check();
Could anyone help me out? If so that would be great! Thanks!
CREATE FUNCTION trg_upbef()
RETURN trigger as
$func$
BEGIN
IF (SELECT count(*)
FROM tab
WHERE second = NEW.second ) > 3 THEN
RAISE EXCEPTION 'Error: there are more than 5 parts.';
END IF;
RETURN NEW; -- must be NEW for BEFORE trigger
END
$func$ LANGUAGE plpgsql;
CREATE TRIGGER upbef
BEFORE INSERT OR UPDATE ON tab
FOR EACH ROW EXECUTE procedure trg_upbef();
Major points
Keyword is RETURNS, not RETURN.
Use the special variable NEW to refer to the newly inserted / updated row.
Use a BEFORE trigger. Better skip early in case of an exception.
Don't count everything for your test, just what you need. Much faster.
Use dollar-quoting. Makes your live easier.
Concurrency:
If you want to be absolutely sure, you'll have to take an exclusive lock on the table before counting. Else, concurrent inserts / updates might outfox each other under heavy concurrent load. While this is rather unlikely, it's possible.