SQL server write once column - sql-server

Is there any way to configure a SQL server instance to not permit updates to values inserted in the database?
e.g. once inserted, the value is fixed, other columns in that same row may be changed, but that value is only written once.

Write a trigger on update that checks the current column against the new value being inserted and rolls back the transaction if the values differ.
create trigger dbo.tr_no_updates
on mytable
for update
as
if update(mycolumn)
rollback transaction

Related

Default value in table column based upon a stored procedure

I have a table in SQL Server 2008 (MyTable) which contains a field (MyField). MyField cannot take nulls and it will be populated with a default value. The default value is calculated by means of a complex stored procedure (MyStoreProcedure), which invokes other tables and fields in my database. My users should have the option to change this default value once it has been supplied by SQL server. SQL server, however, will have a AFTER UPDATE TRIGGER to check that the entered value in MyField conforms to certain rules.
I have tried to populate MyField with the intended default value by means of a 'TRIGGER AFTER INSERT' and SQL Server tells me that null values cannot be inserted into MyField, which is actually true. Is there a workaround to implement this default value based upon a stored proc?
Thanks
This may be a case for an INSTEAD OF INSERT trigger, which will allow you to access the stored procedure and set a default value before the row(s) is/are inserted into the target table.
The trick is then getting the stored procedure result(s) into the trigger. One approach would be to insert the stored procedure results into a temporary table, which then may be joined with the INSERTED meta table for the actual insert. See How to SELECT FROM stored procedure.

SQL Server 2016 Create Trigger to start after New Table is finished updating

I have a table that is pushed to me from another SQL Server. The table is dropped after it is rotated to a "Current Day" table (Current Data is rotated to prev day before this).
Currently we have jobs that are running to do the "rotating" that are set at a specific time. I had originally created a trigger but clearly a trigger won't work (as I figured out from the comments) since the DDL operation wont continue its flow until after this trigger is complete... It also looks like this is just not possible since I don't have control over the group that is pushing the data to us.
Resolution : I went to the org that pushes the data and requested they add a step that inserts a record into a TableLog table and I am doing my trigger off of that insert instead.
CREATE TRIGGER InsertTest
ON [pace].[Table_Load_Log]
after insert
AS
if exists(select Table_name from inserted where inserted.Table_name = 'POE_Task_Details_SE_TEMP')
BEGIN
--drop table dbo.newtable
exec dbo.sp_start_job N'Make Pace Tables From Temp Table Push’
END
GO
There is no way to do this with a trigger. If you need to know when a CRUD operation on a table is complete, you would need to execute a command after the CRUD operation in the same process that launches it.

How do I add a “last modified” and "created" column in a SQL Server table?

