Trigger that updates stock levels after a sale - MySQL Workbench - triggers

I am trying to create a trigger called 'stockupdate' that updates 'quantity_available' when a new order is placed on my order_line_item_table.
Tables and columns required: stock_table.quantity_available (Represents Stock) order_line_item_table.sale_quantity (Represents each order placed)
I have used the below code to create the trigger, however when I insert a new order to check the trigger is working, the stock_table.quantity_available figure does not update and I am not sure why. Any help would be greatly appreciated.
DELIMITER $$
DROP TRIGGER IF EXISTS stockupdate;
CREATE TRIGGER stockupdate
AFTER INSERT ON order_line_item_table
FOR EACH ROW
BEGIN
UPDATE stock_table
SET quantity_available = (quantity_available - NEW.sale_quantity)
WHERE product_id = NEW.product_id;
END$$
DELIMITER ;
I am expecting the quantity_available to decrease by the sale_quantity amount when a new order is placed on the order_line_item_table

Related

PostgreSQL Trigger that updates table where original trigger runs from

I have two tables in this scenario. One is my "hot sync" table which is near-realtime bi-directional sync of data from my Salesforce Org to a Postgres table. As data changes in the source system (Salesforce), it updates that table on Postgres.
On this table in Postgres, I have a trigger that runs some logic. It basically checks to see if the record triggering it has a sent date that meets some business logic, copy that row into another schema/table to "archive" it.
This all works fine.
What I need to do however is once this row has been copied into the other table, I need to update the status of the record hot sync table. Since it is bi-directional, this will allow the data in Salesforce to reflect the changes I make from the Postgres side.
Can I place this update statement within the originating trigger or is this going to cause recursion issues?
CREATE FUNCTION salesforce.archivelogicfunc()
RETURNS trigger
LANGUAGE 'plpgsql'
COST 100
VOLATILE NOT LEAKPROOF
AS $BODY$ BEGIN
IF (DATE(NEW.et4ae5__datesent__c) < NOW() - INTERVAL '180 days'
AND DATE(NEW.et4ae5__datesent__c) > NOW() - INTERVAL '540 days')
THEN
INSERT INTO archive.individualemailresult__c
(dateopened__c,
numberoftotalclicks__c,
datebounced__c,
fromname__c,
hardbounce__c,
fromaddress__c,
softbounce__c,
name,
lastmodifieddate,
opened__c,
ownerid,
subjectline__c,
isdeleted,
contact__c,
systemmodstamp,
lastmodifiedbyid,
datesent__c,
dateunsubscribed__c,
createddate,
createdbyid,
lead__c,
tracking_as_of__c,
numberofuniqueclicks__c,
senddefinition__c,
mergeid__c,
triggeredsenddefinition__c,
sfid,
id,
_hc_lastop,
_hc_err,
isarchived)
VALUES
(NEW.et4ae5__dateopened__c,
NEW.et4ae5__numberoftotalclicks__c,
NEW.et4ae5__datebounced__c,
NEW.et4ae5__fromname__c,
NEW.et4ae5__hardbounce__c,
NEW.et4ae5__fromaddress__c,
NEW.et4ae5__softbounce__c,
NEW.name,
NEW.lastmodifieddate,
NEW.et4ae5__opened__c,
NEW.ownerid,
NEW.et4ae5__subjectline__c,
NEW.isdeleted,
NEW.et4ae5__contact__c,
NEW.systemmodstamp,
NEW.lastmodifiedbyid,
NEW.et4ae5__datesent__c,
NEW.et4ae5__dateunsubscribed__c,
NEW.createddate,
NEW.createdbyid,
NEW.et4ae5__lead__c,
NEW.et4ae5__tracking_as_of__c,
NEW.et4ae5__numberofuniqueclicks__c,
NEW.et4ae5__senddefinition__c,
NEW.et4ae5__mergeid__c,
NEW.et4ae5__triggeredsenddefinition__c,
NEW.sfid,
NEW.id,
NEW._hc_lastop,
NEW._hc_err,
NEW.isarchived__c)
ON CONFLICT (id)
DO NOTHING;
-- Update SF to reflect the archive
UPDATE salesforce."et4ae5__individualemailresult__c" SET isarchived__c = true, isdeleted = true WHERE id = NEW.id;
END IF;
RETURN NULL;
END;
$BODY$;
ALTER FUNCTION salesforce.archivelogicfunc()
OWNER TO ....;
My understanding is that the NEW.* is only going to contain the rows that caused the trigger to fire in the first place. Therefore if my trigger was fired for a single record, the update statement NEW.id should only update one record on the source table?
Trying to ensure the trigger isn't going to fire again with the update statement causing some recursive loop that I am not expecting.
My concern is:
Record is Updated
Trigger Fires and inserts record into an archive table
Update runs on the source table to update the record for the new.id
This update causes the trigger to run again. The insert would fail due to the on conflict, but the update would then run again, and again etc..
The original trigger is fired AFTER INSERT/UPDATE.
TRIGGER:
CREATE TRIGGER archivelogic_firetrigger
AFTER INSERT OR UPDATE
ON salesforce.et4ae5__individualemailresult__c
FOR EACH ROW
EXECUTE PROCEDURE salesforce.archivelogicfunc();
UPDATE:
I added a WHEN condition to my trigger. It appeared to work on a basic test, but willing to take any other advice if suggested.
CREATE TRIGGER archivelogic_firetrigger
AFTER INSERT OR UPDATE
ON salesforce.et4ae5__individualemailresult__c
FOR EACH ROW
WHEN (pg_trigger_depth() = 0) // <-- Added to prevent recursion
EXECUTE PROCEDURE salesforce.archivelogicfunc();
The easiest would be to make it a before trigger, and to replace the update by
NEW.isarchived__c = true;
NEW. isdeleted = true;
[...]
RETURN NEW;
Otherwise, you can filter the rows before running the trigger: it will be called only when isarchived__c and isdeleted have NOT changed (it may be dangerous though, just imagine someone updating ALL fields)
CREATE TRIGGER archivelogic_firetrigger
AFTER INSERT OR UPDATE
ON salesforce.et4ae5__individualemailresult__c
FOR EACH ROW
WHEN (NEW.isarchived__c IS NOT DISTINCT FROM OLD.isarchived__c
AND NEW.isdeleted IS NOT DISTINCT FROM OLD.isdeleted )
EXECUTE PROCEDURE salesforce.archivelogicfunc();

