I am faced with the following situation:
I created a trigger which reacts on insert to the third table. When I insert any data (for example 1 1 2), last number should be subtracted from the Amount of stock column from cell, which has necessary ID Product (as it's shown on picture). But how can I understand which row was the last added? I thought firstly to do it by select, but it seems impossible. And now I think that it's possible to do it with the help of cursor, but it doesn't seem as the best variant. Is there a better variant how can I do it?
Here's my code of trigger, but it only subtracts 1 from the 1st product each time, unfortunately:
CREATE TRIGGER AmountInsert ON Amount
AFTER INSERT
AS
BEGIN
UPDATE Product
SET Amount_On_Stock =
(SELECT Amount_On_Stock FROM Product
WHERE ID_Product = 1) - 1
WHERE ID_Product = 1
END
The first thing you need to understand is that in a trigger in SQL Server you are provided with an inserted pseudo-table and a deleted pseudo-table. You use these tables to determine what changes have occurred.
I think the following trigger accomplishes what you are looking for - the comments explain the logic.
CREATE TRIGGER dbo.AmountInsert ON dbo.Amount
AFTER INSERT
AS
BEGIN
set nocount on;
update P set
-- Adjust the stock level by the amount of the latest insert
Amount_On_Stock = coalesce(Amount_On_Stock) - I.Amount
from dbo.Product P
inner join (
-- We need to group by ID_Product in case the same product appears in the insert multiple times
select ID_Product, sum(Amount) Amount
from Inserted
group by ID_Product
-- No need to update is net change is zero
having sum(Amount) <> 0
) I
on I.ID_Product = P.ID_Product;
END
Related
I have two tables:
[1] Donations - with amount and pet_id field
[2] Pets - with id and total donations field
I'm trying to create a trigger which will update total donations field whenever
a new row is being inserted to Donations table. I tried this one:
create trigger update_donations
on sponserships
for insert
as
update dbo.Pets
set tot_donations = (
select new_val = inserted.amount + pets.tot_donations
from inserted
where inserted.[Pet-ID] = pets.[Animal-ID]
)
But of course it changes all records, whereas i want to change only records that are changed in the donations table.
It is usually not a good practice to store this type of derived information - you could have a view that computes it on the fly rather than a trigger that keeps it up to date. Also please note that if you go that way, you also need a delete and an update trigger...
That said, you can use the following query in your insert trigger to update the relevant record in the pets table:
update p
set p.total_donations = p.total_donations + i.amount
from dbo.Pets p
inner join inserted i on i.[Pet-ID] = p.[Animal-ID]
I have a SQL Server table with student, cohort, program, grad date, and status. Some students have records that are specifically tied together – if the cohort for one row changes, the cohort for another row with status 2 needs to change as well. In most cases I catch this programmatically but there are a LOT of ways to change a student’s cohort so we’re missing some.
Essentially if someone runs the first line update statement, the second also needs to run
UPDATE StudentPrograms
SET Cohort = 201610
WHERE Student = 'A1234' AND Program = 'MBA' AND Cohort = 201510
UPDATE StudentPrograms
SET Cohort = 201610
WHERE Student = 'A1234' AND Cohort = 201510 AND Status = 2
I would like to put an update trigger on the table to change both cohorts, but the problem is I can’t figure out how to capture both the starting cohort that identifies the record that needs to change and the cohort that it would need to change to. Once the update has run and the trigger fires, all I have is the updated cohort, so I don’t know which record to change.
CREATE TRIGGER [dbo].[tr_SP_Cohort]
ON [dbo].StudentPrograms
AFTER UPDATE
AS
DECLARE #student VARCHAR(10);
DECLARE #cohort INT;
SELECT #student = i.student FROM inserted i
SELECT #cohort = i.cohort FROM inserted i
SET NOCOUNT ON;
IF UPDATE(Cohort)
UPDATE dbo.StudentPrograms
SET Cohort = #cohort
WHERE Student = #student AND Status = 2 AND Cohort = ???
Unfortunately the records do have to be in this table and not a sub table for a lot of reasons. The records are occasionally also deleted / reinserted, so I run into the same problems if I tie them together by adding another field with a key - if that record gets deleted / updated / reinserted, I still need to capture the information, record it, then record the change.
Thanks for the immediate help! Here's what I ended up with, changed a bit so it works with batch updates.
ALTER TRIGGER [dbo].[tr_SP_Cohort]
ON [dbo].StudentPrograms
AFTER UPDATE
AS
SET NOCOUNT ON;
IF UPDATE(Cohort)
UPDATE a
SET Cohort = i.Cohort
FROM dbo.StudentPrograms AS a
INNER JOIN inserted AS i ON a.Student = i.Student
INNER JOIN deleted AS d ON a.Cohort = d.Cohort
AND a.Student = d.Student
WHERE a.Status = 2
When creating a trigger, you can access the old values using the deleted table
WHERE Student = #student AND Status = 2 AND Cohort = (SELECT TOP 1 Cohort from deleted)
Ideally, you should handle this in a stored procedure but as a temporary fix you can use the following code.
They way you are handling updates in your code, it will never work if more than one row was updated.
Triggers are fired once in response to a triggering action (update,delete,insert) for all the rows affected by that action, NOT for each row affected by the triggering action.
Keeping all this in mind your temporary solution should look something like...
CREATE TRIGGER [dbo].[tr_SP_Cohort]
ON [dbo].StudentPrograms
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE s
SET s.Cohort = i.Cohort
FROM deleted d
INNER JOIN dbo.StudentPrograms s ON d.Student = s.Student
AND d.Cohort = s.Cohort
INNER JOIN inserted i ON i.Student = s.Student
WHERE s.[Status] = 2;
END
First time creating a Database trigger. I have a child table that when its cost column is updated, I need its parent table to also update its cost column to reflect the change.
Here is my sorry attempt so far. That's obviously not working. I am having a problem figuring out how to extract the total cost as a variable and store it in the parent table.
My current approach assumes a static id vaule at the moment. I am not entirely sure how to dynamically determine the id value of the row that was updated.
CREATE TRIGGER ParentCost_Update
ON ChildTable
AFTER INSERT, UPDATE
AS
SELECT SUM(Cost) AS TotalCost FROM ChildTable where parent_id=2080
UPDATE ParentTable
SET Cost=TotalCost
where id=parent_id;
GO
This current script is returning an error
Msg 207, Level 16, State 1, Procedure ParentCost_Update, Line 9
Invalid column name 'TotalCost'.
You need to be careful in triggers, in that there could be more than one row updated. Therefore, you need to do row-based handling.
To obtain the newly inserted / updated row, use the inserted and deleted pseudo rows.
You will almost certainly also going to need to implement a deleted trigger as well, viz, if a row is removed from a child table that the parent will need to be recalculated.
Here's a row based take, using a CTE to map your two-step process above , as follows:
CREATE TRIGGER ParentCost_Update
ON ChildTable
AFTER INSERT, UPDATE, DELETE
AS
SET NOCOUNT ON;
WITH cteParentsAffected AS
(
SELECT ins.parent_id
FROM inserted ins
UNION
SELECT del.parent_id
FROM deleted del
)
, cteTotal AS
(
SELECT ct.parent_id, SUM(ct.Cost) AS TotalCost
FROM ChildTable ct
INNER JOIN cteParentsAffected par
ON ct.parent_id = par.parent_id
GROUP BY ct.parent_id
)
UPDATE pt
SET Cost=cte.TotalCost
FROM ParentTable pt
INNER JOIN cteTotal cte
ON id=cte.parent_id;
GO
With a SqlFiddle here
Try this
Update parenttable set total= (select sum(total) from childtable c where c.id= parent table.id)
Where id in (select id from inserted)
Change the table and column names.
struggling with this one, quite a lengthy description so ill explain best I can:
I have a table with 12 columns in, 1 being a primary key with identity_insert, 1 a foreign key , the other 10 being cost values, ive created a statement to group them into 5 categories, shown below:
select
(ProductID)ProjectID,
sum(Cost1)Catagory1,
sum(Cost2)Catagory2,
sum(Cost3 + Cost4 + Cost5 + Cost6 + Cost7) Catagory3,
sum(Cost 8 + Cost 9)Catagory4,
sum(Cost10)Catagory5
from ProductTable group by ProductID
ive changed the names of the data to make it more generic, they aren't actually called Cost1 etc by the way ;)
the foreign key can appear multiple times (ProductID) so in the above query the related fields are calculated together based upon this... Now what ive been trying to do is put this query into a table, which i have done successfully, and then update the data via a procedure. the problem im having is that all the data in the table is overwritten by row 1 and when theres is thousands of rows this is a problem.
I have also tried putting the above query into a view and the same result... any suggestions would be great :)
update NewTable set
ProductID = (ProductView.ProductID ),
Catagory1 = (ProductView.Catagory1 ),
Catagory2 = (ProductView.Catagory2 ),
Catagory3 = (ProductView.Catagory3 ),
Catagory4 = (ProductView.Catagory4 ),
Catagory5 = (ProductView.Catagory5 )
from ProductView
I need something along the lines like above.... but one that doesn't overwrite everything with row 1 haha ;)
ANSWERED BY: Noman_1
create procedure NewProducts
insert into NewTable
select ProductID.ProductTable,
Catagory1.ProductView,
Catagory2.ProductView,
Catagory3.ProductView,
Catagory4.ProductView,
Catagory5.ProductView
from ProductView
inner join ProductTable on ProductView.ProductID = ProductTable.ProductID
where not exists(select 1 from NewTable where ProductView.ProductID = NewTable.ProductID)
above procedure locates the new Product that has been created within a view, the procedure query detects that there is a Product that is not located in the NewTable and inserts it via the procedure
As far as i know, and since you want to update all the products in the table, and each product uses all the sums of the product itself from origin, you actually need to update each row 1 by 1, and as consecuence when you do an update like the next, its your only main way
update newtable
set category1 = (select sum(cost1) from productTable where productTable.productId = newtable.ProductID),
category2 = (select sum(cost2) from productTable where productTable.productId = newtable.ProductID),
etc..
Keep in mind that if you have new products, they wont get inserted with the update, you would need like this in order to add them:
Insert into newtable
Select VALUES from productTable a where productId not exists(select 1 from newTable b where a.ProductId = b.ProductId);
A second way, and since you want allways to update all the data, is to simply truncate and do a insert select right after.
Maybe on an Oracle, you would be albe to use a MERGE but im unaware if it would really improve anything.
I asume that simply having a view would not work due the amount of data you state you have.
EDIT, I never knew that the MERGE STATMENT is actually avaiable on SQL Server 2008 and above, with this single statment you could do an UPDATE/INSERT on all but it's efficiency is unknown to me, you may want to test it with your high amount of data:
MERGE newtable AS TARGET
USING select ProductId, sum(cost1) cat1, sum(cost2) cat2 ...
FROM productTable Group by ProductId AS SOURCE
ON TARGET.ProductId = SOURCE.ProductID
WHEN MATCHED
THEN UPDATE SET TARGET.category1 = cat1, TARGET.category2 = cat2...
WHEN NOT MATCHED
THEN INSERT (ProductId, category1, category2,...)
VALUES (SOURCe.ProductId, SOURCE.cat1, SOURCE.cat2...);
More info about merge here:
http://msdn.microsoft.com/library/bb510625.aspx
The example at the end may give you a good overview of the sintax
You haven't given any join condition. SQL Server cannot know that you meant to update rows matched by productid.
update NewTable set
ProductID = (ProductView.ProductID ),
Catagory1 = (ProductView.Catagory1 ),
Catagory2 = (ProductView.Catagory2 ),
Catagory3 = (ProductView.Catagory3 ),
Catagory4 = (ProductView.Catagory4 ),
Catagory5 = (ProductView.Catagory5 )
from NewTable
join ProductView pv on NewTable.productid = pv.productid
You don't need a view. Just past the view query to the place where I said ProductView. Of course, you can use a view.
Background:
I have a set of products for each numbered week starting at 1.
I have a product "library" which is week zero holding the last saved product from any time period.
I have a trigger that fires upon update or insert which keeps the "library" item up to date from the inserted items.
Since there can be duplicate products in the same event using a "sequence" field, my join will create multiple records to update from, which target the same library record.
Some questions:
Will these multiples fail the update command?
Is there a better way to update the single library product?
Code:
-- PK is ID, Week #, and Sequence #
update p set p.name = i.name
from product p join inserted i on
p.id = i.id and p.week = 0 and p.sequence = 1
Note: "inserted" can have multiple events. ID is like a UPC, Week is an identity, and Sequence is like an identity, but starts at 1 for each week. You can have a sequence of 2 while not having a sequence of 1 because they can delete products.
Sample Data:
ID WeekSequence Name
12345 1 3 Lego inserted first
12345 2 2 Lego Toy inserted second
12345 2 3 Lego Toy inserted second
Result data:
ID WeekSequence Name
12345 0 1 Lego Toy Was "Lego" now is "Lego Toy"
According to this BOL entry:
Use caution when specifying the FROM
clause to provide the criteria for the
update operation. The results of an
UPDATE statement are undefined if the
statement includes a FROM clause that
is not specified in such a way that
only one value is available for each
column occurrence that is updated,
that is, if the UPDATE statement is
not deterministic.
In other words, if an UPDATE statement uses a FROM clause that has multiple rows meeting the criteria to update a single row, it is unknown which row of new data will be used. In your sample data, it is unknown whether the result was updated from the name in the row with Sequence=2 or Sequence=3.
So, if it doesn't matter which row is used for the update, what you're currently doing will work just fine. If this is a problem however, you need to write your update's FROM and WHERE clauses so that only one row is returned for each item, possibly something like the following:
;with insert2 as (
select id, week, sequence, name,
row_number() over(partition by id order by week desc, sequence desc) as [descOrd]
from inserted
)
update p
set p.name = i.name
from product p
join insert2 i on p.id = i.id and p.week = 0 and p.sequence = 1
where i.descOrd=1