Trying to capture Date and time when a specific field in a single record is updated - sql

I had to add the fields Comments and Comments DateTime to a table. A user would leave comments, and when this was complete, the Comments DataTime is supposed to capture the date and time of the update of the Comments field. Many examples I've seen track when the entire record updates a datetime field, however I'm looking to capture the datetime only when the specific Comments field is update for that record. How do I go about doing this? Any advice is greatly appreciated.
edit Getting incorrect syntax near the keyword 'FROM'
CREATE TRIGGER dbo.updateComments
ON dbo.tbl_location_history
/*Check whether column comments has been updated. If column comments has
been changed, update column comments_datetime with getdate().*/
FOR INSERT,UPDATE AS
/*Use IF (COLUMNS_UPDATED() &7) = 7 to see whether column 7 was updated.*/
/*Checking out IF UPDATE(comments) for proper time stamp */
IF UPDATE(comments)
BEGIN
SET NOCOUNT ON;
UPDATE dbo.tbl_location_history
SET dbo.tbl_location_history.comments_datetime = GETDATE();
FROM table dbo.tbl_location_history INNER JOIN
inserted i ON dbo.tbl_location_history.location_id = i.id
END

I would create a trigger on sql server. In that trigger test if the comment is different than the old record. You can log the activity anywhere you like using code in the trigger.

If you want to track when ONLY the comments field is updated you can use the COLUMNS_UPDATED() function in your update trigger. It can be used to track if column 1, 4, and 6 were updated, or it can track if only column 10 was updated. The code to put inside your trigger will look something like this:
IF CAST(SUBSTRING(COLUMNS_UPDATED(),1,1) AS INT) = 0)
BEGIN
--do something here
END
It's kind of confusing, but it basically checks which columns were updated and uses binary (I think) to denote what columns were actually changed. Please read through this MSDN article and let me know if you have specific questions.
https://msdn.microsoft.com/en-us/library/ms186329.aspx

An alternative to tracking these changes in your own schema is to configure the database to do it for you. Starting with SQL Server 2008, you can enable change tracking at the database level. Change tracking is like an index in that it is maintained transparently by SQL Server. Once enabled on a table, a variety of change tracking functions are available that let you inspect what changed and when.

GOT IT! First off, thank you to EVERYONE that posted here. You've been an amazing help directing me in the right direction. Looked over the code and saw my mistakes (good night's sleep does wonders). Here's the solution:
CREATE TRIGGER dbo.updateComments
ON dbo.tbl_location_history
/*Check whether column comments has been updated. If column comments has
been changed, update column comments_datetime with getdate().*/
FOR INSERT,UPDATE AS
/*Use IF (COLUMNS_UPDATED() &7) = 7 to see whether column 7 was updated.*/
/*Checking out IF UPDATE(comments) for proper time stamp */
IF UPDATE(comments)
BEGIN
SET NOCOUNT ON;
UPDATE dbo.tbl_location_history
SET dbo.tbl_location_history.comments_datetime = GETDATE()
FROM dbo.tbl_location_history INNER JOIN inserted i ON dbo.tbl_location_history.location_id = i.location_id
END

Related

What can I use instead of an update trigger?