Get data of multiple inserted rows in one object using trigger in postgres

I am trying to write a trigger which gets data from the table attribute in which multiple rows are inserted corresponding to one actionId at one time and group all that data into the one object:
Table Schema
actionId
key
value
I am firing trigger on rows insertion,SO how can I handle this multiple row insertion and how can I collect all the data.
CREATE TRIGGER attribute_changes
AFTER INSERT
ON attributes
FOR EACH ROW
EXECUTE PROCEDURE log_attribute_changes();
and the function,
CREATE OR REPLACE FUNCTION wflowr222.log_task_extendedattribute_changes()
RETURNS trigger AS
$BODY$
DECLARE
_message json;
_extendedAttributes jsonb;
BEGIN
SELECT json_agg(tmp)
INTO _extendedAttributes
FROM (
-- your subquery goes here, for example:
SELECT attributes.key, attributes.value
FROM attributes
WHERE attributes.actionId=NEW.actionId
) tmp;
_message :=json_build_object('actionId',NEW.actionId,'extendedAttributes',_extendedAttributes);
INSERT INTO wflowr222.irisevents(message)
VALUES(_message );
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
and data format is,
actionId key value
2 flag true
2 image http:test.com/image
2 status New
I tried to do it via Insert trigger, but it is firing on each row inserted.
If anyone has any idea about this?
I expect that the problem is that you're using a FOR EACH ROW trigger; what you likely want is a FOR EACH STATEMENT trigger - ie. which only fires once for your multi-line INSERT statement. See the description at https://www.postgresql.org/docs/current/sql-createtrigger.html for a more through explanation.
AFAICT, you will also need to add REFERENCING NEW TABLE AS NEW in this mode to make the NEW reference available to the trigger function. So your CREATE TRIGGER syntax would need to be:
CREATE TRIGGER attribute_changes
AFTER INSERT
ON attributes
REFERENCING NEW TABLE AS NEW
FOR EACH STATEMENT
EXECUTE PROCEDURE log_attribute_changes();
I've read elsewhere that the required REFERENCING NEW TABLE ... syntax is only supported in PostgreSQL 10 and later.
Considering the version of postgres you have, and therefore keeping in mind that you can't use a trigger defined FOR EACH STATEMENT for your purpose, the only alternative I see is
using a trigger after insert in order to collect some information about changes in a utility table
using a unix cron that execute a pl/sql that do the job on data set
For example:
Your utility table
CREATE TABLE utility (
actionid integer,
createtime timestamp
);
You can define a trigger FOR EACH ROW with a body that do something like this
INSERT INTO utilty values(NEW.actionid, curent_timestamp);
And, finally, have a crontab UNIX that execute a file or a procedure that to something like this:
SELECT a.* FROM utility u JOIN yourtable a ON a.actionid = u.actionid WHERE u.createtime < current_timestamp;
// do something here with records selected above
TRUNCATE table utility;
If you had postgres 9.5 you could have used pg_cron instead of unix cron...