I'm design a new db schema for a SQL Server 2012 database.
Each table should get two extra columns called modified and created which should be automatically change as soon a row gets inserted or updated.
I don't know how rather the best way to get there.
I assuming that trigger are the best way to handle it.
I was trying to find examples with triggers.. but the tutorials which I found insert data in another table etc.
I assumed it's a quite common scenario but I couldn't find the answer yet.
The created column is simple - just a DATETIME2(3) column with a default constraint that gets set when a new row is inserted:
Created DATETIME2(3)
CONSTRAINT DF_YourTable_Created DEFAULT (SYSDATETIME())
So when you insert a row into YourTable and don't specify a value for Created, it will be set to the current date & time.
The modified is a bit more work, since you'll need to write a trigger for the AFTER UPDATE case and update it - you cannot declaratively tell SQL Server to do this for you....
Modified DATETIME2(3)
and then
CREATE TRIGGER updateModified
ON dbo.YourTable
AFTER UPDATE
AS
UPDATE dbo.YourTable
SET modified = SYSDATETIME()
FROM Inserted i
WHERE dbo.YourTable.PrimaryKey = i.PrimaryKey
You need to join the Inserted pseudo table which contains all rows that were updated with your base table on your primary key for that table.
And you'll have to create this AFTER UPDATE trigger for each table that you want to have a modified column in.
Generally, you can have the following columns:
LastModifiedBy
LastModifiedOn
CreatedBy
CreatedOn
where LastModifiedBy and CreatedBy are references to a users table (UserID) and the LastModifiedOn and CreatedOn columns are date and time columns.
You have the following options:
Solution without triggers - I have read somewhere that "The best way to write triggers is not to write such." and you should know that generally they are hurting the performance. So, if you can avoid them it is better to do so, even using triggers may look the easiest thing to do in some cases.
So, just edit all you INSERT and UPDATE statements to include the current UserID and current date and time. If such user ID can not be defined (anonymous user) you can use 0 instead and the default value of the columns (in case no user ID is specified will be NULL). When you see NULL values are inserted you should find the "guilty" statements and edit it.
Solution with triggers - you can created AFTER INSERT, UPDATE trigger and populated the users columns there. It's easy to get the current date and time in the context of the trigger (use GETUTCDATE() for example). The issue here is that the triggers do not allowed passing/accepting parameters. So, as you are not inserting the user ID value and you are not able to pass it to the trigger. How to find the current user?
You can use SET CONTEXT_INFO and CONTEXT_INFO. Before all you insert and update statements you must use the SET CONTEXT_INFO to add the current user ID to the current context and in the trigger you are using the CONTEXT_INFO function to extract it.
So, when using triggers you again need to edit all your INSERT and UPDATE clauses - that's why I prefer not to use them.
Anyway, if you need to have only date and time columns and not created/modified by columns, using triggers is more durable and easier as you are not going to edit any other statements now and in the future.
With SQL Server 2016 we can now use the SESSION_CONTEXT function to read session details. The details are set using sp_set_session_context (as read-only or read and write). The things are a little bit user-friendly:
EXEC sp_set_session_context 'user_id', 4;
SELECT SESSION_CONTEXT(N'user_id');
A nice example.
Attention, above works fine but not in all cases,
I lost a lot of time and found this helpfull:
create TRIGGER yourtable_update_insert
ON yourtable
AFTER UPDATE
as
begin
set nocount on;
update yourtable set modified=getdate(), modifiedby = suser_sname()
from yourtable t
inner join inserted i on t.uniqueid=i.uniqueid
end
go
set nocount on; is needed else you get the error:
Microsoft SQL Server Management Studio
No row was updated.
The data in row 5 was not committed.
Error Source: Microsoft.SqlServer.Management.DataTools.
Error Message: The row value(s) updated or deleted either do not make the row unique or they alter multiple rows(2 rows).
Correct the errors and retry or press ESC to cancel the change(s).
OK Help
CREATE TRIGGER [dbo].[updateModified]
ON [dbo].[Transaction_details]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.Transaction_details
SET ModifedDate = GETDATE() FROM dbo.Transaction_details t JOIN inserted i ON
t.TransactionID = i.TransactionID--SYSDATETIME()
END
One important thing to consider is that you should always have the inserted / updated time for all of your tables and rows be from the same time source. There is a danger - if you do not use triggers - that different applications making direct updates to your tables will be on machines that have different times on their clocks, or that there will not be consistent use of local vs. UTC in the application layer.
Consider a case where the system making the insert or update query that directly sets the updated / modified time value has a clock that is 5 minutes behind (unlikely, but worth considering) or is using local time versus UTC. If another system is polling using an interval of 1 minute, it might miss the update.
For a number of reasons, I never expose my tables directly to applications. To handle this situation, I create a view on the table explicitly listing the fields to be accessed (including the updated / modified time field). I then use an INSTEAD OF UPDATE, INSERT trigger on the view and explicitly set the updatedAt time using the database server's clock. This way I can guarantee that the timebase for all records in the database is identical.
This has a few benefits:
It only makes one insert to the base table and you don't have to
worry about cascading triggers being called
It allows me to control at the field level what information I expose
to the business layer or to other consumers of my data
It allows me to secure the view independently from the base table
It works great on SQL Azure.
Take a look at this example of the trigger on the view:
ALTER TRIGGER [MR3W].[tgUpdateBuilding] ON [MR3W].[vwMrWebBuilding]
INSTEAD OF UPDATE, INSERT AS
BEGIN
SET NOCOUNT ON
IF EXISTS(SELECT * FROM DELETED)
BEGIN
UPDATE [dbo].[Building]
SET
,[BuildingName] = i.BuildingName
,[isActive] = i.isActive
,[updatedAt] = getdate()
FROM dbo.Building b
inner join inserted i on i.BuildingId = b.BuildingId
END
ELSE
BEGIN
INSERT INTO [dbo].[Building]
(
[BuildingName]
,[isActive]
,[updatedAt]
)
SELECT
[BuildingName]
,[isActive]
,getdate()
FROM INSERTED
END
END
I hope this helps, and I would welcome comments if there are reasons this is not the best solution.
This solution might not work for all use cases but wherever possible its a very clean way.
Create an stored procedure for inserting/updating row in table and only use this sp for modifying the table. In stored procedure you can always set created and updated column as required. e.g. setting updatedTime = GetUTCTime()

Generate generic insert statement from existing row for disconnected insert to another database for sync

We currently have a c# console app that creates a sync table from triggers that allows the app to send an insert update or delete statement to another copy of the database at another site. This keeps the tables in sync, however the system regularly crashes and we do not have the source code.
I am currently trying to recreate this in sql server.
The sync table is generated in a trigger on each table and adds the
TableName Action RowNumer columns_updated
test Insert 10 0x3
test2 Delete 2
test update 15 07x
From this I can generate an insert, update or delete statement which can be run on the remote server, but with thousands of rows it would be far too slow.
Insert server2.test.column1,column2,column3.column4 select column1,column2,colum3,column4 from
server1.test where row = RowNumber
What I would like to do is generate the insert statement on server1, then simply run it on server2
"Insert column1.column2.colum3.column4 into table1 values 110000,New Order, £99.00, 'John Smith'"
So does anybody have a way to write the insert statement to a table row as a string ready for processing in server 2. This select does not have to happen in the trigger.
i.e. read any row in any table and convert it into an insert statement?

Selecting Change_Tracking_Current_Version() in SQL Server 2008 table trigger

I am using SQL Server 2008 and Change tracking is enabled on my Database.
On one of my tables in the database, I have created the following Trigger.
CREATE TRIGGER [dbo].[tr_student]
ON [dbo].[Student]
FOR UPDATE,DELETE
AS
BEGIN
SET NOCOUNT ON;
SELECT CHANGE_TRACKING_CURRENT_VERSION()
END
The purpose of this Trigger is - when I update or delete a record in table dbo.Student I should get the the Current change Tracking Version Id.
But when I update/delete a record in the table, CHANGE_TRACKING_CURRENT_VERSION() is not giving the current version_id but the previous version_id. After the trigger is fired,if I execute
select CHANGE_TRACKING_CURRENT_VERSION()
I am getting the correct current version id.
So does the Change_Tracking_Current_Version() id change only after the trigger? Is there any method to get correct Change_Tracking_Current_Version() in the Trigger?
According to the manual, this function
Returns a version that is associated with the last committed transaction.
(Emphasis is mine.)
While your trigger is executing, the transaction is not yet committed. So yes, the new value will be returned after the trigger.
Moreover, there's no way to obtain the new ID in a trigger, because, as specified elsewhere in the manual,
Change tracking is based on committed transactions.
(Emphasis is mine.)

Resources