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.
Related
I have two tables:
Order and
Product.
I want a specific column(OnShelfQuantity) in the Product table to be updated as a new row is added in the Order table. I have used the below query to implement a trigger which will do that. But the problem is that when I insert a row in the Order table and then later check the Product table to see the changes, I notice that the Product table has been updated 3 times. For e.g: Order quantity inserted = 10, then only 10 should be subtracted from Product_TAB.OnShelfQuantity. But 30 gets subtracted. Please help!
create trigger dbo.Trigge
ON dbo.Ordertable
AFTER INSERT
AS
BEGIN
update Product_TAB set OnShelfQuantity= Product_TAB.OnShelfQuantity - Ordertable.Quantity
FROM dbo.Product_TAB
INNER JOIN Ordertable
ON Ordertable.ProductID = Product_TAB.ProductID;
END;
I think, you can use INSERTED table to resolve this Issue.
Inserted table is a table which is used by triggers to store the frequently inserted records in the tables.
So, you can use the same in your update statement to avoid this.
update Product_TAB set OnShelfQuantity= Product_TAB.OnShelfQuantity -
Ordertable.Quantity
FROM dbo.Product_TAB
INNER JOIN Ordertable ON Ordertable.ProductID = Product_TAB.ProductID
INNER JOIN inserted INS ON INS.Order_ID=Ordertable.Order_ID
You can have multiple rows in the inserted table. And, these rows could have the same product. A row in the target table is only updated once in an update statement. Hence, you want to aggregate the data before the update:
create trigger dbo.Trigge
ON dbo.Ordertable
AFTER INSERT
AS
BEGIN
update p
set OnShelfQuantity= p.OnShelfQuantity - i.total_quantity
from dbo.Product_TAB p JOIN
(SELECT i.ProductId, SUM(i.Quantity) as total_quantity
FROM inserted i
GROUP BY i.ProductId
) i
on i.ProductID = p.ProductID;
END;
Note that this only uses inserted and not the original table.
So the issue was that I was inserting new rows but with the same Order ID. This is why it was doing additional subtraction which I didn't require. So now I have to just insert a new row but with unique OrderID. Thanks to everyone who replied above!
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.
I have a problem where I need to check that two columns in each table in a database are unique.
We have the database with barcode entries called uid and rid.
Table 1: T1.uid
And
Table 2: T2.rid
No barcodes in the two table columns must be the same.
How can we ensure that.
If a insertion of a barcode into table T1.uid matches an entry in
T2.rid we want to throw an error.
The tables are cleaned up and is in a consistent state where the entries in
T1.uid and T2.rid are unique over both table columns.
It is not possible to insert NULL values in the tables respective uid and tid column(T1.uid and T2.rid)
It is not possible to create a new table for all barcodes.
Because we don't have full control of the database server.
EDIT 19-02-2015
This solution cannot work for us, because we cannot make a new table
to keep track of the unique names(see table illustration).
We want to have a constraint over two columns in different tables without changing the schema.
Per the illustration we want to make it impossible for john to exist in
T2 because he already exists in table T1. So an error must be "thrown"
when we try to insert John in T2.Name.
The reason is that we have different suppliers that inserts into these tables
in different ways, if we change the schema layout, all suppliers would
need to change their database queries. The total work is just to much,
if we force every suppplier to make changes.
So we need something unobtrusive, that doesnt require the suppliers to change
their code.
A example could be that T1.Name is unique and do not accept NULL values.
If we try insert an existing name, like "Alan", then an exception will occur
because the column has unique values.
But we want to check for uniqueness in T2.Name at the same time.
The new inserted value should be unique over the two tables.
Maybe something like this:
SELECT uid FROM Table1
Where Exists (
SELECT rid FROM Table2
WHERE Table1.uid = rid )
This will show all rows from Table1 where their column uid has an equivalent in column rid of Table2.
The condition before the insertion happens could look like below. #Idis the id you need to insert the data for.
DECLARE #allowed INT;
SELECT #allowed = COUNT(*)
FROM
(
SELECT T1.uid FROM T1 WHERE T1.uid = #Id
UNION ALL
SELECT T2.rid FROM T2 WHERE T2.rid = #id
)
WHERE
#id IS NOT NULL;
IF #allowed = 0
BEGIN
---- insert allowed
SELECT 0;
END
Thanks to all who answered.
I have solved the problem. A trigger is added to the database
everytime an insert or update procedure is executed, we catch it
check that the value(s) to be inserted doens't exist in the columns of the two
tables. if that check is succesfull we exceute the original query.
Otherwise we rollback the query.
http://www.codeproject.com/Articles/25600/Triggers-SQL-Server
Instead Of Triggers
I need confirmation for one "tricky" question.
I am in a transaction, in which i insert some rows in a table.
After that, querying the same table, and based on the results, i insert in another table.
My question is: Will the inserted rows in the first table be visible when inserting in the second table? (I need the inserted rows to be visible).
My first Insert Is :
INSERT INTO BioUsers Select NewId(),A.BadgeNr,GetDate(),A.PersNr
FROM (
SELECT CB.Persnr,C.BadgeNr FROM CurBioDistribution CB
INNER JOIN CUR C ON C.PersNr = CB.PersNr
WHERE
CB.[Type] = 1
AND CB.GroupNr = #Nr
AND CB.PersNr IN (SELECT PersNr FROM CurBioDistribution WHERE GroupNr = #Nr)
EXCEPT (SELECT PersNr, BadgeId as BadgeNr FROM BioUsers)
) A
I want To insert all the BioUserID's in the first table in another table, which contains the field BioUserID, but only if they are not already in it. So when select from the first one, i want to get the inserted rows too.
PS: I searched for this problem, but i could only find answers about this issue when multiple transaction (from different clients) are involved.
If both inserts are in the same transaction, then yes.
If not then you are in what you described as a multi-user scenario. Multi user is infact misleading, multi transaction would be more correct.
I have a table with two columns where I need one (columnB) to be a copy of the other one (columnA). So, if a row is inserted or updated, I want the value from columnA to be copied to columnB.
Here's what I have now:
CREATE TRIGGER tUpdateColB
ON products
FOR INSERT, UPDATE AS
BEGIN
UPDATE table
SET columnB = columnA
END
The problem now is that the query affects all rows, not just the one that was updated or inserted. How would I go about fixing that?
Assuming you have a primary key column, id, (and you should have a primary key), join to the inserted table (making the trigger capable of handling multiple rows):
CREATE TRIGGER tUpdateColB
ON products
FOR INSERT, UPDATE AS
BEGIN
UPDATE table
SET t.columnB = i.columnA
FROM table t INNER JOIN inserted i ON t.id = i.id
END
But if ColumnB is always a copy of ColumnA, why not create a Computed column instead?
Using the inserted and deleted Tables
There is a special inserted table available in triggers that will contain the "after" version of rows impacted by an INSERT or UPDATE operation. Similarly, there is a deleted table that will contain the "before" version of rows impacted by an UPDATE or DELETE operation.
So, for your specific case:
UPDATE t
SET t.columnB = t.columnA
FROM inserted i
INNER JOIN table t
ON i.PrimaryKeyColumn = t.PrimaryKeyColumn