SQL Server trigger execution - sql-server

Say I have an UPDATE trigger on tableA that inserts a new record into tableB.
CREATE TRIGGER insertIntoTableB
ON tableA
FOR UPDATE
AS
INSERT INTO tableB (...) VALUES (...)
GO
I then run these statements sequentially. Will the second UPDATE statement (UPDATE tableB) work OK? (i.e. wait for the trigger on table A to fully execute)
UPDATE tableA
SET ...
WHERE key = 'some key'
UPDATE tableB
SET ...
WHERE key = 'newly inserted key from trigger'

The behavior is subject to the nested triggers server configuration, see Using Nested Triggers:
Both DML and DDL triggers are nested
when a trigger performs an action that
initiates another trigger. These
actions can initiate other triggers,
and so on. DML and DDL triggers can be
nested up to 32 levels. You can
control whether AFTER triggers can be
nested through the nested triggers
server configuration option. INSTEAD
OF triggers (only DML triggers can be
INSTEAD OF triggers) can be nested
regardless of this setting.
When a trigger on table A fires and inside the trigger table B is updated, the trigger on table B runs immediately. The Table A trigger did not finish, it is blocked in waiting for the UPDATE statement to finish, which in turn waits for the Table B trigger to finish. However, the updates to table A have already occurred (assuming a normal AFTER trigger) and querying the Table A from the table B's trigger will see the updates.

If the updates are sequentially coded into the UPDATE trigger of A then yes.

Related

Prevent insert on trigger and perform an update instead

I have a trigger that is raised after an insert or update. In case of an insert, I want to check if the record being inserted on table exists. If exists, then I want to ignore the insert and perform an update within trigger for that record. How can I do this? Is is possible to do it within an after insert,update trigger (not using instead of trigger)?

Can the INSERTED table contain values from two simultaneous transactions that fired the same trigger? SQL Server 2012

I have the same application on different hosts bulk inserting into the same table. Each bulk insert fires a trigger. The structure of the table is as follows:
Hostname Quantity
---------------------
BOX_1 10
BOX_1 15
BOX_1 20
BOX_1 11
If I have the following code as part of the trigger:
DECLARE #hostname VARCHAR(20)
SELECT #hostname = Hostname
FROM INSERTED
Each bulk insert contains only one hostname since the application is only capturing data from the box its running on, but if two machines bulk insert simultaneously into the same table, could the INSERTED table be a combination of bulk inserts from different machines?
Or will the triggers execute sequentially, meaning the INSERTED table will always contain data from only one application at a time?
I need to know if my code setting the #hostname variable has any possibility of not being confined to just one choice.
The INSERTED (and DELETED) table will only ever contain rows from the statement that caused the trigger to fire.
See here: https://msdn.microsoft.com/en-us/library/ms191300(v=sql.110).aspx
The inserted table stores copies of the affected rows during INSERT
and UPDATE statements. During an insert or update transaction, new
rows are added to both the inserted table and the trigger table. The
rows in the inserted table are copies of the new rows in the trigger
table.
The rows in these tables are effectively scoped to the insert/update/delete statement that caused the trigger to fire initially.
See also here for some more info: SQL Server Trigger Isolation / Scope Documentation
But bear in mind in your trigger design that some other insert or update operation (a manual bulk insert, or data maintenance) might cause your trigger to fire, and the assumption about the hostname may no longer hold. Probably this will be years down the line after you've moved on or forgotten about this trigger!

Is an INSERT trigger recursive, or cause an infinite loop?

I have an INSERT trigger on table. When a row is inserted in table, the INSERT trigger is fired.
The trigger's behaviour is such that it will insert another row into the same table.
What would be the result of the INSERT statement?
Does this INSERT result in an infinite loop, or just the expected 2 inserts?
This is a setting in SQL- see the CREATE TRIGGER msdn page, specifically the section on Recursive Triggers. The setting you need to look into is RECURSIVE_TRIGGERS, if this is false, a trigger on Table1 will not trigger another insert into Table1. If you do allow recursive triggers, the limit is 32 levels deep.

getting rid of Insert trigger