A trigger fires on a table, but the select on the table returns null. How can I create the code to be able to access the row that fired the trigger?

A trigger fires on a table, but the select on the table returns null. How can I create the code to be able to access the row that fired the trigger?
I have the following in the trigger:
begin
dws_edi_api.init_edi_message(message_id,order_no',supplier_no');
end;
This fires on update of the column row_state in the table out_message_tab
The event fires OK but when in the procedure dws_edi_api.init_edi_message_line I do a select c08 from out_message_tab where message_id = message_id_ (variable from the trigger). it returns null.
I assume the change hasnt been committed. I have tried adding a commit as the first line in my code to force the change to commit but that doesnt help. I have tried adding a dbms_lock.sleep(!0) but that doesnt help either.
I add the code to the procedure in the "show some code box"
procedure init_edi_message_line(message_id in number) is
pragma autonomous_transaction;
message_id_ number;
order_no_ varchar2(20);
supplier_no_ varchar2(20);
c08_ varchar2(200);
cursor c1 is
select c08
from jdifs.out_message_line_tab
where message_id = message_id_
and name = 'HEADER';
begin
-- dbms_lock.sleep(10);
message_id_ := message_id;
open c1;
loop
fetch c1
into c08_;
exit when c08_ is not null;
insert into jdifs.jdws_temp_line_tab
values
(message_id_, '2', c08_, '4');
commit;
END LOOP;
close c1;
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- Do something
null;
WHEN OTHERS THEN
null;
end init_edi_message_line;
EDIT:
Hi, no this didnt solve the problem unfortunately,
I will try again to explain as thourougly as possible.
I have a trigger on the table called out_message_line_tab. When a row is created in that table it contains a big number of columns.
the ones that are interesting to me are message_id(which is a sequential number), order_no (P123456), supplier_no(11242), linenumber(1), part_no (F1524).
When the trigger fires data needs to be fetched from that table (and a table "connected to this table" in this case, out_message_tab.
So the trigger is on out_message_line_tab, but it isnt enough to send the values in the trigger to the procedure, since I need some data from the other table as well.
The primary key between the tables out_message_tab and out_message_line_tab is message_id
So my problem is how to do the select from out_message_tab where message_id = message_id(primary key from out_message_line_tab
When I do, it just says no data found. I assume its because it has not been commited yet.
I hope this is clearer.
Your procedure init_edi_message_line() is defined using pragma autonomous_transaction. That means it executes in a completely separate session. Consequently it cannot see any of the uncommitted data in the session which fired the trigger.
If you want init_edi_message_line() to process data from that session your triggers needs to pass everything to the procedure as an argument. However it's not clear exactly what you're doing - is out_message_line_tab the table which owns the trigger? - so I can't guarantee that it will be easy for you to make the necessary changes.

PgSQL log table update time

I've created the following table:
CREATE TABLE updates
(
"table" text,
last_update timestamp without time zone
)
I want to update it whenever any table is updated, the problem is I don't know how, could someone please help me turn this pseudocode into a trigger?
this = current table on whitch operation is performed
ON ALTER,INSERT,DELETE {
IF (SELECT COUNT(*) FROM updates where table = this) = 1
THEN
UPDATE updates SET last_update = timeofday()::timestamp WHERE `table`=this
ELSE
INSERT INTO updates VALUES (this,timeofday()::timestamp);
}
You need a trigger function that is called whenever one of your tables is "updated", assuming that you mean that an INSERT, UPDATE, or DELETE is successfully executed. That trigger function would look like this:
CREATE FUNCTION log_update() RETURNS trigger AS $$
BEGIN
UPDATE updates SET last_update = now() WHERE "table" = TG_TABLE_NAME;
IF NOT FOUND THEN
INSERT INTO updates VALUES (TG_TABLE_NAME, now());
END IF;
IF (TG_OP = 'DELETE') THEN
RETURN OLD;
ELSE
RETURN NEW;
END IF;
END; $$ LANGUAGE PLPGSQL;
Every table that has to be logged this way needs to have a trigger associated with it like this:
CREATE TRIGGER ZZZ_mytable_log_updates
AFTER INSERT OR UPDATE OR DELETE ON mytable
FOR EACH ROW EXECUTE PROCEDURE log_update();
A few comments:
Trigger functions are created with PL/PgSQL; see chapter 40 in the documentation. Trigger functions come with some automatic parameters such as TG_TABLE_NAME.
Don't use reserved words ("table" in your case) as column names. Actually, in this case you are better off using the oid of the table, with the associated TG_RELID automatic parameter. It takes up less storage, it is faster, and it avoids confusion between tables with the same name in different schemas of your database. You can use the pg_tables system catalog table to look up the table name from the oid.
You must return the proper value depending on the operation, or the operation may fail. INSERT and UPDATE operations need to have NEW returned; DELETE needs to have OLD returned.
The name of the trigger starts with "ZZZ" to make sure that it fires after any other triggers on the same table have succeeded (they are fired in alphabetical order). If a prior trigger fails, this trigger function will not be called, which is the proper behaviour because the insert, update or delete will not take place either.

Mutating table in SQL for a specific case of update

I have create a trigger for table stock
The schema of the table is as follows:
create table stock(item_code varchar2(2) primary key, p_qty number(2),s_qty number(2));
The Trigger is as follows:
CREATE OR REPLACE TRIGGER TR_STOCK BEFORE UPDATE OF S_QTY ON STOCK FOR EACH ROW
DECLARE
V_P STOCK.P_QTY%TYPE;
V_S STOCK.S_QTY%TYPE;
V_I VARCHAR2(2);
BEGIN
V_S:=:NEW.S_QTY;
V_I:=:NEW.ITEM_CODE;
SELECT P_QTY INTO V_P FROM STOCK WHERE ITEM_CODE=V_I;
IF V_S>V_P THEN
RAISE_APPLICATION_ERROR(-20400,'SOLD QTY CANNOT EXCEED PURCHASED QTY...');
END IF;
END;
/
Now every time I execute an update query, it says the table is mutating and flags the following error:
update stock set s_qty=2 where item_code='i4'
*
ERROR at line 1:
ORA-04091: table HR.STOCK is mutating, trigger/function may not see it
ORA-06512: at "HR.TR_STOCK", line 8
ORA-04088: error during execution of trigger 'HR.TR_STOCK'
Any help with this specific problem?
There is no need to query the STOCK table. Simply compare the :NEW.P_QTY and :NEW.S_QTY fields directly
CREATE OR REPLACE TRIGGER TR_STOCK BEFORE UPDATE OF S_QTY ON STOCK FOR EACH ROW
DECLARE
BEGIN
IF :new.s_qty > :new.p_qty THEN
RAISE_APPLICATION_ERROR(-20400,'SOLD QTY CANNOT EXCEED PURCHASED QTY...');
END IF;
END;
/
You really should consider using a database constraint to implement this logic, in which case you wouldn't need the trigger at all.
ALTER TABLE hr.stock
ADD CONSTRAINT stock_ck1
CHECK (
s_qty <= p_qty
)
Triggers have many drawbacks compared with constraints:
Triggers do not account for existing data rows, constraints can do this if you desire.
A FOR EACH ROW trigger has to context-switch between the SQL engine and the PL/SQL engine for every row, which increases the overhead of the INSERT or UPDATE statement running. This adds up as your number of rows increases.
Oracle can use constraints when optimising your SQL statements (it knows that a WHERE clause that violates a CHECK constraint will never return any rows without needing to inspect the rows).
If you're using the trigger to provide an error message, you should really consider moving this into your application logic, with constraints as a safeguard.