Simple Update trigger + simple row insert - sql-server-2008

Total novice at triggers... all the docs don't bother with beginner stuff.
I just want to update the row(s) that has/have been updated. My trigger below updates the entire table. The trigger below just tests two columns for changes.
How do I restrict this update trigger to the update only the updated rows and not the entire table?
ALTER TRIGGER [dbo].[geog_update] ON [dbo].[Site]
FOR UPDATE
AS
SET NOCOUNT ON
IF (UPDATE(Latitude) OR UPDATE(Longitude))
BEGIN
UPDATE Site
SET geog = geography::Point([Latitude], [Longitude], 4326)
WHERE Latitude is not null and Longitude is not null
END
Can I use the same trigger for inserted rows by just using FOR UPDATE, INSERT? Probably not since the IF checks for UPDATE() on a column, unless INSERT implies UPDATE.

Ok first, you never under any circumstances design a trigger to update only one row in SQL Server. You design it to update the rows that were inserted, deleted or updated. Triggers operate on batches and you cannot assume that you will never change more than one record in code that hits the database.
You do that by joining to one of two pseudotables that are available only in triggers, inserted or deleted. Inserted contains the new records or the values after an update, delted contains the values for the records deleted or the values before an update occurred.
You could try something like:
ALTER TRIGGER [dbo].[geog_update] ON [dbo].[Site]
FOR UPDATE
AS
SET NOCOUNT ON
UPDATE S
SET geog = geography::Point(i.[Latitude], i.[Longitude], 4326)
FROM Site s
JOIN Inserted I on s.id = i.id
WHERE Latitude is not null and Longitude is not null

Here is the final UPDATE trigger. I made an INSERT trigger that is almost identical.
CREATE TRIGGER [dbo].[geog_update] ON [dbo].[Site]
FOR UPDATE
AS
SET NOCOUNT ON
IF (UPDATE(Latitude) OR UPDATE(Longitude))
BEGIN
UPDATE t1
SET t1.geog = geography::Point(t1.Latitude, t1.Longitude, 4326)
FROM Site AS t1 INNER JOIN Inserted AS t2 ON t1.Site_ID = t2.Site_ID
WHERE (t1.Latitude IS NOT NULL)
AND (t1.Longitude IS NOT NULL)
AND (t1.Record_Archive_Date IS NULL)
END

Related

Triggers are calculating the wrong sum, giving unexpected results

