I've got a SQL Server table which keeps track of customer name changes:
CREATE TABLE CustomerHistory
(
Id INT IDENTITY(1,1) PRIMARY KEY,
CustomerId INT NOT NULL,
Name VARCHAR(255) NOT NULL,
ValidFrom DATETIME NOT NULL,
ValidTo DATETIME NOT NULL,
CreatedOn DATETIME NOT NULL,
ModifiedOn DATETIME NOT NULL
)
INSERT INTO CustomerHistory (CustomerId, Name, ValidFrom, ValidTo, CreatedOn, ModifiedOn )
VALUES (1, 'ABC', '1900-01-01','2999-12-31', '2015-07-03 11:29:23.000', '2015-07-03 11:29:23.000')
Application allows user to make changes in two ways i.e. either by changing current record (Name
and ModifiedOn
are updated) or by inserting new record
INSERT INTO CustomerHistory (CustomerId, Name, ValidFrom, ValidTo, CreatedOn, ModifiedOn)
VALUES (1,'AAB', '2015-07-04','2999-12-31', '2015-07-04 12:29:23.000', '2015-07-04 12:29:23.000')
and updating previous one (Name, ValidTo, ModifiedOn
are updated). Due to several unwanted changes done in the first way (updating current record only) I need to block it so each change made is done in the second way (inserting new record and updating previous one). I need to do it with trigger so users gets special error information.
Any idea how to cope with it?
You can use this trigger for example:
Filled with demo data using:
The table is filled with:
If you run the Insert again it will result in this:
This trigger will just invalidate all older rows of a specific
CustomerId
.If you want additionally to set the
ModifiedOn
date if an update occurs you can create this additional trigger:By the way, a procedure which handles this would be better than a trigger. But in some cases you can't provide a procedure. For example if users will manipulate the data using
SSMS
,Access
or direct updates through any other applications.Edit based on the Feedback
I leave the old code above just in case someone else needed a solution like described above. In your case you just want to block all Updates except they update only the
ModifiedOn
and theValidTo
columns.In this case this trigger will solve it:
This code will throw an exception:
While this one will work:
Another addition can be an
WHERE
-clause inside theINSTEAD OF
-Trigger inside theIF
-clause. Which will prevent updates on the two mentioned columns, if the Insert is older than 60 seconds (for example). This can be achieved by adding this row to theIF
:Which results into this code:
Addition for really broad tables
If you have a really broad table with many columns and you won't to maintain all columns and just want to add the allowed columns you can probably use the following trigger which uses a hash to compare the old and the new row instead. It will work after the same principle of the above one but uses a dynamic hash algorithm.
Idealy I would recommend allowing users only to execute a stored procedure that will encapsulate the logic.
If you can't use a stored procedure, You can use an instead of update trigger on the table. That way the only way to update existing records is going through this trigger.