Trying to explore solutions alternative to using insert triggers. Like API based ones and pros and cons with different approaches.
In an API approach you would create a procedure to perform both operations - something like:
package body emp_api is
procedure insert_emp (...) is
begin
insert into emp (...) values (...);
-- Insert that was previously in trigger
insert into other_table (...) values (...);
end;
end;
Then you force applications to use the API by giving them EXECUTE access to the api package but no INSERT/UPDATE/DELETE access to the tables.
If you want to guarantee that you'll have a record inserted into tableB when something inserts into tableA, then keep the trigger. You can disable if bulk loading into tableA and can guarantee you'll have the only process loading into that table during that time.
As soon as you remove the trigger, you have NO guarantees about inserts into tableB. Your only hope is that any and all programs that may insert into tableA (do you really know all of these?) adhere to the secondary insert into tableB. This is "data integrity via company policy", not data integrity enforced via Oracle.
This approach depends on how much you care about the state of the data in tableB I suppose.
I would NOT go the route of table apis (TAPIs), which now force any/all operations through some pl/sql api that handles the logic. These almost always tend to be slow and buggy in my experience.
In DDL You can disable a trigger with ALTER TRIGGER or ALTER TABLE.
ALTER TRIGGER triggername DISABLE; -- disable a single trigger
ALTER TABLE tablename DISABLE ALL TRIGGERS; -- disable all triggers on a table
To do this at runtime, you would have to use dynamic SQL and the schema in which the procedure is running must own the table or otherwise have the necessary privileges.
EXECUTE IMMEDIATE 'ALTER TRIGGER tablename DISABLE ALL TRIGGERS';
For more info on enabling/disabling triggers, see http://download.oracle.com/docs/cd/B28359_01/server.111/b28310/general004.htm

Why FOR Trigger does not run befor the action?

I am trying to write a trigger on a table to avoid insertion of two Names which are not flagged as IsDeleted. But the first part of selection contains the inserted one and so the condition is always true. I though that using FOR keyword causes the trigger to run before the INSERTION but in this case the inserted row is already in the table. Am I wrong or this is how all FOR trigger work?
ALTER TRIGGER TriggerName
ON MyTable
FOR INSERT, UPDATE
AS
BEGIN
If exist (select [Name] From MyTable WHERE IsDeleted = 0 AND [Name] in (SELECT [Name] FROM INSERTED)
BEGIN
RAISERROR ('ERROR Description', 16, 1);
Rollback;
END
END
FOR runs after the data is changed, INSTEAD OF is what I think you are after.
EDIT: As stated by others, INSTEAD OF runs instead of the data you are changing, therefore you need to insert the data if it is valid, rather than stopping the insert if it is invalid.
Read this question for a much more detailed explanation of the types of Triggers.
SQL Server "AFTER INSERT" trigger doesn't see the just-inserted row
FOR is the same as AFTER. if you want to "simulate" BEFORE trigger, use INSTEAD OF, caveat, it's not exactly what you would expect on proper BEFORE trigger, i.e. if you fail to provide the necessary INSTEAD action, your inserted/updated data could be lost/ignored.
MSSQL doesn't have BEFORE trigger.
For SQL Server, FOR runs AFTER the SQL which triggered it.
From:
http://msdn.microsoft.com/en-us/library/ms189799.aspx
FOR | AFTER
AFTER specifies that the DML trigger
is fired only when all operations
specified in the triggering SQL
statement have executed successfully.
All referential cascade actions and
constraint checks also must succeed
before this trigger fires.
AFTER is the default when FOR is the
only keyword specified.
AFTER triggers
cannot be defined on views.
I've actually ran into a similar problem lately, and found a cool way to handle it. I had a table which could have several rows for one id, but only ONE of them could be marked as primary.
In SQL Server 2008, you'll be able to make a partial unique index something like this:
create unique index IX on MyTable(name) where isDeleted = 0;
However, you can accomplish it with a little more work in SQL Server 2005. The trick is to make a view showing only the rows which aren't deleted, and then create a unique clustered index on it:
create view MyTableNotDeleted_vw
with schema_binding /* Must be schema bound to create an indexed view */
as
select name
from dbo.MyTable /* Have to use dbo. for schema bound views */
where isDeleted = 0;
GO
create unique clustered index IX on MyTableNotDeleted_vw ( name );
This will effectively create a unique constraint only affecting rows that haven't yet been deleted, and will probably perform better than a custom trigger!

Resources