I tried create a trigger:
CREATE TRIGGER DataTrigger ON Data AFTER INSERT , DELETE
AS
BEGIN
IF EXISTS(SELECT * FROM Inserted)
INSERT INTO [dbo].[AuditTrail]
([ActionType]
,[TableName]
,[Name]
,[Time])
('INSERT' //Incorrect syntax near 'INSERT'
,'Data'
,SELECT col1 FROM Inserted
,CURRENT_TIMESTAMP //Incorrect syntax near the keyword 'CURRENT_TIMESTAMP')
END
but it keep saying that I have thoes errors, can somebody show me where I did wrong?
P/S: what is the best way to detect an update?
Thank you
CREATE TRIGGER DataTrigger ON Data AFTER INSERT , DELETE
AS
BEGIN
INSERT INTO [dbo].[AuditTrail]
([ActionType]
,[TableName]
,[Name]
,[Time])
SELECT 'INSERT'
,'Data'
,col1
,CURRENT_TIMESTAMP
FROM Inserted
END
Just for clarification.
Your query, if syntatically correct would have failed if more than one row was inserted, the version above allows for multiple inserts.
The IF EXISTS was redundant, which is why it was removed, if there are no rows there will be no insert into your audit table.
If you want to audit DELETE you'll need a similar statement again, but using the Deleted table rather than Inserted
To audit UPDATE, create a new trigger, for each updated row you get an entry in Inserted with the new updates and an entry in Deleted with the old data, you can join these if you want to track old and new.
CREATE TRIGGER DataTrigger ON Data AFTER INSERT , DELETE
AS
BEGIN
INSERT INTO [dbo].[AuditTrail]
([ActionType]
,[TableName]
,[Name]
,[Time])
SELECT 'INSERT', 'Data', col1, CURRENT_TIMESTAMP FROM Inserted
END
I am not entirely sure what you are trying to accomplish here so I have just corrected the syntax to get it to work, but it should help. I have also tried to handle the deleted case as well
CREATE TRIGGER DataTrigger
ON Data
AFTER INSERT , DELETE
AS
BEGIN
INSERT INTO [dbo].[AuditTrail]([ActionType],
[TableName],
[Name],
[Time])
SELECT 'INSERT','Data', col1, CURRENT_TIMESTAMP
FROM Inserted
INSERT INTO [dbo].[AuditTrail]([ActionType],
[TableName],
[Name],
[Time])
SELECT 'DELETE','Data', col1, CURRENT_TIMESTAMP
FROM Deleted
END
Related
I am trying to update the table with huge data. On checking I figured that upsert (Update and insert) is slow compared to using the temp table to delete and then insert from temp table. But I am facing issue with duplicate data in such scenario.
As far as I understand the delete and insert is happening in same transaction, so I could not understand why I am facing duplicate data issue when I have already deleted the data with the delete.
This is multithreaded scenario but i guess the other transaction will have its own set of data.
Any help is appreciated here.
Sample code
CREATE OR REPLACE FUNCTION insertUsingTempTable(a_id int, s_obj int[], p bigint[])
RETURNS BOOLEAN AS $BODY$
DECLARE passed BOOLEAN;
BEGIN
CREATE TEMP TABLE IF NOT EXISTS temp_lists AS SELECT * FROM acm_lists WHERE 0=1;
TRUNCATE TABLE temp_lists;
INSERT INTO temp_lists(a_id, s_obj_id, eff_p)
SELECT a_id , unnest($2::int[]), unnest($3::bit(64)[]) ;
IF NOT EXISTS(SELECT t.a_id, s_obj_id, count(1) FROM temp_lists t
GROUP BY t.a_id, s_obj_id HAVING count(1) > 1) THEN
DELETE FROM acm_lists t WHERE EXISTS (SELECT 1 FROM temp_lists t1
WHERE t1.a_id=t.a_id AND s_obj_id=t.s_obj_id);
INSERT INTO acm_lists (a_id, s_obj_id, eff_p)
SELECT t.a_id, t.s_obj_id, t.eff_p FROM temp_lists t;
RETURN true;
END IF;
RETURN false;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
Uniqueness is on a_id, s_obj_id in the above code.
I would like to know why duplicate exception is coming at times, if I am already deleting the data before inserting. This happens only when multiple transactions are running at the same time on the table.
INSERT ... ON CONFLICT DO UPDATE resolves the issue, but seems there is considerable performance hit. so I don't plan to use ON CONFLICT DO UPDATE approach.
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 made an after update trigger which I intended to use as an audit log for changes to the primary record in source table. Everything is working as expected except when I view my trigger's definition I can see it resolved all columns for MYLIB.NMF00PAUDI and OTBL which means that I have to recreate this trigger for every column I add into source and audit tables.
Can I somehow retain SELECT *? This way I only need to modfiy source and audit tables.
CREATE TRIGGER MYLIB.NMF00PAUDIT
AFTER UPDATE ON MYLIB.NMF00P
REFERENCING OLD_TABLE AS OTBL
FOR EACH ROW
MODE DB2SQL
BEGIN ATOMIC
INSERT INTO MYLIB . NMF00PAUDI
SELECT * FROM OTBL
;
END
Becomes this when I view the definition:
BEGIN ATOMIC
INSERT INTO MYLIB . NMF00PAUDI (COL1, COL2, COL3)
SELECT COL1, COL2, COL3 FROM OTBL
;
END
Could you automate the creation of the trigger definition (either with a fancy text templating tool or even just a simple script) that could find the columns from sysibm.syscolumns and generate your trigger body using all the current columns? Then any time the table schema changes you just re-generate the script and re-apply the trigger to cover the new field.
i have a function:
CREATE OR REPLACE FUNCTION delete_student()
RETURNS TRIGGER AS
$BODY$
BEGIN
IF (TG_OP = 'DELETE')
THEN
INSERT INTO cancel(eno, excode,sno,cdate,cuser)
VALUES ((SELECT entry.eno FROM entry
JOIN student ON (entry.sno = student.sno)
WHERE entry.sno = OLD.sno),(SELECT entry.excode FROM entry
JOIN student ON (entry.sno = student.sno)
WHERE entry.sno = OLD.sno),
OLD.sno,current_timestamp,current_user);
END IF;
RETURN OLD;
END; $BODY$ LANGUAGE plpgsql;
and i also have the trigger:
CREATE TRIGGER delete_student
BEFORE DELETE
on student
FOR EACH ROW
EXECUTE PROCEDURE delete_student();
the idea is when i delete a student from the student relation then the entry in the entry relation also delete and my cancel relation updates.
this is what i put into my student relation:
INSERT INTO
student(sno, sname, semail) VALUES (1, 'a. adedeji', 'ayooladedeji#live.com');
and this is what i put into my entry relation:
INSERT INTO
entry(excode, sno, egrade) VALUES (1, 1, 98.56);
when i execute the command
DELETE FROM student WHERE sno = 1;
it deletes the student and also the corresponding entry and the query returns with no errors however when i run a select on my cancel table the table shows up empty?
You do not show how the corresponding entry is deleted. If the entry is deleted before the student record then that causes the problem because then the INSERT in the trigger will fail because the SELECT statement will not provide any values to insert. Is the corresponding entry deleted through a CASCADING delete on student?
Also, your trigger can be much simpler:
CREATE OR REPLACE FUNCTION delete_student() RETURNS trigger AS $BODY$
BEGIN
INSERT INTO cancel(eno, excode, sno, cdate, cuser)
VALUES (SELECT eno, excode, sno, current_timestamp, current_user
FROM entry
WHERE sno = OLD.sno);
RETURN OLD;
END; $BODY$ LANGUAGE plpgsql;
First of all, the function only fires on a DELETE trigger, so you do not have to test for TG_OP. Secondly, in the INSERT statement you never access any data from the student relation so there is no need to JOIN to that relation; the sno does come from the student relation, but through the OLD implicit parameter.
You didn't post your DB schema and it's not very clear what your problem is, but it looks like a cascade delete is interfering somewhere. Specifically:
Before deleting the student, you insert something into cancel that references it.
Postgres proceeds to delete the row in student.
Postgres proceeds to honors all applicable cascade deletes.
Postgres deletes rows in entry and ... cancel (including the one you just inserted).
A few remarks:
Firstly, and as a rule of thumb, before triggers should never have side-effects on anything but the row itself. Inserting row in a before delete trigger is a big no no: besides introducing potential problems related such as Postgres reporting an incorrect FOUND value or incorrect row counts upon completing the query, consider the case where a separate before trigger cancels the delete altogether by returning NULL. As such, your trigger function should be running on an after trigger -- only at that point can you be sure that the row is indeed deleted.
Secondly, you don't need these inefficient, redundant, and ugly-as-sin sub-select statements. Use the insert ... select ... variety of inserts instead:
INSERT INTO cancel(eno, excode,sno,cdate,cuser)
SELECT entry.eno entry.excode, OLD.sno, current_timestamp, current_user
FROM entry
WHERE entry.sno = OLD.sno;
Thirdly, your trigger should probably be running on the entry table, like so:
INSERT INTO cancel(eno, excode,sno,cdate,cuser)
SELECT OLD.eno OLD.excode, OLD.sno, current_timestamp, current_user;
Lastly, there might be a few problems in your schema. If there is a unique row in entry for each row in student, and you need information in entry to make your trigger work in order to fill in cancel, it probably means the two tables (student and entry) ought to be merged. Whether you merge them or not, you might also need to remove (or manually manage) some cascade deletes where applicable, in order to enforce the business logic in the order you need it to run.
Is there a way to prevent deletion of all rows in a table? I know how to user DDL triggers to prevent dropping a table or truncation. I once failed to highlight the entire commmand, leaving off the where clause, and deleted all rows in a table. I want to prevent this from occurring again unless a where clause is present.
You could do this with a trigger.
CREATE TRIGGER dbo.KeepRows
ON dbo.TableName
FOR DELETE
AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS (SELECT 1 FROM dbo.TableName)
BEGIN
RAISERROR('Naughty developer, you tried to delete the last row!', 11, 1);
ROLLBACK TRANSACTION;
END
END
GO
In order to avoid the delete and then rollback, which can be quite expensive if the table is large, you could instead implement this as an instead of trigger, which will prevent any work from happening (except the check of course). This assumes the column id is the primary key:
CREATE TRIGGER dbo.KeepRows2
ON dbo.TableName
INSTEAD OF DELETE
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (SELECT 1 FROM dbo.TableName WHERE id NOT IN (SELECT id FROM deleted))
BEGIN
DELETE dbo.TableName WHERE id IN (SELECT id FROM deleted);
END
-- optional:
ELSE
BEGIN
RAISERROR('Nice try, you need a WHERE clause!', 11, 1);
END
END
GO