I have a weird issue with my Trigger. There are 2 tables: Table A and Table B.
Whenever a row is inserted to Table A sum of a column in this table is inserted into Table B
It was working fine at first, but recently I noticed when >1 rows are inserted at the exact time for a user, the trigger returns sum in a weird way.
CREATE TRIGGER `update_something` AFTER INSERT ON `Table_A`
FOR EACH ROW BEGIN
DECLARE sum BIGINT(20);
SELECT IFNULL(SUM(number), 0) INTO sum FROM Table_A WHERE `user` = NEW.user;
UPDATE Table_B SET sum_number = sum WHERE id = NEW.id;
END
Example:
Table A
User X has a sum of 15 currently, then (with almost no delay in between):
Number 5 is inserted for him
Number 7 is inserted for him
Table B
On this table where we hold the sum, sum for this user was 15
Trigger updates this table in this way:
20
22 <--- Wrong, this should be 27
As you can see there isn't any number 2 inserted, it adds 7-5 = 2 for some reason.
How is that possible and why does it subtract 5 from 7 and add 2 to the sum instead of normally adding 7?
Edit 1:
Warning: This won't work, check the accepted answer instead
One of answers suggested select for update method.
Will this SELECT ... FOR UPDATE affect the performance negatively in a huge way?
CREATE TRIGGER `update_something` AFTER INSERT ON `Table_A`
FOR EACH ROW BEGIN
DECLARE sum BIGINT(20);
SELECT IFNULL(SUM(number), 0) INTO sum FROM Table_A WHERE `user` = NEW.user FOR UPDATE;
UPDATE Table_B SET sum_number = sum WHERE id = NEW.id;
END
Basically we only add FOR UPDATE to the end of SELECT line like this and it will perform Row Lock in InnoDB to fix the issue?
SELECT IFNULL(SUM(number), 0) INTO sum FROM Table_A WHERE user = NEW.user FOR UPDATE;
Edit 2 (Temporary Fix):
In case some one needs a very quick temporary fix for this before doing the actual & logical suggested fix: What I did was to put a random usleep(rand(1,500000)) before INSERT query in PHP to reduce the chance of simultaneous inserts.
The reason for this behaviour is that the inserted data is only committed to the database when the trigger finishes executing. So when both insert operations (5 and 7) execute the trigger in parallel, they read the data as it is in their transaction, i.e. the committed data with the changes made in their own transaction, but not the changes made in any other ongoing transaction.
The committed data in table A sums up to 20 for both transactions, and to that is added the record that is inserted in their own transaction. For the one this is 5, for the other it is 7, but as these records were not yet committed, the other transaction does not see this value.
That is why the sum is 20+5 for the one, and 20+7 for the other. The transactions then both update table B, one after the other (because table B will be locked during an update and until the end of the transaction), and the one that is latest "wins".
To solve this, don't read the sum from table A, but keep a running sum in Table B:
CREATE TRIGGER `update_something` AFTER INSERT ON `Table_A`
FOR EACH ROW BEGIN
UPDATE Table_B SET sum_number = sum_number + NEW.number WHERE id = NEW.id;
END;
/
I suppose you already have triggers for delete and update on Table_B, as otherwise you'd have another source of inconsistencies.
So these need to be (re)written too:
CREATE TRIGGER `delete_something` AFTER DELETE ON `Table_A`
FOR EACH ROW BEGIN
UPDATE Table_B SET sum_number = sum_number - OLD.number WHERE id = OLD.id;
END;
/
CREATE TRIGGER `update_something` AFTER UPDATE ON `Table_A`
FOR EACH ROW BEGIN
UPDATE Table_B SET sum_number = sum_number - OLD.number WHERE id = OLD.id;
UPDATE Table_B SET sum_number = sum_number + NEW.number WHERE id = NEW.id;
END;
/
This way you prevent to lock potentially many rows in your triggers.
Then, after you have done the above, you can fix the issues from the past, and do a one-shot update:
update Table_B
join (select id, user, ifnull(sum(number),0) sum_number
from Table_A
group by id, user) A
on Table_B.id = A.id
and Table_B.sum_number <> A.sum_number
set Table_B.sum_number = A.sum_number;
You get this because of the race condition in the trigger. Both triggeres are fired at the same time, thus SELECT returns the same value for both of them - 15. Then first trigger updates tha value adding 5 and resulting in 20, and then the second update is run with 15 + 7 = 22.
What you should do is use SELECT ... FOR UPDATE instead. This way if first trigger issues the select, then the second one will have to wait until first one finishes.
EDIT:
Your question made me think, and maybe using FOR UPDATE is not the best solution. According to documentation:
For index records the search encounters, SELECT ... FOR UPDATE locks the rows and any associated index entries, the same as if you issued an UPDATE statement for those rows.
And because you are selecting the sum of entries from Table A it will lock those entries, but will still allow inserting new ones, so the problem would not be solved.
It would be better to operate only on data from Table B inside the trigger, as suggested by trincot.

PL SQL Trigger to update start time for row when a single column is updated

I'm fairly new to triggers and have already tried searching for a solution to my question with little results. I want to update a single row's start time column whenever it's active column is set to 1.
I have two columns ACTIVE (number) and START_TIME (timestamp) in my_table. I would like to create a PL/SQL trigger that updates the START_TIME column to current_timestamp whenever an update statement has been applied to the ACTIVE column - setting it to 1.
So far I have only seen examples for inserting new rows or updating entire tables which isn't what I'm looking to do. I'd have thought there would be a fairly simple solution to my problem.
This is what I've got so far from other examples. I know the structure of my solution is poor and I'm asking for any input to modify my trigger to achieve my desired result.
CREATE OR REPLACE TRIGGER routine_active
AFTER UPDATE ON my_table
FOR EACH ROW
WHEN (my_table.ACTIVE = 1)
begin
insert my_table.start_time = current_timestamp;
end;
\
you can use like this .it may help you
write the update query instead of insert query
CREATE OR REPLACE TRIGGER routine_active
AFTER UPDATE ON my_table
FOR EACH ROW
WHEN (new.ACTIVE = 1)
begin
update my_table set start_time =current_timestamp;
end;
I think it should be a BEFORE UPDATE, not AFTER UPDATE, so it saves both changes with a single action. Then you don't need the INSERT or UPDATE statements. I also added the "OF active" clause, so it will only start this trigger if that column was updated, which may reduce the workload if other columns get updated.
CREATE OR REPLACE TRIGGER routine_active
BEFORE UPDATE OF active ON my_table
FOR EACH ROW
BEGIN
IF active = 1
THEN
:NEW.start_time = current_timestamp;
END IF;
END;

