I made a trigger that is supposed to update another value in the same table a after I make an insert to the table. I do get the result I am looking for, but when I ask my teacher if it is correct he responded that this trigger updates "all" tables(?) and thus incorrect. He would not explain more than that (he is that kind of teacher...). Can anyone understand what he means? Not looking for the right code, just an explanation of what I might have misunderstood.
CREATE TRIGGER setDate
ON Loans
AFTER INSERT
AS
BEGIN
UPDATE Loans
set date = GETDATE()
END;
Your teacher intends to say that the query updates all rows in the table -- perhaps you misunderstood her or him.
The best way to do what you want is to use a default value:
alter table loans alter column date datetime default getdate();
That is, a trigger is not needed. If you did use a trigger, I'll give you two hints:
An instead of trigger.
inserted should be somewhere in the trigger.
Hello there. I upvoted your question because that might be the question of many beginners in SQL Server.
As I see you defined the trigger right! It is a correct way although it's not the best.
By the way we are not discussing all ways you could choose or not, I'm gonna correct your code and explain what your teacher meant.
Look at this UPDATE you wrote:
UPDATE Loans
SET date = GETDATE()
If you write a SELECT without a WHERE clause, what does it do? It would result all the rows in the table which was selected. right?
SELECT * FROM dbo.loans
So without a WHERE clause, your UPDATE will update all of the rows in the table.
OK now what to do to just update the only row ( or rows) which were recently inserted?
When you are writing a trigger, you are allowed to use this table: Inserted
which it has the rows were recently inserted. kinda these rows first come to this table and then go to the final destination table. So you can update them before they go the dbo.loans
it would be like this:
Update dbo.loans
SET date = GETDATE()
WHERE Id IN (SELECT * FROM Inserted)
Related
Please assist if I should use a trigger or procedure. I am trying to update the ScaleRating in table GSelfAssessment from GRatingScale if the Score in GSelfAssessment falls between the minimum and maximum score in GRatingScale.
GSelfAssessment table
GRatingScale Table
Preferably this should be achieved for each row on either update or insert. I believe SQL trigger is the most appropriate one. I understand the inserted/deleted concept inside a trigger after my research. E.g.
CREATE TRIGGER [dbo].[TR_GSelfAssessment_update] ON [dbo].[GSelfAssessment]
FOR UPDATE
AS
BEGIN
UPDATE GSelfAssessment
SET GSelfAssessment.ScaleRating= (Select )---this is where i have a problem-----
END
I believe there is Guru out here who can give me solution to this. I will learn a lot.
SQL Server supports computed columns. If you want ScaleRating to always be aligned with the rest of the data, then that is the best approach:
alter table GSelfAssessment
add ScaleRating as ( . . . );
This adds a new "column" that gets calculated when the value is used in a query. If the computation is expensive or you want to build an index, then use persisted so the value is actually stored with the rest of the data -- and recalculated when needed.
You can add the computed column in the create table statement as well. If you have already created the table, you can drop the column and re-add it or modify it.
You should not have that column. Join to the rating table when you need to. You can create a view if it makes it easier.
select …
from GSelfAssessment a
inner join
GRatingScale r
on (a.Score>r.MinScore and a.Score<=r.MaxScore)
Adjust/create view as required
I need to create a trigger that will keep track of the number of times a movie is rented from a business like Blockbuster. I need a separate trigger for an insert, a delete and an update.
The column that tracks this number of times rented is called num_rentals and has a datatype of int. This column is part of the Movies table which has *movie_id (pk*), movie_title, release_year, movie_description, and num_rentals. The customer_rentals table has item_rental_id(pk), *movie_id(fk*), customer_id.
I searched and found a similar thread here for this and tried using the supplied answer, but to no avail. I executed the following string without any errors but I saw no change in the num_rentals column when I inserted data into either the Movie or the Customer_rentals table. What am I doing wrong?
CREATE TRIGGER dbo.tr_num_rented_insert ON dbo.customer_rentals
FOR INSERT
AS
BEGIN
UPDATE m
SET num_rentals = num_rentals + 1
FROM dbo.movies AS m
INNER JOIN inserted AS i ON m.movie_id = i.movie_id ;
END
I added the num_rentals field to the table later and I also need to know how to initialize the field value to zero for all records currently in the Movies table.
I want to understand this as much as I want the answer so assistance is greatly appreciated. I read that there may be more efficient ways to manage this type of data, but this is the way my instructor wants it. Thank you in advance!
I suspect your problem is null num_rental column. So you're adding null to 1, resulting in null.
Personally, I'd set the num_rentals column to non-nullable with a default of zero, but assuming you can't do that, use isnull().
Like so
alter TRIGGER dbo.tr_num_rented_insert ON dbo.customer_rentals
FOR INSERT
AS
BEGIN
UPDATE m
SET num_rentals = isnull(num_rentals,0) + 1
FROM dbo.movies AS m
INNER JOIN inserted AS i ON m.movie_id = i.movie_id ;
END
However, even though this works, there is a problem with it; if you add multiple rows to customer_rentals, it's only going to increment by one. Personally, I'd change the trigger to account for that.
Like this
alter TRIGGER dbo.tr_num_rented_insert ON dbo.customer_rentals
FOR INSERT
AS
BEGIN
UPDATE m
SET num_rentals = isnull(num_rentals,0) + (select COUNT(*) from inserted where movie_id = m.movie_id)
FROM dbo.movies AS m
where movie_id in (select movie_id from inserted)
END
As far as the duplicate information goes, Aaron is right that this is needlessly redundant and in my experience, this type of thing often gets out of sync. With such a simple database, the num_rentals column is overkill (to be generous), but your movie database is a contrived example to teach you a concept. Basically, sometimes you will want calculated values to be easily accessible or filtered on. Take SO rep for example, I assume they don't recalc that every time they display it.
Aaron's frequently upvoted comment suggests that you are doing something unwise. However, since you are doing what your instructor is specifying, I suggest that you look at a couple of things.
First, will your update query work for the initial rental? If the initial value is 0 it will. If the initial value is null it won't.
Second, you might be referencing the wrong table. The trigger is on customer_rentals but your trigger refers to inserted. That strikes me as odd.
Just so you know, I was one of those who upvoted Aaron's comment.
I need to write a trigger that only runs when an insertion that has a certain field with a specific value is inserted. There is no updating.
For example, if I insert an entry that has column X with the value "MAC ID", I want it to run a trigger, but ONLY if the value is "MAC ID".
I know how to test for this normally, but not sure how to test this in a trigger. So far I've found that I want to use "FOR INSERT", but beyond that I don't know how to implement it.
Any help is much appreciated! Thanks!
create trigger MaxIdInsert
on YourTable
after insert
as
if exists
(
select *
from inserted
where ColumnX = 'MAX ID'
)
begin
-- do what you want here if ColumnX has value of 'MAX ID'
end
go
There's no way to only fire a trigger on certain DML specifications (besides insert, update, and/or delete). Your best bet is to test out the dynamic inserted table, which contains records that are inserted into YourTable. In this, you can test for inserted records that have a ColumnX value of "MAX ID".
Edit: In case you were wondering, I know you specified a for trigger in your question. A for trigger is equivalent to an after trigger in SQL Server.
You need to be aware that triggers run once for a batch, not once per row. As such, you may be running in a circumstance in which some of the rows match your criteria, and others do not.
As such, you'd do best to write your trigger logic to select the matching rows from inserted. If there were no matching rows, the rest of your logic will be working with an empty set, but that's no real problem - SQL Server is plenty fast enough at doing nothing, when required.
E.g. if you're inserting these rows into an audit table, something like:
create trigger MacId
on T
for insert
as
insert into Audit(Col1,Col2,Col3)
select i.Col1,i.Col2,'inserted'
from inserted i
where i.Col4 = 'MAC ID'
I am using the following to list the row count for all of my tables:
select convert(varchar(30),object_name(id)) [Table Name], rows, ModifiedOn from sysindexes
where object_name(id) not like 'sys%' and indid = 1
order by object_name(id)
I confess that I found this somewhere and only have a conceptual idea of what it is doing. But for my purposes, where I want to perform an application action and reverse engineer what happened in the database, it works well to identify new rows (I copy and paste before/after results into excel to compare).
Now, I would also like to know which tables have been updated. On (almost) all of my tables there is a ModifiedOn column, so I am hoping I can add the max of this to my output, which will tell me when the table's contents were last updated.
I have no idea how to join these two, and any help is appreciated.
I would strongly suggest against this approach, as it is DB dependent and unreliable.
Creating an ON INSERT on ON UPDATE trigger it the correct solution, then in the trigger you can put the new or updated data into a separate table which you can query. Triggers are the tool to monitor changes in the database without doing anything in the applications using them.
Example trigger for 'after update' on table MY_TABLE(id, name):
CREATE TRIGGER mark_changes AFTER UPDATE ON my_table FOR EACH ROW
BEGIN
INSERT INTO tracking_table VALUES "Change in table my_table", OLD.id, NEW.id
END$$
This assumes you have a table tracking_table(description, old_id, new_id)
Imagine there is a Price column in Products table, and the price may change.
I'm fine with it changing but I want to store the original Price value in another column.
Is there any automatic way MS SQL server may do this?
Can I do this using Default Value field?
Do I have to declare a trigger?
Update
I tried to use Price to simplify the question but it looks like this provoked "use separate table" type of answers.
I'm sorry for the confusion I caused.
In the real world, I need to store a foreign key ID and I'm 100% I only need current and original values.
Update 2
I got a little confused by the different approaches suggested so please let me explain the situation again.
Imaginary Products table has three fields: ID, Price and OriginalPrice.
I want to setOriginalPrice to Price value on any insert.
Sometimes it is a single product that gets created from code. Sometimes there are thousands of products created by a single insert from a stored procedure so I want to handle these properly as well.
Once OriginalPrice has been set, I never intend to update it.
Hope my question is clearer now.
Thanks for your effort.
Final Update
I want to thank everyone, particularly #gbn, for their help.
Although I posted my own answer, it is largely based on #gbn's answer and his further suggestions. His answer is also more complete, therefore I mark it as correct.
After your update, let's assume you have only old and new values.
Let's ignore if the same update happens in quick succession because of a client-code bug and that you aren't interested in history (other answers)
You can use a trigger or a stored procedure.
Personally, I'd use a stored proc to provide a basic bit of control. And then no direct UPDATE permissions are needed, which means you have read only unless via your code.
CREATE PROC etc
...
UPDATE
MyTable
SET
OldPrice = Price,
Price = #NewPrice,
UpdatedBy = (variable or default)
UpdatedWhen = DEFAULT --you have a DEFAULT right?
WHERE
PKCol = #SomeID
AND --provide some modicum of logic to trap useless updates
Price <> #NewPrice;
A trigger would be similar but you need to have a JOIN with the INSERTED and DELETED tables
What if someone updates OldPrice directly?
UPDATE
T
SET
OldPrice = D.Price
FROM
Mytable T
JOIN
INSERTED I ON T.PKCol = I.PKCol
JOIN
DELETED D ON T.PKCol = D.PKCol
WHERE
T.Price <> I.Price;
Now do you see why you got jumped on...?
After question edit, for INSERT only
UPDATE
T
SET
OriginalPrice = I.Price
FROM
Mytable T
JOIN
INSERTED I ON T.PKCol = I.PKCol
But if all INSERTs happen via stored procedure I'd set it there though....
There is no readonly attribute for a SQL Server table column. BUT you could implement the functionality you describe using a trigger (and restricting permissions)
Except, it is not the best way to solve the problem. Instead treat the price as Type 2 'slowly changing dimension'. This involves having a 'ValidTo' column (os 'StartDate' and 'EndDate' columns), and closing off a record:
Supplier_Key Supplier_Code Supplier_Name Supplier_State Start_Date End_Date
123 ABC Acme Supply Co CA 01-Jan-2000 21-Dec-2004
124 ABC Acme Supply Co IL 22-Dec-2004
If you do go the route of a trigger (I suggest you use SCD type 2), make sure it can handle multiple rows: Multirow Considerations for DML Triggers
I would recommend storing your price in a seperate table called Prices, with the columns Price and Date.
Then whenever the price is updated, INSERT a new record into the Prices table. Then when you need to know the current price, you can pull from there.
However, if you wish to update an OriginalPrice column automatically, you could add a TRIGGER to the table to do this:
http://msdn.microsoft.com/en-us/library/aa258254%28v=sql.80%29.aspx
This is what I ended up with, with a heavy help from #gbn, #Mitch and #Curt:
create trigger TRG_Products_Price_I --common-ish naming style
on dbo.Products after insert as
begin
set nocount on
update m
set OriginalPrice = i.Price
from Products p
join inserted i on p.ID = i.ID
end
I tried to follow this article as well.
Thanks to everyone!