Fire Trigger Only After All Items have been Inserted in the Table? - sql-server

We have table that holds each item on an order in a separate row. In basic terms, I want to use a trigger that fires when an order is entered to set a field for each item to either A if the total value of the order is >= $50 and set it to B if it's less than that. I've written the trigger and it works but only on each item.
For example, item 1 is $75 and item 2 is $25. Total of the order is $100 so I want the trigger to set both items to A. However, the trigger is working on each separately item 1 is being set to A and item 2 is being set to B.
Is it possible to make the trigger work on the entire order instead of each line item separately? If so, how? If not, is there another method I can use to accomplish this?
This is simplified version of my trigger so far:
ALTER TRIGGER Disposition
ON order_line
FOR UPDATE
AS
IF (##rowcount = 0)
RETURN
SET NOCOUNT ON
DECLARE #order_min int
SET #order_min = (SELECT DISTINCT SUM(item_price) order_min
FROM order_line ol
INNER JOIN inserted i ON i.order_no = o.order_no
IF #order_min >= 50
BEGIN
UPDATE order_line
SET disposition = 'A'
FROM inserted
INNER JOIN order_line ON inserted.order_no = order_line.order_no
AND inserted.item = order_line.item
WHERE order_line.order_no = inserted.order_no
AND inserted.line_no = order_line.line_no
RETURN
END
ELSE IF #order_min < 50
BEGIN
UPDATE order_line
SET disposition = 'B'
FROM inserted
INNER JOIN order_line ON inserted.order_no = order_line.order_no
AND inserted.item = order_line.item
WHERE order_line.order_no = inserted.order_no
AND inserted.line_no = order_line.line_no
RETURN
END

Related

SQL Trigger action based off comparing deleted and inserted value

I have written an inventory application in C# and want to track changes to inventory quantity. To that end, I have two tables: tblInventory and tblInvChange. Currently I have an AFTER UPDATE Trigger which works great by adding the following to the tblInvChange: (using d. for Deleted and i. for inserted) d.lastphysical; i.lastphysical; d.quantity; i.quantity.
My problem is if, on the same date, I go back and change the quantity of an item I will get two records for the same date on the same item. I would like to have the trigger insert a record into the tblInvChange table if the date does not exist and update a current record if it does.
BEGIN
If(Select [fldlastPhysical] from deleted) <> (Select [fldLastPhysical] from inserted)
INSERT tblInvTracking(keyProductID, fldLocationId, fldLastPhysical, fldQuantity, fldInventoryChange, fldNewQuantity)
SELECT
D.keyProductID, D.fldLocationID, D.fldLastPhysical, d.fldQuantity, i.fldLastPhysical, i.fldQuantity
FROM
DELETED D JOIN INSERTED I ON D.keyProductID = I.keyProductID AND D.fldLocationID = I.fldLocationID;
Else
UPDATE tblInvTracking(keyProductID, fldLocationId, fldLastPhysical, fldQuantity, fldInventoryChange, fldNewQuantity)
SELECT
D.keyProductID, D.fldLocationID, D.fldLastPhysical, d.fldQuantity, i.fldLastPhysical, i.fldQuantity
FROM
DELETED D JOIN INSERTED I ON D.keyProductID = I.keyProductID AND D.fldLocationID = I.fldLocationID;
END
This was my understanding but does not work. An example on the correct way to accomplish this would be appreciated.
You need to create 2 statements, an insert for records that don't exist and an update for records that exist. As best I can tell it would be something like the following, but this won't be 100% correct, you will need to work through the logic and make sure it matches what you are trying to achieve.
NOTE: This is assuming that fldLastPhysical is the date of interest, and that its a date not a datetime.
-- WHERE THE RECORD DOESN'T EXIST FOR THE GIVEN DATE - ADD A NEW ONE
INSERT tblInvTracking(keyProductID, fldLocationId, fldLastPhysical, fldQuantity, fldInventoryChange, fldNewQuantity)
SELECT D.keyProductID, D.fldLocationID, D.fldLastPhysical, d.fldQuantity, i.fldLastPhysical, i.fldQuantity
FROM DELETED D
JOIN INSERTED I ON D.keyProductID = I.keyProductID AND D.fldLocationID = I.fldLocationID
-- ONLY ADD A NEW RECORD FOR THIS DATE IF IT DOESN"T ALREADY EXIST
WHERE NOT EXISTS (
SELECT 1
FROM tblInvTracking T
WHERE T.fldlastPhysical = D.fldLastPhysical
AND T.keyProductID = D.keyProductID AND T.fldLocationID = D.fldLocationID
);
-- WHERE THE RECORD EXISTS FOR THE GIVEN DATE - UPDATE EXISTING
UPDATE T SET
fldQuantity = T.fldQuantity + X.fldQuantity
-- It appears the following line is a datetime column, so you will need to determine what logic is required there - maybe just a straight update.
, fldInventoryChange = X.fldInventoryChange
, fldNewQuantity = T.fldNewQuantity + X.fldNewQuantity
FROM tblInvTracking T
INNER JOIN (
SELECT D.keyProductID, D.fldLocationID, D.fldLastPhysical, d.fldQuantity, i.fldLastPhysical fldInventoryChange, i.fldQuantity fldNewQuantity
FROM DELETED D
JOIN INSERTED I ON D.keyProductID = I.keyProductID AND D.fldLocationID = I.fldLocationID
-- ONLY ADD A NEW RECORD FOR THIS DATE IF IT DOESN"T ALREADY EXIST
WHERE EXISTS (
SELECT 1
FROM tblInvTracking T
WHERE T.fldlastPhysical = D.fldLastPhysical
AND T.keyProductID = D.keyProductID AND T.fldLocationID = D.fldLocationID
)
) X ON T.fldlastPhysical = X.fldLastPhysical AND T.keyProductID = X.keyProductID AND T.fldLocationID = X.fldLocationID

Why using SQL UPDATE() function along with Inserted table is not working in this after update trigger?

I have found a couple of similar questions in here that I have used to try solving this case, but nothing seems to work for me.
So, basically I have a Pallet table, and I need to change Status field when Loaded field changes to a value <> to what it was or <> to NULL, and Status is also different. The trigger is basically doing nothing. So, I have:
ALTER TRIGGER [dbo].[TR_Status_Change]
ON [dbo].[Pallet]
AFTER UPDATE
AS BEGIN
SET NOCOUNT ON;
IF UPDATE (Loaded)
BEGIN
UPDATE [Pallet]
SET PStatus = 3
FROM [Pallet] P
INNER JOIN Inserted I ON P.ID = I.ID
WHERE P.PStatus <> 3
AND P.Loaded <> I.Loaded
AND I.Loaded IS NOT NULL
END
END
Where ID is primary key. Row values are as follows:
before update: Loaded = NULL, Status = 1
after: Loaded = 'somevalue' and Status remains = 1
expected: Status = 3
Thanks in advance.
"Something compared to NULL" comparisons are always false so this fails
AND P.Loaded <> I.Loaded
Try this. You need both "before" and "after" values of Loaded too
UPDATE [Pallet]
SET PStatus = 3
FROM [Pallet] P
INNER JOIN Deleted D ON P.ID = D.ID
INNER JOIN Inserted I ON P.ID = I.ID
WHERE P.PStatus <> 3
AND NOT EXISTS (SELECT I.Loaded INTERSECT SELECT D.Loaded)
AND I.Loaded IS NOT NULL
INTERSECT internally does a "is different from" comparison, not equality. If they are different, the intersect gives no rows, so NOT EXISTS gives true
For more on INTERSECT and EXCEPT, please see my answer here Why does EXCEPT exist in T-SQL?
Here's the thing:
P.Loaded <> I.Loaded
This will never be true, because you are comparing the data of the table after the update ([Pallet P]) with....the data that of the table after the update (Inserted I).
I think you want to change Inserted I to the deleted table, which holds the values BEFORE the update operation.

SQL Server Table Triggers update an aggregate table-update/insert multiple records

(NOTE: My question is detailed and specific to our system so I will apologize for the length of the explanation before I get to my actual question in advance.)
We have a number of tables that need to have a portion of the data contained therein aggregated to another table.
I have tried, to the best of my ability, to ensure the trigger can handle multiple rows and not just single row transactions:
TRIGGER [dbo].[TR_SavedFiles_PlanLibraryMetrics] on [dbo].[SavedFiles]
AFTER INSERT, UPDATE, DELETE
AS
SET NOCOUNT ON
BEGIN
IF EXISTS (
SELECT m.AccountID, m.PlanLibCode
FROM PlanLibraryMetrics AS m
JOIN PlanLibraryItems AS it
ON m.PlanLibCode = it.PlanLibCode
JOIN inserted AS i
ON m.AccountID = i.AccountID
AND (it.ItemTypeID = i.CISFileID AND it.ItemType = 'FILE')
JOIN Accounts AS a ON i.AccountID = a.AccountID
WHERE a.Status = 0
)
BEGIN
UPDATE PlanLibraryMetrics
SET MetricValue = x.newValue
FROM (SELECT COUNT(s.AccountID) AS newValue, it.PlanLibCode, i.AccountID
FROM SavedFiles AS s
JOIN PlanLibraryItems AS it
ON (s.CISFileID = it.ItemTypeID AND it.ItemType = 'FILE')
JOIN inserted AS i
ON s.AccountID = i.AccountID
AND s.CISFileID = i.CISFileID
Group By PlanLibCode, i.AccountID) AS x
JOIN PlanLibraryMetrics AS m
ON x.PlanLibCode = m.PlanLibCode
AND x.AccountID = m.AccountID
END
ELSE IF EXISTS (
SELECT m.AccountID, m.PlanLibCode
FROM PlanLibraryMetrics AS m
JOIN PlanLibraryItems AS it
ON m.PlanLibCode = it.PlanLibCode
JOIN deleted AS d
ON m.AccountID = d.AccountID
AND (it.ItemTypeID = d.CISFileID AND it.ItemType = 'FILE')
JOIN Accounts AS a ON d.AccountID = a.AccountID
WHERE a.Status = 0
)
BEGIN
UPDATE PlanLibraryMetrics
SET MetricValue = x.newValue
FROM (SELECT COUNT(s.AccountID) AS newValue,it.PlanLibCode, d.AccountID
FROM SavedFiles AS s
RIGHT OUTER JOIN deleted AS d
ON s.AccountID = d.AccountID
AND s.CISFileID = d.CISFileID
JOIN PlanLibraryItems AS it
ON (d.CISFileID = it.ItemTypeID AND it.ItemType = 'FILE')
Group By PlanLibCode, d.AccountID) AS x
JOIN PlanLibraryMetrics AS m
ON x.PlanLibCode = m.PlanLibCode
AND x.AccountID = m.AccountID
END
ELSE IF NOT EXISTS (
SELECT m.AccountID, m.PlanLibCode
FROM PlanLibraryMetrics AS m
JOIN PlanLibraryItems AS it ON m.PlanLibCode = it.PlanLibCode
JOIN inserted AS i
ON m.AccountID = i.AccountID
AND (it.ItemTypeID = i.CISFileID AND it.ItemType = 'FILE')
JOIN Accounts AS a ON i.AccountID = a.AccountID
WHERE a.Status = 0
)
BEGIN
INSERT INTO PlanLibraryMetrics
SELECT i.AccountID, it.PlanLibCode, COUNT(s.AccountID)
FROM SavedFiles AS s
JOIN PlanLibraryItems AS it
ON (s.CISFileID = it.ItemTypeID AND it.ItemType = 'FILE')
JOIN inserted AS i
ON s.AccountID = i.AccountID
AND s.CISFileID = i.CISFileID
Group By PlanLibCode, i.AccountID
END
END
And it appears to work, until we get to a system we have in place to "merge" accounts whereby all the records from the old account(s) have their accountid's changed to the primary account.
The behavior I am trying to accomplish is to update any existing records in the aggregate table and insert new records for anything that doesn't already exist for the primary account.
So my questions: does the existence of the If exists/elseif exists clauses actually make it so this trigger (and the rest of the triggers modeled on this one) does not actually handle multiple rows? If so, I don't think my problem would be solved by writing 2 or 3 separate triggers for each table as I still have to check for an existing record every time (i.e. an update/delete trigger and a separate insert trigger)? Should I move all of this to a stored procedure and pass in, for instance, a table variable that contains all the records from the inserted/deleted tables (something I thought of cruising around the forums)?
SQL is not my strong suit and triggers even less so. Any help from the more experienced would be immensely appreciated.
Short answer - no. The use of "if exists" does not prevent the correct handling of multi-row statements.
But let's be honest. If you struggle with tsql and triggers, then you should be writing separate triggers for each action UNTIL you have confidence in your approach and logic.
Your logic appears to be overly complicated, but no one knows your schema and how your tables are used. It also appears to be at least one error. In your insert section, you check for existence based on a.Status = 0. That same logic is not included in the associated insert statement. This has been propagated to the other 2 sets of logic.
For example, assume 5 rows inserted in SavedFiles. 1 matches a row in PlanLibraryMetrics, the others don't. What does your code do? After execution, only one row in PlanLibraryMetrics will be updated - you have skipped the logic needed to aggregate/insert the other 4 rows. Your deletion logic seems highly suspect with the right join; without knowing the actual and logic keys to the various tables it is difficult to understand.
And on that note - now is a good time to start commenting your code to help others (including yourself at a later date) understand what the code should be doing and perhaps why. And yes - please consider Damien's suggestion. A view, indexed or not, might be (probably is) a better approach - and certainly a safer, more easily maintained one.

SQL Server Update trigger not working correctly

I have written an UPDATE trigger to look for changes in my Test.dbo.receiving_podio_dump table and then perform one of two different scenarios:
IF there is an existing receiving_id and existing sku_no match and this is an update to that combination in the Test.dbo.receiving_detail table, the appropriate row gets updated, OR
IF there is an existing receiving_id and but a new sku_no item, a new row needs to be added/inserted into the Test.dbo.receiving_detail table.
Here is my trigger:
CREATE TRIGGER trigger_receiving_detail_sku1_update
ON [Test].[dbo].[receiving_podio_dump]
FOR UPDATE
AS
BEGIN
--UPDATES DATA IN THE ROW FOR THAT RECEIVING LOG/SKU NO COMBO IF IT ALREADY EXISTS--
IF (SELECT ISNULL(COUNT(*), 0)
FROM inserted
LEFT JOIN dbo.receiving_detail AS rec ON (inserted.receiving_id = rec.receiving_id AND '001' = rec.sku_no)) <> 0
BEGIN
UPDATE [dbo].[receiving_detail]
SET receiving_assigned_id = inserted.receiving_assigned_id,
sku_id = sku1_id, sku = sku1, sku_lot = sku1_lot,
sku_whloc = sku1_whloc, sku_weight_of_unit = sku1_weight_of_unit,
sku_unit_of_weight_type = sku1_unit_of_weight_type,
sku_quantity_of_units = sku1_quantity_of_units,
sku_unit_of_measure = sku1_unit_of_measure,
sku_temperature = sku1_temperature
FROM inserted
WHERE ([dbo].[receiving_detail].receiving_id = inserted.receiving_id
AND [dbo].[receiving_detail].sku_no = '001')
END
--INSERTS A ROW FOR A NEW SKU NO FOR AN EXISTING RECEIVING LOG NUMBER--
IF (SELECT ISNULL(COUNT(*), 0)
FROM inserted
LEFT JOIN dbo.receiving_detail AS rec ON (inserted.receiving_id = rec.receiving_id AND '001' = rec.sku_no)) = 0
BEGIN
INSERT INTO [dbo].[receiving_detail] (receiving_id, receiving_assigned_id, sku_id, sku_no, sku, sku_lot, sku_whloc, sku_weight_of_unit, sku_unit_of_weight_type, sku_quantity_of_units, sku_unit_of_measure, sku_temperature)
SELECT
inserted.receiving_id, inserted.receiving_assigned_id, sku1_id,
'001', sku1, sku1_lot, sku1_whloc, sku1_weight_of_unit,
sku1_unit_of_weight_type, sku1_quantity_of_units,
sku1_unit_of_measure, sku1_temperature
FROM
inserted
WHERE
sku1 IS NOT NULL
END
END
GO
The first part of the trigger is working, and quantities of products are being updated on the Test.dbo.receiving_detail table if there is an update in the Test.dbo.receiving_podio_dump table.
However, if a totally new SKU is being added, a new row is not being inserted into my Test.dbo.receiving_detail table.
I am at a loss as to what to change, I have tried several things, any advice is welcome and appreciated. Thanks!
PS: the receiving log has a total of 24 SKU Items that can potentially be added or updated. I have only included the first trigger, but all of them follow this pattern.
I remove both IF statements and added those Conditions on the UPDATE and INSERT statements to support the scenario of a mass update with both conditions are true.
CREATE TRIGGER trigger_receiving_detail_sku1_update
ON [Test].[dbo].[receiving_podio_dump]
FOR UPDATE
AS
BEGIN
--UPDATES DATA IN THE ROW FOR THAT RECEIVING LOG/SKU NO COMBO IF IT ALREADY EXISTS--
UPDATE rec
SET receiving_assigned_id = i.receiving_assigned_id,
sku_id = sku1_id, sku = sku1, sku_lot = sku1_lot,
sku_whloc = sku1_whloc, sku_weight_of_unit = sku1_weight_of_unit,
sku_unit_of_weight_type = sku1_unit_of_weight_type,
sku_quantity_of_units = sku1_quantity_of_units,
sku_unit_of_measure = sku1_unit_of_measure,
sku_temperature = sku1_temperature
FROM inserted i
INNER JOIN [dbo].[receiving_detail] rec ON rec.receiving_id = i.receiving_id
WHERE (rec.sku_no = '001')
--INSERTS A ROW FOR A NEW SKU NO FOR AN EXISTING RECEIVING LOG NUMBER--
INSERT INTO [dbo].[receiving_detail] (receiving_id, receiving_assigned_id, sku_id, sku_no, sku, sku_lot, sku_whloc, sku_weight_of_unit, sku_unit_of_weight_type, sku_quantity_of_units, sku_unit_of_measure, sku_temperature)
SELECT
i.receiving_id, i.receiving_assigned_id, sku1_id,
'001', sku1, sku1_lot, sku1_whloc, sku1_weight_of_unit,
sku1_unit_of_weight_type, sku1_quantity_of_units,
sku1_unit_of_measure, sku1_temperature
FROM
inserted i
WHERE sku1 IS NOT NULL
AND NOT EXISTS (SELECT * FROM [dbo].[receiving_detail] rec WHERE rec.receiving_id = i.receiving_id AND rec.sku_no = '001')
END
GO

UPDATE row from query with multiple matches

Given an update statement like so:
UPDATE
UserAssesment
SET
AssessmentDate = comp.AssessmentDate
FROM
UserAssesment ua
INNER JOIN
vw_CompletedAssessments comp
On
ua.NatId = comp.NatId and
ua.FamilyName = comp.ClientLastName and
ua.GivenName = comp.ClientFirstName
WHERE
ua.HasCompletedAssessment <> 0
if a user can have multiple records that match the join clause to vw_CompletedAssessments, which record will be used for the update? Is there a way to order it so the max or min AssessmentDate is used?
Your Syntax for UPDATE needs some tweaking see below:
UPDATE ua
SET
ua.AssessmentDate = comp.AssessmentDate
FROM UserAssesment ua
INNER JOIN vw_CompletedAssessments comp
ON ua.NatId = comp.NatId and
ua.FamilyName = comp.ClientLastName and
ua.GivenName = comp.ClientFirstName
WHERE ua.HasCompletedAssessment <> 0
Now coming to the point if you have multiple values and you want to Pick a particular value from Comp table for that you can make use of ROW_NUMBER functions something like this...
UPDATE ua
SET
ua.AssessmentDate = comp.AssessmentDate
FROM UserAssesment ua
INNER JOIN (SELECT *
, ROW_NUMBER() OVER (PARTITION BY NatId ORDER BY AssessmentDate DESC) rn
FROM vw_CompletedAssessments) comp
ON ua.NatId = comp.NatId
and ua.FamilyName = comp.ClientLastName
and ua.GivenName = comp.ClientFirstName
WHERE ua.HasCompletedAssessment <> 0
AND Comp.rn = 1
This query will update the ua.AssessmentDate to the latest comp.AssessmentDate for a particular NatId. Similarly you can see how you can manipulate the results using row number. If you want to update it to the oldest comp.AssessmentDate value just change the order by clause in row_number() function to ASC and so on....
If more than one match is found, then all will be updated.
If you want only one to be updated, can you use UPDATE TOP (1)?
If you want to guarantee the order of the one to be updated, try to add an appropriate ORDER BY clause.

Resources