How can I get an after update trigger to work when it is being fired from an after insert trigger in mysql?

Hello, every one :)!
I'll try and keep this as simple as possible, basically, I have one table that references itself via a parent_id column. Each row in the table can have a parent and can keep count of how many children it has via the count column. So essentially what I'm trying to do is have the triggers update each parent row's count column when necessary
The problem is that the update trigger gets called when the update operation in the insert trigger gets called. Then I get:
"General error: 1442 Can't update table 'term_taxonomies' in stored function/trigger because it is already used by statement which invoked this stored function/trigger".
Any ideas?
Actual code:
TRIGGER `dbname`.`ai_term_taxonomies`
AFTER INSERT ON `dbname`.`term_taxonomies`
FOR EACH ROW
BEGIN
IF NEW.parent_id NOT 0 THEN
UPDATE term_taxonomies as termTax SET assocItemCount = (assocItemCount + 1)
WHERE termTax.term_taxonomy_id = NEW.parent_id;
END IF;
END$$
CREATE
TRIGGER `dbname`.`au_term_taxonomies`
AFTER UPDATE ON `dbname`.`term_taxonomies`
FOR EACH ROW
BEGIN
IF NEW.parent_id NOT OLD.parent_id THEN
IF NEW.parent_id NOT 0 THEN
UPDATE term_taxonomies as termTax SET assocItemCount = (assocItemCount + 1)
WHERE termTax.term_taxonomy_id = NEW.parent_id;
END IF;
IF OLD.parent_id NOT 0 THEN
UPDATE term_taxonomies as termTax SET assocItemCount = (assocItemCount - 1)
WHERE termTax.term_taxonomy_id = OLD.parent_id;
END IF;
END IF;
END$$
All mysql triggers execute in the same transaction as the triggering statement.
You want to update using the SET NEW.assocItemCount syntax as opposed to performing an UPDATE statement on the underlying table.
Edit: However, in your case this is not possible because you are updating a different row in the same table, the hardest thing to do in a mysql trigger. Sorry.
You will have to change your schema. Take assocItemCount out of your table, create a new table holding just term_taxonomy_id and assocItemCount, and update that using an UPDATE statement from your query. It is also possible to use a view joining these two tables to hide this detail if a query needs to use your original schema.
Alternatively, if you did not have assocItemCount in your database at all, you would still be able to compute it in any queries, and your database would be better normalized than it is now.

Update MySQL Row When Updating Using Another Table

I'm trying to use a trigger to update a column on my database when a row gets updated.
This is the trigger
CREATE
DEFINER=`root`#`localhost`
TRIGGER `mysql_development`.`update_translated_position`
BEFORE UPDATE ON `mysql_development`.`players_to_teams`
FOR EACH ROW
BEGIN
UPDATE players_to_teams
INNER JOIN position_translator
ON NEW.position = position_translator.RawPosition
SET NEW.translated_position = position_translator.NCAAposAbbrev1;
END$$
I need to "calculate" the translated_position from the raw position input (in case someone gives me a non-standard position).
I think this is locking the row, because I am getting a 1096, no table used error.
I need to update the players_to_teams row being updated using the external position_translator table.
Use SET directly rather than UPDATE (and therefore avoid the join altogether):
SET NEW.translated_position := (
SELECT NCAAposAbbrev1 FROM position_translator WHERE RawPosition = NEW.position
);

Automatic Update of datetime on Table Update :MS SQL08

I want to insert the current datetime whenever a new row is inserted or updated.
The getdate() gives the datetime whenever a row is inserted. But it doesn’t update itself at the time of row update.
Is there any way to do this?
Edit: I don't want to use Triggers.
This is the Trigger you need for update:
CREATE TRIGGER Update ON TABLE1
FOR UPDATE
AS
BEGIN
SET NOCOUNT ON
UPDATE TABLE1
SET UpdatedOn = GETDATE()
FROM TABLE1 A
INNER JOIN Inserted INS ON (A.Id = INS.Id)
SET NOCOUNT OFF
END
A stored procedure may help you then, but then an ad-hoc update operation will lead to inconsistent data.