Why trigger is doing unnecessary subtraction after a query in sql? - sql-server

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!

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.

Display Results Before Delete SQL Server

I'm so close to getting the answer to this, but I feel like I'm missing the final part.
I've created a DELETE trigger that should display values from the SELECT statement before they are deleted from the table. However, when the trigger is called I get a blank result, but the values (order_id=10001 AND product_id=25) are still deleted from the table.
I've verified the SELECT statement works before I've run the trigger, so I'm confident that part is correct.
I've tried using an INSTEAD OF DELETE trigger, but the values end up not being deleted. I don't believe there is a BEFORE DELETE function for SQL Server? Is there a work around?
Suggestions?
CREATE TRIGGER deleteOrderTrigger
ON order_details
FOR DELETE
AS
SELECT order_details.product_id, products.name,
order_details.quantity AS 'Quantity being deleted from order',
SUM(products.quantity_in_stock) + order_details.quantity AS 'In Stock Quantity after Deletion'
FROM order_details
LEFT JOIN products ON products.product_id = order_details.product_id
WHERE order_details.order_id = 10001 AND products.product_id = 25
GROUP BY order_details.product_id, products.name, order_details.quantity
GO
-- Below is the code that will fire the trigger
DELETE order_details
WHERE order_id = 10001 AND product_id = 25
#JoeC - using a proc is probably the best answer.
If you must use a trigger then remmeber that it must be coded to support sets. If someone executes delete order_details where order_id = 10001 then your trigger will need to return the stock level for every product on the order.
Also, when coding a trigger, you have access to a built in table named deleted. This table contains the records deleted.
So you can do something like this:
CREATE TRIGGER deleteOrderTrigger
ON order_details
FOR DELETE
AS
INSERT INTO deleted_order_products_log
SELECT order_details.product_id
,products.name
,[Quantity being deleted from order] = order_details.quantity
,[In Stock Quantity after Deletion] = SUM(products.quantity_in_stock) + order_details.quantity
FROM order_details
INNER JOIN deleted d
ON order_details.primaryKey = d.primaryKey
LEFT JOIN products
ON products.product_id = order_details.product_id
GROUP BY order_details.product_id
,products.name
,order_details.quantity;
You can then query the log file to get the results of the calculation.
I haven't used triggers a lot, but it seems you have an AFTER trigger, which is not able to read the deleted rows, that's why you get blank result.
The thing with triggers are the inserted and deleted tables, maybe this would help: https://learn.microsoft.com/en-us/sql/relational-databases/triggers/use-the-inserted-and-deleted-tables
You need a INSTEAD OF trigger as you mentioned, but you have to do the delete operation yourself. More on this subject here: https://learn.microsoft.com/en-us/sql/relational-databases/triggers/dml-triggers and one example in here: https://stackoverflow.com/a/3267726/5605866
Put the trigger results into an audit table and you will see the results of the trigger. I have never seen a trigger with a where clause similar to yours.
Do a insert into table x select.... You also want to use the deleted table(there is an inserted table) that is created just for each occurrence of the trigger that will contain any/all of the rows that were just deleted. The delete occurs in the main body of the code. The trigger is then invoked and the deleted table (you can do a delete * from deleted only in the trigger) that will contain the row(s) that were deleted.

SQL Server - Select inserted rows in a table, inside THE SAME transaction

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.

How do I make a trigger that only affects the row that was updated/inserted?

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

updating multiple tables with triggers - SQL Server

I need to update my tableA based on the insertions in tableB. i have created a trigger which fires when a row is inserted into tableB and simply copies it to tableA (which is exactly what i want). But the problem comes when i update my existing records in tableB, the rows in tableA already exist with the same primary key.
to deal with it, i tried to delete the existing row in tableA where the primary key matches for the updated row and then simply insert the row from the "inserted" table. But, tsql simply does not let me delete me from a trigger and gives an error stating "The multi-part identifier "INSERTED.Id" could not be bound."
Here is what i am trying to accomplish code-wise:
delete from dbo.CurrentItems where dbo.CurrentItems.CurrentItemId = INSERTED.Id
INSERT INTO CurrentItems
SELECT * FROM INSERTED
WHERE IsActive = 1
Any ideas guys?
In the DELETE FROM statement, you have to add the INSERTED pseudo-table to the tables you're operating on (in the FROM clause), like so:
DELETE dbo.CurrentItems
FROM dbo.CurrentItems
, INSERTED
WHERE dbo.CurrentItems.CurrentItemId = INSERTED.Id
INSERT INTO CurrentItems
SELECT * FROM INSERTED
WHERE IsActive = 1
Alternatively, you could use an INNER JOIN:
DELETE dbo.CurrentItems
FROM dbo.CurrentItems CI
INNER JOIN INSERTED I ON CI.CurrentItemId = I.Id
Why can't you just simply update the existing rows in TableA when your rows in TableB get updated?? UPDATE instead of DELETE/INSERT.
You could have a AFTER INSERT trigger which does what you want now, plus an AFTER UPDATE trigger which would be updating rows in TableA (instead of inserting new ones).
You said you have a trigger that fires when "a row is inserted into tableB". So you have the trigger to fire on Inserts. It sounds to me like you also have the trigger to fire on Updates as well. Did you want the trigger to fire on updates as well? If not, remove "Update" from the trigger definition.
Randy

Resources