inserted table role in instead of insert trigger - sql-server

I have a view based upon 2 tables, I have written an instead of insert trigger to insert into that view:
Create trigger tr_vWEmployeeDetails_InsteadOfInsert
on vWEmployeeDetails
Instead Of Insert
as
Begin
Declare #DeptId int
Select #DeptId = DeptId
from tblDepartment
join inserted on inserted.DeptName = tblDepartment.DeptName
if(#DeptId is null)
Begin
Raiserror('Invalid Department Name. Statement terminated', 16, 1)
return
End
Insert into tblEmployee(Id, Name, Gender, DepartmentId)
Select Id, Name, Gender, #DeptId
from inserted
End
The above code works fine, but I want to know that the insert statement to the view doesn't work, instead the trigger works, then from where do we get values in the 'inserted' magic table.
Please explain.

From the relevant MSDN page:
DML trigger statements use two special tables: the deleted table and the inserted tables. SQL Server automatically creates and manages these tables. You can use these temporary, memory-resident tables to test the effects of certain data modifications and to set conditions for DML trigger actions. You cannot directly modify the data in the tables or perform data definition language (DDL) operations on the tables, such as CREATE INDEX.
The insert on the view is blocked by the use of the instead of trigger - That's literally the meaning of "instead of".
Please note that your trigger code will fail if an insert statement to the view will try to insert multiple records. The reason for this is that in SQL Server, triggers are raised per statement, and not per row. The following code that is specified in the beginning of the trigger
Declare #DeptId int
Select #DeptId = DeptId
from tblDepartment
join inserted
on inserted.DeptName = tblDepartment.DeptName
Will fail once the inserted table will have multiple rows that matches the on clause if the join.
Triggers in SQL Server must always be written to handle multiple rows in the inserted / deleted table.

Related

Triggers Inner join inserted with orginial table

I was reviewing creating DML triggers in SQL Server in SQL docs: Use the inserted and deleted Tables
There is an example which do the following:
The following example creates a DML trigger. This trigger checks to make sure the credit rating for the vendor is good when an attempt is made to insert a new purchase order into the PurchaseOrderHeader table. To obtain the credit rating of the vendor corresponding to the purchase order that was just inserted, the Vendor table must be referenced and joined with the inserted table. If the credit rating is too low, a message is displayed and the insertion does not execute.
Note that this example does not allow for multirow data modifications.
USE AdventureWorks2012;
GO
IF OBJECT_ID ('Purchasing.LowCredit','TR') IS NOT NULL
DROP TRIGGER Purchasing.LowCredit;
GO
-- This trigger prevents a row from being inserted in the Purchasing.PurchaseOrderHeader table
-- when the credit rating of the specified vendor is set to 5 (below average).
CREATE TRIGGER Purchasing.LowCredit
ON Purchasing.PurchaseOrderHeader
AFTER INSERT
AS
IF EXISTS (SELECT *
FROM Purchasing.PurchaseOrderHeader p
JOIN inserted AS i ON p.PurchaseOrderID = i.PurchaseOrderID
JOIN Purchasing.Vendor AS v ON v.BusinessEntityID = p.VendorID
WHERE v.CreditRating = 5)
BEGIN
RAISERROR ('A vendor''s credit rating is too low to accept new purchase orders.', 16, 1);
ROLLBACK TRANSACTION;
RETURN
END;
GO
I wonder why the example inner joined the inserted table with Purchasing.PurchaseOrderHeader table and then join the vendor table.
Can I get the same result using only the inserted table joining the vendor table directly without joining with Purchasing.PurchaseOrderHeader table?
Not only do I believe you are correct, I think there are further inaccuracies on this documentation page - which I have to admit is pretty rare in my experience.
Take for example this part:
Note that this example does not allow for multirow data modifications.
This is a false claim. The trigger code example will handle multiple rows insert as well as a single row insert.
Note that according to the documented (and observed) behavior, the inserted table will contain all the rows inserted (or updated) to the trigger's target table:
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.
Therefor, the join to inserted should be enough in this case to enforce the business rule discussed.
That being said, using triggers to enforce business rules might prove difficult and even problematic - note that this trigger only covers inserted rows, but not updated rows. This means that a new row might be inserted with valid values, and later on updated to invalid values.

SQL Server trigger (before insert) to validate data?

We have an application that we use in the warehouse to track which pickers are picking which orders. We have an instance every so often where the the order number that is inserted into the table is not the actual order number. While we are going to put in some validation on the application, I was wondering how we could put a trigger in place on the Table to prevent writing the record when the order number is wrong.
For example:
Before insert, when len(ordernum) <> '6' then delete record.
else insert into Table1
?
INSTEAD OF triggers cause their source DML operation to skip and they just execute the code provided inside them. Actual insert, delete or update operation do not occur at all. However they have their associated inserted and deleted tables simulating the DML operation. Inserted and deleted tables are widely used in operations inside triggers
--Instead of Trigger
CREATE TRIGGER BeforeDelete_Trigger ON [dbo].[Order1]
INSTEAD OF INSERT AS
BEGIN
IF (SELECT * FROM Order1 WHERE len(ordernum) <> '6')
THEN
INSERT INTO Order1;
ELSE
INSERT INTO Order2 (col 1, col 2) VALUES (inserted.col1, inserted.col2);
END;
GO
you can create an instead of trigger:
--Create an INSTEAD OF INSERT trigger on the table:
CREATE TRIGGER InsteadTrigger on dbo.table1
INSTEAD OF INSERT
AS
BEGIN
if len(inserted.ordernum)= 6
insert into table1 (ordernum,col1, col2) values (inserted.ordernum,inserted.col1, inserted.col2)
--rows that dont have a ordernum length of 6 are not inserted
end

SQL Script add records with identity FK

I am trying to create an SQL script to insert a new row and use that row's identity column as an FK when inserting into another table.
This is what I use for a one-to-one relationship:
INSERT INTO userTable(name) VALUES(N'admin')
INSERT INTO adminsTable(userId,permissions) SELECT userId,255 FROM userTable WHERE name=N'admin'
But now I also have a one-to-many relationship, and I asked myself whether I can use less SELECT queries than this:
INSERT INTO bonusCodeTypes(name) VALUES(N'1500 pages')
INSERT INTO bonusCodeInstances(codeType,codeNo,isRedeemed) SELECT name,N'123456',0 FROM bonusCodeTypes WHERE name=N'1500 pages'
INSERT INTO bonusCodeInstances(codeType,codeNo,isRedeemed) SELECT name,N'012345',0 FROM bonusCodeTypes WHERE name=N'1500 pages'
I could also use sth like this:
INSERT INTO bonusCodeInstances(codeType,codeNo,isRedeemed)
SELECT name,bonusCode,0 FROM bonusCodeTypes JOIN
(SELECT N'123456' AS bonusCode UNION SELECT N'012345' AS bonusCode)
WHERE name=N'1500 pages'
but this is also a very complicated way of inserting all the codes, I don't know whether it is even faster.
So, is there a possibility to use a variable inside SQL statements? Like
var lastinsertID = INSERT INTO bonusCodeTypes(name) OUTPUT inserted.id VALUES(N'300 pages')
INSERT INTO bonusCodeInstances(codeType,codeNo,isRedeemed) VALUES(lastinsertID,N'123456',0)
OUTPUT can only insert into a table. If you're only inserting a single record, it's much more convenient to use SCOPE_IDENTITY(), which holds the value of the most recently inserted identity value. If you need a range of values, one technique is to OUTPUT all the identity values into a temp table or table variable along with the business keys, and join on that -- but provided the table you are inserting into has an index on those keys (and why shouldn't it) this buys you nothing over simply joining the base table in a transaction, other than lots more I/O.
So, in your example:
INSERT INTO bonusCodeTypes(name) VALUES(N'300 pages');
DECLARE #lastInsertID INT = SCOPE_IDENTITY();
INSERT INTO bonusCodeInstances(codeType,codeNo,isRedeemed) VALUES (#lastInsertID, N'123456',0);
SELECT #lastInsertID AS id; -- if you want to return the value to the client, as OUTPUT implies
Instead of VALUES, you can of course join on a table instead, provided you need the same #lastInsertID value everywhere.
As to your original question, yes, you can also assign variables from statements -- but not with OUTPUT. However, SELECT #x = TOP(1) something FROM table is perfectly OK.

Finding out which rows are inserted , updated or deleted using triggers

I have a table named indication in Database and it has three columns Name, Age, and Enable.
I want to create a trigger that inserts an alarm into my alarm table whenever the Age is under 18 and Enable is true, I want to check the record on the indication table at the exact moment that it has been inserted, that way I can check whether it should be inserted in alarm or not.
I found COLUMNS_UPDATED (Transact-SQL) on MSDN and it works for updated columns, is there the same thing for ROWS_UPDATED?
You can always set your trigger to respond to only an INSERT action, with
CREATE TRIGGER TR_Whatever_I ON dbo.YourTable FOR INSERT
AS
... (body of trigger)
Be aware FOR INSERT is the same as AFTER INSERT. You also have the option of INSTEAD OF, but with that you have to perform the data modification yourself. There is no BEFORE trigger in SQL Server.
In some cases it is very convenient to handle more than one action at once because the script for the different actions is similar--why write three triggers when you can write just one? So in the case where your trigger looks more like this:
CREATE TRIGGER TR_Whatever_IUD ON dbo.YourTable FOR INSERT, UPDATE, DELETE
AS
... (body of trigger)
Then you don't automatically know it was an insert in the body. In this case, you can detect whether it's an insert similar to this:
IF EXISTS (SELECT * FROM Inserted)
AND NOT EXISTS (SELECT * FROM Deleted) BEGIN
--It's an INSERT.
END
Or, if you want to determine which of the three DML operations it is:
DECLARE #DataOperation char(1);
SET #DataOperation =
CASE
WHEN NOT EXISTS (SELECT * FROM Inserted) THEN 'D'
WHEN NOT EXISTS (SELECT * FROM Deleted) THEN 'I'
ELSE 'U'
END
;
Triggers still run if a DML operation affects no rows (for example, UPDATE dbo.YourTable SET Column = '' WHERE 1 = 0). In this case, you can't tell whether it was an update, delete, or insert--but since no modification occurred, it doesn't matter.
A Special Note
It's worth mentioning that in SQL Server, triggers fire once per operation, NOT once per row. This means that the Inserted and Deleted meta-tables will have as many rows in them during trigger execution as there are rows affected by the operation. Be careful and don't write triggers that assume there will only be one row.
Firstly I think you have to increase your knowledge on the way triggers work, and what the different type of triggers are.
You can create a trigger like this
CREATE TRIGGER trg_Indication_Ins
ON Indication
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
Insert Alarms (column1, column2) Select value1, value2 from inserted where Age < 18 and Enable = 1
END
This should basically do what you are looking for, and from what I understand from your quesion.
UPDATE:
Basically triggers can fire on INSERT, UPDATE or DELETE or any combination of the three, You can also set it to fire 'FOR/AFTER' the event (of which both actually means AFTER), or INSTEAD OF the event. A trigger will always have "internal" or meta-tables on the event.
These tables are inserted and deleted
The inserted table is basically all the new records that is applied to the table, and the deleted table have all the records that will be removed. In the case of the UPDATE event, the inserted table will have all the new values and deleted will have all the old values.
The inserted table will be empty on a DELETE trigger, and the deleted table will be empty on an INSERT trigger
Triggers can affect performance drastically if not used properly, so use it wisely.

Trigger that updates just the inserted row

I'm trying to create a simple trigger using TSQL (or SQL Server 2008). The problem is: my current trigger is updating the entire table. This was fine for a while, but now the table has more than 20k rows. So I want a trigger that only updates the rows that are being inserted.
Here's my current simple trigger:
CREATE TRIGGER trig_MyPplUpdate
ON [Persons]
FOR INSERT
AS
Begin
Update Persons
set MyFile = NULL
where Len(MyFile) < 60
End
I think I'll have to use either the "inserted" table or the row_number function ordered by the primary key. Any ideas?
If it is necessary to use a trigger here at all I would use an INSTEAD OF trigger to adjust the values pre-insert and avoid the need to JOIN back onto the base table and Update them afterwards.
CREATE TRIGGER trig_MyPplUpdate
ON [Persons]
INSTEAD OF INSERT
AS
BEGIN
INSERT INTO Persons
SELECT foo,
bar,
CASE
WHEN Len(MyFile) >= 60 THEN MyFile
END
FROM Inserted
END
You need to join the Inserted pseudo table in your UPDATE statement. Always be aware that SQL Server fires the trigger once per statement and that statement can very well modify/insert multiple rows at once - so your Inserted table quite likely will contain more than one row - just need to keep that in mind when you write your trigger.
Try something like this:
CREATE TRIGGER trig_MyPplUpdate
ON [Persons]
FOR INSERT
AS
UPDATE dbo.Persons
SET MyFile = NULL
WHERE Len(MyFile) < 60
AND PersonID IN (SELECT DISTINCT PersonID FROM Inserted)
or use whatever unique column (your primary key) you have to get exactly those rows that have been inserted.

Resources