Microsoft sql trigger update multiple lines - sql-server

I have a two tables, a Customers and a Sales table.
I am trying to create a trigger to update the amount of sales in the customer table when the Sales table is updated.
CREATE TRIGGER salesUPDATE
ON SALES
AFTER INSERT
AS
UPDATE Customers
SET salesAmount = Sales.Amount
GO
But I get that sales does not exist. Should I be using a join?
Will this trigger update all columns or do I need to specify which column to update?

CREATE TRIGGER salesUpdate ON SALES
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
;WITH cteAffectedCustomers AS (
SELECT DISTINCT CustomerId
FROM
inserted
UNION
SELECT DISTINCT CustomerId
FROM
deleted
)
, cteAggregations AS (
SELECT
ca.CustomerId
,SUM(ISNULL(s.Amount,0)) as SalesAmount
,COUNT(s.SalesId) as NumOfSales
FROM
cteAffectedCustomers ca
INNER JOIN Customers c
ON ca.CustomerId = c.CustomerId
LEFT JOIN Sales s
ON ca.CustomerId = s.CustomerId
GROUP BY
ca.CustomerId
)
UPDATE c
SET SalesAmount = ca.SalesAmount
,NumOfSales = ca.NumOfSales
FROM
Customers c
INNER JOIN cteAggregations ca
ON c.CustomerId = ca.CustomerId
END
Here is an example of the type of logic you would need to create to maintain a pre-agregated value. If you want to SUM an Amount in the Sales table you will need an AFTER INSERT, UPDATE, and DELETE. Then you would need to:
determine all of the affected customers So you don't update the entire customer table
do the aggregation
update with an inner join to the aggregated data
A note about triggers, they are a set based operation NOT a scalar. That means they fire once for x# of rows NOT x# of times for x# of rows. So you have to account for multiple records during updates and do joins just like would outside of a trigger when updating one table with another.
This has a performance impact to write operations but does expedite your reads, however if you are not in an extremely extremely high read volume operation you would do better to use a view/query and optimize your indexes. There is less likely hood of the synchronization of the aggregate data getting messed up. If you do go the trigger route I suggest you also have a SQL job set up on some reasonable increment (nightly) that checks and rectifies any inconsistencies that may occur.

Use magic table inserted
CREATE TRIGGER salesUPDATE
ON SALES
AFTER INSERT
AS
BEGIN
Declare #Amount varchar(50) = (Select top 1 Amount from inserted)
UPDATE Customers
SET salesAmount = #Amount
END
GO
Note : top 1 for insertion of multiple records

Use the inserted table name which contains the new value:
CREATE TRIGGER salesUPDATE
ON SALES
AFTER INSERT
AS
UPDATE Customers
SET salesAmount = inserted.Amount
GO

Related

SQL Server : AFTER INSERT trigger and INSERT INTO

