I'm having some problem to create a new trigger before insert a new row.
It should act before insert to stop an insert of a new row that has a value that is already referenced from another row in the same table.
I tried to use this trigger but it is not compatible with mariaDB, in fact it gives me asyntax error on referencing.
CREATE TRIGGER BadgeAlreadyUsed
BEFORE INSERT ON User
REFERENCING NEW AS N
FOR EACH ROW
WHEN (EXISTS ( SELECT IDBadge FROM User WHERE N.IDBadge = User.IDBadge ))
SIGNAL SQLSTATE '70002' ('Badge already used!!');
How i can do the same thing with the new syntax?
thanks.
Each database (DB2, MariaDB, etc) has hundreds of differences. Don't assume anything!
This might be closer:
CREATE TRIGGER BadgeAlreadyUsed
BEFORE INSERT ON User
FOR EACH ROW
BEGIN
IF (EXISTS ( SELECT IDBadge FROM User
WHERE NEW.IDBadge = User.IDBadge ))
THEN
SIGNAL SQLSTATE '70002'
SET MESSAGE_TEXT = 'Badge already used!!';
END IF;
END;
Notice there there are at least 3 syntax changes (WHEN, NEW, SET).
Related
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.
I'm trying to create a trigger in DB2 (AS400) to insert/delete a row into a table when an insert/delete is triggered on a different table, but I need to use information about the triger table.
The example would be I would like is like this (column 1 from table 1 and table 2 are the same and unique in table 2):
CREATE TRIGGER MY_TRIGGER
AFTER INSERT OR DELETE ON DB1.TABLE1
BEGIN
IF INSERTING
THEN INSERT INTO DB1.TABLE2 (Col1, Col2) VALUES (Db1.TABLE1.Col1, 0);
ELSEIF DELETING
THEN DELETE FROM Db1.TABLE2 WHERE Col1=TABLE1.Col1;
END IF;
END
But this doesn't work (it doesn't recognize TABLE1.Col1 on insert/delete statements).
Also it would prompt me an error (I guess) since it would create a duplicate key when a second row is inserted in Table 1. How could I avoid errors (just skip the insert) when the Table2.Col1 already exists?
Try adding correlation names like this:
CREATE TRIGGER MY_TRIGGER
AFTER INSERT OR DELETE ON DB1.TABLE1
REFERENCING OLD ROW AS OLD
NEW ROW AS NEW
BEGIN
IF INSERTING
THEN INSERT INTO DB1.TABLE2 (Col1, Col2) VALUES (NEW.Col1, 0);
ELSEIF DELETING
THEN DELETE FROM Db1.TABLE2 WHERE Col1=OLD.Col1;
END IF;
END
The trigger has access to both the old and new image of a row. You need to tell it which to use. BTW, only the update action populates both the old and new image. Insert only provides the new image, and delete only provides the old image. One might think that SQL could figure that out, but no, you still have to tell it explicitly.
EDIT This is the final trigger actually used (from comments, thank you #MarçalTorroella)
CREATE TRIGGER MY_TRIGGER
AFTER INSERT OR DELETE ON DB1.TABLE1
REFERENCING OLD ROW AS OLD
NEW ROW AS NEW
FOR EACH ROW MODE DB2ROW
BEGIN
DECLARE rowcnt INTEGER;
IF INSERTING THEN
SELECT COUNT(*)
INTO rowcnt
FROM DB1.TABL2
WHERE Col1 = NEW.Col1;
IF rowcnt = 0 THEN
INSERT INTO DB1.TABLE2 (Col1, Col2)
VALUES (NEW.Col1, 0);
END IF;
ELSEIF DELETING THEN
DELETE FROM Db1.TABLE2
WHERE Col1=OLD.Col1;
END IF;
END
I am new to PostgreSQL and I'm trying to create a trigger on update. I have two tables source and destination with same table structure. So I want the records to be updated on destination when there is an update on source. I tried the below trigger function:
Create FUNCTION ins_functiontest() RETURNS trigger AS '
BEGIN
IF tg_op = ''UPDATE'' THEN
INSERT INTO destination(id,name,tg_op)
VALUES (new.id,new.name, tg_op);
RETURN new;
END IF;
END
' LANGUAGE plpgsql;
Column 'id' is primary key on both tables so the above function fails as when there is an update on source as that record already exists on destination.
I tried to modify function to update rest of the columns in the table comparing the id fields on source and destination.
Update des
Set name = new.name,tg_op= update
From destination des join source src
ON des.id = src.id
Where des.id = src.id
But couldn't get the syntax correct. Any help would be most appreciated.
I'm using PostgreSQL 8.4.
I figured out solution for my problem, Below is the answer.
Create FUNCTION ins_functiontest() RETURNS trigger AS '
BEGIN
IF tg_op = ''UPDATE'' THEN
Update destination_table_name
SET
name = new.name,
Where id = new.id;
END IF;
END
' LANGUAGE plpgsql;
I did something similar for my log tables. But I duplicate the colums. 1 set for the new.* to catch insert and update and a set for the old.* also for update and delete. Then inserted a serials primary key, the time and idtransaction, txid_current(). The only bullet proof is the serial. The idtransaction depend from wich server works. If you change pc, and it can happen in the lifetime of a db, it will start again the counter. But cannot happen that two transactions with same id have same time. But can happen two transaction at the same time. Expecially if you have several users. connected
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.
I am learning to use triggers in PostgreSQL but run into an issue with this code:
CREATE OR REPLACE FUNCTION checkAdressen() RETURNS TRIGGER AS $$
DECLARE
adrCnt int = 0;
BEGIN
SELECT INTO adrCnt count(*) FROM Adresse
WHERE gehoert_zu = NEW.kundenId;
IF adrCnt < 1 OR adrCnt > 3 THEN
RAISE EXCEPTION 'Customer must have 1 to 3 addresses.';
ELSE
RAISE EXCEPTION 'No exception';
END IF;
END;
$$ LANGUAGE plpgsql;
I create a trigger with this procedure after freshly creating all my tables so they are all empty. However the count(*) function in the above code returns 1.
When I run SELECT count(*) FROM adresse; outside of PL/pgSQL, I get 0.
I tried using the FOUND variable but it is always true.
Even more strangely, when I insert some values into my tables and then delete them again so that they are empty again, the code works as intended and count(*) returns 0.
Also if I leave out the WHERE gehoert_zu = NEW.kundenId, count(*) returns 0 which means I get more results with the WHERE clause than without.
--Edit:
Here is an example of how I use the procedure:
CREATE TABLE kunde (
kundenId int PRIMARY KEY
);
CREATE TABLE adresse (
id int PRIMARY KEY,
gehoert_zu int REFERENCES kunde
);
CREATE CONSTRAINT TRIGGER adressenKonsistenzTrigger AFTER INSERT ON Kunde
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
EXECUTE PROCEDURE checkAdressen();
INSERT INTO kunde VALUES (1);
INSERT INTO adresse VALUES (1,1);
It looks like I am getting the DEFERRABLE INITIALLY DEFERRED part wrong. I assumed the trigger would be executed after the first INSERT statement but it happens after the second one, although the inserts are not inside a BEGIN; - COMMIT; - Block.
According to the PostgreSQL Documentation inserts are commited automatically every time if not inside such a block and thus there shouldn't be an entry in adresse when the first INSERT statement is commited.
Can anyone point out my mistake?
--Edit:
The trigger and DEFERRABLE INITIALLY DEFERRED seem to be working all right.
My mistake was to assume that since I am not using a BEGIN-COMMIT-Block each insert would be executed in an own transaction with the trigger being executed afterwards every time.
However even without the BEGIN-COMMIT all inserts get bundled into one transaction and the trigger is executed afterwards.
Given this behaviour, what is the point in using BEGIN-COMMIT?
You need a transaction plus the "DEFERRABLE INITIALLY DEFERRED" because of the chicken and egg problem.
starting with two empty tables:
you cannot insert a single row into the person table, because the it needs at least one address.
you cannot insert a single row into the address table, because the FK constraint needs a corresponding row on the person table to exist
This is why you need to bundle the two inserts into one operation: the transaction. You need the BEGIN+ COMMIT, and the DEFERRABLE allows transient forbidden database states to exists: it causes the check to be evaluated at commit time.
This may seem a bit silly, but the answer is you need to stop deferring the trigger and run it BEFORE the insert. If you run it after the insert, of course there is data in the table.
As far as I can tell this is working as expected.
One further note, you probably dont mean:
RAISE EXCEPTION 'No Exception';
You probably want
RAISE INFO 'No Exception';
Then you can change your settings and run queries in transactions to test that the trigger does what you want it to do. As it is, every insert is going to fail and you have no way to move this into production without editing your procedure.