I have an update trigger in SQL Server and I want to remove this trigger and make update operation with a stored procedure instead of the trigger. But I have UPDATE(end_date) control in update trigger.
What can I use instead of below UPDATE(end_date) control? How can I compare old and new end_dates in stored procedure efficiently?
Update trigger
ALTER TRIGGER [dbo].[trig_tbl_personnel_car_update]
ON [dbo].[tbl_personnel_cars]
FOR UPDATE
AS
IF (UPDATE(end_date))
UPDATE pc
SET pc.owner_changed = 1
FROM tbl_personnel_cars pc, inserted i
WHERE pc.pk_id = i.pk_id
Sample updated script in stored procedure
ALTER PROCEDURE [dbo].[personnel_car_update]
(#PkId INT)
UPDATE tbl_personnel_cars
SET end_date = GETDATE()
WHERE pk_id = #PkId
I update tbl_personnel_cars table inside many stored procedures like this. How can I update this table like trigger does instead of update trigger?
I tried below codes to get old and new end_dates but I can't.
Sample updated script in stored procedure:
ALTER PROCEDURE [dbo].[personnel_car_update]
(#PkId INT)
UPDATE tbl_personnel_cars
SET end_date = GETDATE()
WHERE pk_id = #PkId
EXEC update_operation_sp_instead_trigger #PkId
ALTER PROCEDURE [dbo].[update_operation_sp_instead_trigger]
(#PkId INT)
UPDATE pc
SET pc.owner_changed = 1
FROM tbl_personnel_cars pc
JOIN tbl_personnel_cars pc2 ON pc.pk_id = pc2.pk_id
WHERE pc.end_date <> pc2.end_date
And last question. Is it a correct choice to use stored procedure instead of trigger where the table is updated?
Firstly, I want to clarify a misunderstanding you appear to have about the UPDATE function in Triggers. UPDATE returns a boolean result based on if the column inside the function was assigned a value in the SET clause of the UPDATE statement. It does not check if that value changed. This is both documented feature, and is stated to be "by-design".
This means that if you had a TRIGGER with UPDATE(SomeColumn) the function would return TRUE for both of these statements, even though no data was changed:
UPDATE dbo.SomeTable
SET SomeColumn = SomeColumn;
UPDATE ST
SET SomeColumn = NULL
FROM dbo.SomeTable ST
WHERE SomeColumn IS NULL;
If, within a TRIGGER, you need to check if a value has changed you need to reference the inserted and deleted pseudo-tables. For non-NULLable columns equality (=) can be checked, however, for NULLable columns you'll also need to check if the column changed from/to NULL. In the latest version of the data engine (at time of writing) IS DISTINCT FROM makes this far easier.
Now onto the problem you are actually trying to solve. It looks like you are, in truth, overly complicated this. Firstly, you are setting the value to GETDATE so it is almost certainly impossible that the column will be set to be the same value it already set to; you have a 1/300 second window to do the same UPDATE twice, and if you add IO operations, connection timing, etc, that basically makes hitting that window twice impossible.
For what you want, just UPDATE both columns in your procedure's definition:
ALTER PROCEDURE [dbo].[personnel_car_update] #PkId int AS --This has a trailing comma, which is invalid syntax. The parathesis are also not needed; SP's aren't functions. You were also missing the AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.tbl_personnel_cars --Always schema qualify
SET end_date = GETDATE(),
owner_changed = 1
WHERE pk_id = #PkId;
END;
Larnu gave you a great answer about the stored procedure logic, so I want to answer your question about "Is it a correct choice to use stored procedure instead of trigger where the table is updated?"
The upsides of DML triggers are following in my opinion:
When you have a lot of places that manipulate a table, and there need to be some common logic performed together with this manipulation like audit / logging, trigger can solve it nicely because you don't have to repeat your code in a lot of places
Triggers can prevent "stupid" actions like: DELETEs / UPDATEs without WHERE by performing some specific validation etc. They can also make sure you are setting all mandatory fields (for example change date) when performing adhoc updates
They can simplify quick and dirty patches when you can concentrate your logic to one place instead of X number of procedures / compiled code.
Downside of triggers is performance in some cases, as well as some more specific problems like output clause not working on the triggered tables and more complicated maintenance.
It's seldom you can't solve these issues with other non-trigger solutions, so i'd say if your shop already uses triggers, fine, but if they don't, then there's seldom a really good reason to start either

SQL Server stored procedure locking issue?

I created this pretty basic stored procedure, that gets called by our cms when a user creates a specific type of item. However, it looks like there are times when we get two rows for each cms item created with the same data, but an off-by-one SourceID. I don't do much SQL work, so this might be something basic - but do I need to explicitly lock the table somehow in the stored procedure to keep this from happening?
Here is the stored procedure code:
BEGIN
SET #newid = (SELECT MAX(SourceID)+1 from [dbo].[sourcecode])
IF NOT EXISTS(SELECT SourceId from [dbo].[sourcecode] where SourceId = #newid)
INSERT INTO [dbo].[sourcecode]
(
SourceID,
Description,
RunCounts,
ShowOnReport,
SourceParentID,
ApprovedSource,
Created
)
VALUES
(
#newid,
#Desc,
1,
#ShowOnReport,
1,
1,
GetDate()
)
RETURN #newid
END
and here is an example of the duplicated data (less a couple of irrelevant columns):
SourceId Description Created
676 some text 2012-10-17 09:42:36.553
677 some text 2012-10-17 09:43:01.380
I am sure this has nothing to do with SP. As Oded mentioned, this could be the result of your code.
I don't see anything in the stored procedure which is capable of generating duplicates.
Also, I wouldn't use MAX(SourceId) + 1. Why don't you use "Auto Increment" if you want a new Source Id all the time anyways?
As it has been said in the comments, I think your issue is more in the code layer; none of the data seems to be violating any constraints. You may want to do a check to see if the same user has submitted the same data "recently" before performing the insert.
You can use locking when using stored procedures. On the ones I use I usually use WITH (ROWLOCK). Locking is used to ensure data integrity. I think a simple Google should bring up lots of information about why you should be using locking.
But as other commentators had said, see if there isn't anything in your code as well. Is there something that is calling the same method twice? Are there 'events' referencing the method that is doing the updating?
The description is probably duplicated because you are calling the same function twice, by clicking the button twice, or whatever.
You should use an IDENTITY on your SourceID column and use the Scope_Identity() function
If you don't want to do that for some reason, then you should wrap the above code in a transaction with the isolation level set to Serializable
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRAN
SET #newid = ....
COMMIT

My Update statement is being processed before a previously stated INSERT INTO

I'm trying to update 37k records with a new value, but prior to that I'm trying to enter an Audit trail record to record the previous value. What I'm running into is that the Update command, even though comes after the INSERT INTO command, the INSERT INTO command's "Previous Value" is showing the new value. It appears that the Update command is being processed first.
WHILE #iLP <=#cntDeal_SD
BEGIN
-- History Change
SET #PrevSettleDate=(SELECT SettleDate FROM Deal WHERE DealID=#Deal_ID)
INSERT INTO History
(ItemKey,LoginID,TimeStamp,HowChanged,FieldChanged,PreviousValue,NewValue,Comment,Created)
VALUES ('CDeal' + CAST(#Deal_ID AS varchar(10)),1,GETDATE(), 'M','SettleDate', ISNULL(#PrevSettleDate,'NULL'), '3/02/2012', 'TSR5691', 0)
-- Record Change
SET #Deal_ID = (SELECT DealID FROM #tblDeal_SD WHERE Row = #iLP)
UPDATE Deal SET SettleDate = '3/02/2012' WHERE DealID=#Deal_ID
SET #iLP=#iLP + 1
END
[Edit]
Just realized that my SET #Deal_ID statement falls after my INSERT INTO.
While I had realized earlier that I need my INSERT INTO to be posted before my UPDATE, I failed to move the SET statement that everything was based off of.
My bad.
No, the problem is that you're;
Reading #PrevSettleDate from Deal #Deal_ID.
Writing #PrevSettleDate to History for Deal #Deal_ID - so far so good.
Updating #Deal_ID to point to the next Deal - this is where you go wrong.
Updating the next deal's date.
Going back to the beginning, reading PrevSettleDate from the deal you just updated.
...
If you move the updating of #Deal_ID to the start of the loop, things should just work.
I'd use the OUTPUT clause of the update statement. This will let you save old values into a table variable, or even directly into the history table. You can do all of the updates at once this way.
http://msdn.microsoft.com/en-us/library/ms177564.aspx

Updating records from a XML

I need to provide 4 MySQL stored procedures for each table in a database. They are for get, update, insert and delete.
"Get", "delete" and "insert" are straightforward. The problem is "update", because I don't know which parameters will be set and which ones not. Some parameters could be set to NULL, and other simply won't change so they won't be provided.
As I'm already working with XML, after several search in Google I've found that is possible to use a function called UpdateXML, but the examples are too complex and some articles are from 2007. So I don't know if there is a better technique at this moment or something easier.
Any comment, documentation, link, article or whatever of something that you've used and you're happy with, will be well appreciated :D
Cheers.
Usually when you have data from a row in your database in the front-end, you should have all of the values that you might use to update that row in the database. You should pass all of those values into your update, regardless of whether or not they have actually changed. Otherwise, your database doesn't really know whether it's getting a NULL value for a column because that's what it's supposed to be or because you just didn't pass the real value along.
If you are going to have areas of the application where you don't need certain columns from a table, then it's possible to set up additional stored procedures that do not use those columns. It's often easier though to just retrieve all of the columns from the database when you fill your front-end object. The overhead of the extra columns is usually minimal and worth the saved maintenance of multiple update stored procedures.
Here's an example. It's MS SQL Server syntax, so you may have to alter it slightly, but hopefully it illustrates the idea:
CREATE PROCEDURE Update_My_Table
#my_table_id INT,
#name VARCHAR(40),
#description VARCHAR(500),
#some_other_col INT
AS
BEGIN
UPDATE
My_Table
SET
name = #name,
description = #description,
some_other_col = #some_other_col
WHERE
my_table_id = #my_table_id
END
CREATE PROCEDURE Update_My_Table_Limited
#my_table_id INT,
#name VARCHAR(40),
#description VARCHAR(500)
AS
BEGIN
UPDATE
My_Table
SET
name = #name,
description = #description
WHERE
my_table_id = #my_table_id
END
As you can see, just eliminate those columns that you're not updating from the UPDATE statement. Just don't go overboard and try to have a stored procedure for every possible combination of columns that you might want to update. It's much easier to just get the extra columns from the DB when you select from the table in the first place. You'll end up passing the same value back and your server will wind up updating the column with the same exact value, but that's not a big deal. You can code your front end to make sure that at least one column has changed before it will actually try to update anything in the database.

SQL Query fails to update

I am trying to update a row on an SQL SERVER 2005. When I run the SQL, I receive a message indicating that the Execution was successful and 1 row was affected. However, when I do a select against this row I supposedly updated, the value remains unchanged. What's going on with this SQL server when a successful query does absolutely nothing.
The query is:
UPDATE [database1].[dbo].[table1]
SET [order] = 215
WHERE [email] = 'email#email.com'
check for a trigger on [database1].[dbo].[table1], possibly it is doing something you are not aware of.
EDIT
without seeing the trigger code, you probably just need to add support for [order] into the trigger, since it is a new column (based on your comment).
Thanks KM I checked the triggers and you were right. There was a trigger that I had to disable to get the sql to work.