I want to create a trigger that will fill up my sales history base after firing in the ORDER table.
I am creating a specific order in regular data base and after that this order automatically goes to sales_history database.
Below part works properly.
When I create a new order in regular database my sales_history database is growing with new ID_ORDERS, hooray! :)
ALTER TRIGGER [dbo].[InsertTrig]
ON [dbo].[ORDER]
AFTER INSERT
AS
IF EXISTS (SELECT *
FROM inserted i
WHERE i.ID_TYPE = 1) -- specific order type
BEGIN
INSERT INTO id.dbo.sales_history (id_order)
SELECT i.ID_ORDER FROM inserted i
END
The problem arises when I want join another table. The trigger stops working
ALTER TRIGGER [dbo].[InsertTrig]
ON [dbo].[ORDER]
AFTER INSERT
AS
IF EXISTS (SELECT *
FROM inserted i
WHERE i.ID_TYPE = 1) -- specific order type
BEGIN
INSERT INTO id.dbo.sales_history (id_order, id_item)
SELECT
inserted.ID_ORDER, ORDER_DETAILS.ID_ITEM
FROM
inserted
INNER JOIN
ORDER_DETAILS ON ORDER_DETAILS.ID_ORDER = inserted.ID_ORDER
END
I also tried this way, and still nothing :(
ALTER TRIGGER [dbo].[InsertTrig]
ON [dbo].[ORDER]
AFTER INSERT
AS
IF EXISTS (SELECT *
FROM inserted i
WHERE i.ID_TYPE = 1) -- specific order type
BEGIN
DECLARE #xyz AS numeric(18, 0)
SET #xyz = (SELECT inserted.ID_ORDER FROM inserted)
INSERT INTO id.dbo.sales_history (id_order, id_item)
SELECT
ORDER.ID_ORDER, ORDER_DETAILS.ID_ITEM
FROM
ORDER
INNER JOIN
ORDER_DETAILS ON ORDER_DETAILS.ID_ORDER = ORDER.ID_ORDER
WHERE
ORDER.ID_ORDER = #xyz
END
I want to create a trigger that will automatically fill up my sales history base after firing in ORDER table.
Your trigger is on the Order table, meaning SQL Server fires it after you insert records into the Order table. At which point, the relevant records in the Order_Details table couldn't have been inserted yet, because they have a foreign key to the Order table.
This is why an inner join between your inserted table and the Order_details table returns 0 rows.
If you want your sales_history from the order_details table, you have to populate it after you insert the records to the order_details table.
CREATE OR ALTER TRIGGER [dbo].[OrderDetails_AfterInsert]
ON [dbo].[ORDER_DETAILS]
AFTER INSERT
AS
INSERT INTO id.dbo.sales_history (id_order, id_item)
SELECT
inserted.ID_ORDER, inserted.ID_ITEM
FROM
inserted
INNER JOIN
[ORDER] ON [ORDER].ID_ORDER = inserted.ID_ORDER
WHERE [ORDER].ID_TYPE = 1 -- specific order type
As a side note: InsertTrig is bad name. Note the name of the trigger in my answer - it tells you exactly what this trigger is for, and on what table.

Creating Trigger to get data from one table to another and generating a timestamp

I am trying to track Inventory where data would be entered in an Excel sheet (SQL Spreads) and then updates the SQL table and then gather the sum of that data and put it onto another table that would then generate a timestamp to when it was changed/updated/inserted.
The Pictures with highlighted columns is where I want to have the data in.
(TotalBinLo --> Binlocation)
and then when Binlocation is populated (inserted/updated/deleted) generating a timestamp (MM/DD/YYYY HH:MM:SS)
This is what I've come up so far.
---This Trigger is working when pulling data from one table into another--
Create Trigger tr_BC_totalbinLoc
on bincount
After Update
AS
Begin
update OnHandInv
set OnHandInv.binlocation = bincount.totalbinlo
from bincount
inner join OnHandInv on bincount.partnumber = OnHandInv.PartNumber;
End
---Another Trigger (Works) but enters in date for all rows. (Don't want) (only need for one column.)
Create Trigger tr_totalbinLoc_OHI
On Onhandinv
After Update
AS
Update Onhandinv
set dateupdated = getutcdate()
where PartNumber in (select distinct PartNumber from onhandinv)
totalbinlo
ColNeedToPopu
You need to join the inserted table to OnHandInv. I have assumed that PartNumber is the primary key to join on.
You also need to remove deleted rows which exactly match, in other words rows where no change has actually been made.
CREATE OR ALTER TRIGGER tr_BC_totalbinLoc
ON bincount
AFTER UPDATE
AS
SET NOCOUNT ON;
IF NOT EXISTS (SELECT 1 FROM inserted)
RETURN;
UPDATE o
SET
binlocation = i.totalbinlo
dateupdated = getutcdate()
FROM (
SELECT partnumber, totalbinlo
FROM inserted i
EXCEPT
SELECT partnumber, totalbinlo
FROM deleted d
) i
INNER JOIN OnHandInv o ON i.partnumber = i.PartNumber;
GO

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

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!

How do I use where clause in the sql trigger?

I am trying to create a trigger where the account balance is automatically updated from the transaction table, but I am not being able to update a particular table. My trigger changes the account balance of every customer. I want to know how I can use where clause to make it customer-specific. Here is what I have:
create trigger trig_one
on dbo.transactions
for insert, update, delete
as begin
declare #amount int
select #amount = (select amount from inserted)
update dbo.account set balance = balance + #amount
end
Please help me out. Thanks!
You structure the query like this:
update dbo.account
set balance = balance + i.amount
from dbo.account a join
inserted i
on i.accountid = a.accountid ;
(The join key is whatever the join key should be.)
Of course, you need to do the same thing for the deleted. Note: this will work for multiple rows being inserted. That is handy. Your version will fail unexpectedly.

Weird Behavior of Trigger

I've got a trigger (SQL 2008 R2) that does one simple operation but the results are not logical.
Here is the overview:
A text file is fed to an SSIS package with ONE line(one record) that loads it into "ORDERS_IN_PROCESS" table. The Data Access Mode is set to "Table or view" in the "OLE DB Destination" to allow triggers to fire.
Here is my ORDERS table:
OrderID ItemNo
--------- ---------
9813 1
9813 2
9813 3
9817 1
So, SSIS executes and
ORDERS_IN_PROCESS gets one record inserted which is OrderID 9813
Trigger is fired:
INSERT INTO ORDERS_ARCHIVE SELECT * FROM ORDERS WHERE OrderID=INSERTED.OrderID
Pretty simple so far...
The results I get in my ORDERS_ARCHIVE (identical layout to ORDERS) Table are
OrderId ItemNo
--------- ----------
9813 3
Where are the rest of the 2 line items?
Note, it only inserted the last row read from ORDERS table into ORDERS_ARCHIVE.
I need all 3 of them in ORDERS_ARCHIVE.
Why does this happen?
I believe it has something to do with the way SSIS processes it using "OLE DB Destination" because if I insert a record into RLFL manually, the trigger does exactly what it's supposed to do and inserts all 3 records from BACK.
You may argue that trigger fires once per batch and I agree but in this case I have a batch of just ONE record.
I'm thinking of an sp, but i'd rather not add another level of complexity for something so trivial, supposedly.
Any ideas?
Thanks.
I don't think that this has something to do with SSIS at all, but with your trigger. Instead of than IN that you have there, try using a JOIN in your query:
INSERT INTO ORDERS_ARCHIVE
SELECT O.*
FROM ORDERS O
INNER JOIN INSERTED I
ON O.ORderID = I.OrderID
I concur with Lamark, misspelling intentional, with the assessment of your trigger is incorrect.
The logic you provided for your trigger does not compile. The WHERE clause is not valid. I'm assuming, as Lamak did that your intention was to join based on OrderID.
create table dbo.ORDERS_ARCHIVE
(
OrderID int
, ItemNo int
)
GO
create table dbo.ORDERS
(
OrderID int
, ItemNo int
)
GO
create trigger
trUpdate
ON
dbo.ORDERS
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
-- This doesn't work
-- Msg 4104, Level 16, State 1, Procedure trUpdate, Line 12
-- The multi-part identifier "INSERTED.OrderID" could not be bound.
--INSERT INTO dbo.ORDERS_ARCHIVE
--SELECT *
--FROM ORDERS
--WHERE OrderID=INSERTED.OrderID;
-- I think you meant
INSERT INTO dbo.ORDERS_ARCHIVE
SELECT *
FROM ORDERS
WHERE OrderID=(SELECT INSERTED.OrderID FROM INSERTED);
END
GO
I then ginned up a simple SSIS package, I have a data source that supplies the 4 rows you indicated and writes to dbo.ORDERS. I ran the package 2 times and each one netted 4 rows in the ORDERS_ARCHIVE table. 3 rows with 9813, 1 with 9817 per batch.
I am getting the right count of rows in there so I believe the trigger is firing correctly. Instead, what is happening is the logic is incorrect. Since the OrderID is not unique in the ORDERS table, the database engine is going to pick the first row that happens to satisfy the search criteria. It just so happens that it picks the same row (ItemNo = 1) each time but since there is no guarantee of order without an ORDER BY clause, this is just random or an artifact of how the Engine chooses but no behaviour I would bank on remaining consistent.
How do you fix this?
Fix the trigger. Joining to the inserted virtual table only on the OrderID is resulting in multiple rows satisfying the condition.
create trigger
trUpdate
ON
dbo.ORDERS
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
-- This trigger will add all the rows from the ORDERS table
-- that match what was just inserted based on OrderID and ItemNo
INSERT INTO dbo.ORDERS_ARCHIVE
SELECT O.*
FROM dbo.ORDERS O
INNER JOIN INSERTED I
ON O.OrderID = I.OrderID
AND O.ItemNo = I.ItemNo;
END
Now when I run the ETL, I see 4 rows in ORDERS_ARCHIVE with the correct ItemNo values.

Resources