timestamp, rowversion and datetime - I need a mark when a row is inserted or updated - sql

I have the following requirement:
I have a table with 1 unique auto-incremental Int ID and data columns.
I need the following:
Every time a row is inserted into that table, a column at the right end of the table must hold the full datetime of that insert.
Also, if a row is updated I need that column that holds the full datetime of the insert of that row into the table, to be updated to hold the update time for that row.
Now the obvious and very straightforward way to do this is:
you create your table:
create table My_Test_Table
(my_id int identity not null,
my_data nvarchar(max));
you alter your table adding the datetime column and a Default constraint on it:
ALTER TABLE My_Test_Table
ADD [timestamp] datetime;
ALTER TABLE My_Test_Table
ADD CONSTRAINT DF_My_Test_Table_timestamp DEFAULT GETDATE() FOR [timestamp];
then you make a nice trigger for update, like so:
CREATE TRIGGER DML_Trigger_update_My_Test_Table
ON My_Test_Table
FOR UPDATE
AS
IF ##ROWCOUNT <> 0
BEGIN
IF EXISTS (SELECT * FROM INSERTED) AND EXISTS (SELECT * FROM DELETED)
BEGIN
UPDATE My_Test_Table
SET [timestamp] = GETDATE()
FROM INSERTED
WHERE My_Test_Table.my_id = INSERTED.my_id;
END
END
Now the tricky part is:
I want, for reasons that are beyond scope here, to implement this exact thing as above but without a Trigger!
Is it possible?
I do not want to use the SQL type timestamp or rowversion, this won't work for me, I need the date, time down to the milliseconds to be clearly stored in that column.
Any ideas would be much appreciated.

You don't need a trigger
You can use the DEFAULT keyword as the source value in the UPDATE statement to use, well, the DEFAULT constraint defined on that column
UPDATE
MyTable
SET
foo = ...,
bar = ...,
ChangedDateTime = DEFAULT
WHERE
...;
I wouldn't use a column called timestamp because this has meaning on SQL Server, as a synonym for rowversion. For SQL Server 2008+ use datetime2(3) to accurately record milliseconds. The "old" datetime is accurate to a rounded 3.33 milliseconds only

Related

SQL server using computed column and user defined function to grab datetime based on change in another column

Given: Given a Microsoft SQL (2016 and above) database table Log with multiple columns including these important ones: id (primary key), code (an integer that can take multiple values representing status changes), lastupdated (a datetime field)...
What I need:
I need to add a computed column ActiveDate which stores the exact first time when the code changed to 10 (i.e. an active status). As the status keep[s changing in future, this column must maintain the same value as the exact time it went active (thus keeping the active datetime record persistently). This timestamp value should initially begin with a NULL.
My approach
I want the activedate field to automatically store the datetime at which the status code becomes 10, but when the status changes again, I want it to remain the same. Since I can't reference a calculated column from a calculated column, I created a user defined function to fetch the current value of activedate and use that whenever the status code is not 10.
Limitations:
I can't make modifications to the Db or to columns (other than the new columns I can add).
This T-SQL script must be idempotent such that it can be run multiple times at anytime in the production pipeline without losing or damaging data.
Here is what I tried.
IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE Name=N'ActiveDate' AND OBJECT_ID = OBJECT_ID(N'[dbo].[Log]'))
/* First, create a dummy ActiveDate column since the user-defined function below needs it */
ALTER TABLE [dbo].[Log] ADD ActiveDate DATETIME NULL
IF OBJECT_ID('UDF_GetActiveDate', 'FN') IS NOT NULL
DROP FUNCTION UDF_GetActiveDate
GO
/* Function to grab the datetime when status goes active, otherwise leave it unchanged */
CREATE FUNCTION UDF_GetActiveDate(#ID INT, #code INT) RETURNS DATETIME WITH SCHEMABINDING AS
BEGIN
DECLARE #statusDate DATETIME
SELECT #statusDate = CASE
WHEN (#code = 10) THEN [lastupdated]
ELSE (SELECT [ActiveDate] from [dbo].[Log] WHERE id=#ID)
END
FROM [dbo].[Log] WHERE id=#ID
RETURN #statusDate
END
GO
/* Rename the dummy ActiveDate column so that we can be allowed to create the computed one */
EXEC sp_rename '[dbo].[Log].ActiveDate', 'ActiveDateTemp', 'COLUMN';
/* Computed column for ActiveDate */
ALTER TABLE [dbo].[Log] ADD ActiveDate AS (
[dbo].UDF_GetActiveDate([id],[code])
) PERSISTED NOT NULL
/* Delete the dummy ActiveDate column */
ALTER TABLE [dbo].[Log] DROP COLUMN ActiveDateTemp;
print ('Successfully added ActiveDate column to Log table')
GO
What I get: The following errors
[dbo].[Log].ActiveDate cannot be renamed because the object
participates in enforced dependencies.
Column names in each table
must be unique. Column name 'ActiveDate' in table 'dbo.Log' is
specified more than once.
Is my approach wrong? Or is there a better way to achieve the same result? Please help.
You shouldn't try to compute a column from itself.
Instead, I'd use a trigger...
CREATE TRIGGER dbo.log__set_active_date
ON dbo.log
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE
log
SET
active_date = INSERTED.last_updated
FROM
dbo.log
INNER JOIN
INSERTED
ON log.id = INSERTED.id
WHERE
INSERTED.code = 10
AND log.active_date IS NULL -- Added to ensure the value is only ever copied ONCE
END
db<>fiddle demo
I would advise you not to use a computed column or functions for this.
Just create a query that uses window functions:
SELECT
id,
code,
lastupdateddate,
ActiveDate = MIN(CASE WHEN l.code = 10 THEN l.lastupdateddate END)
OVER (PARTITION BY l.id)
FROM dbo.Log;

How can I prevent a record inserted by an SQL trigger attempting to set the identity column

I'm attempting to create a 'history' table that gets updated every time a row on the source table is updated.
Here's the (SQL Server) code I'm using to create the history table:
DROP TABLE eventGroup_History
SELECT
CAST(NULL AS UNIQUEIDENTIFIER) AS NewId,
CAST(NULL AS varchar(255)) AS DoneBy,
CAST(NULL AS varchar(255)) AS Operation,
CAST(NULL AS datetime) AS DoneAt,
*
INTO
eventGroup_History
FROM
eventGroup
WHERE
1 = 0
GO
ALTER TABLE eventGroup_History
ALTER COLUMN NewId UNIQUEIDENTIFIER NOT NULL
go
ALTER TABLE eventGroup_History
ADD PRIMARY KEY (NewId)
GO
ALTER TABLE eventGroup_History
ADD CONSTRAINT DF_eventGroup_History_NewId DEFAULT NewSequentialId() FOR NewId
GO
The trigger is created like this:
drop trigger eventGroup_LogUpdate
go
create trigger eventGroup_LogUpdate
on dbo.eventGroup
for update
as
declare #Now as DateTime = GetDate()
set nocount on
insert into eventGroup_History
select #Now, SUser_SName(), 'update-deleted', *
from deleted
insert into eventGroup_History
select SUser_SName(), 'update-inserted', #Now, *
from inserted
go
exec sp_settriggerorder #triggername = 'eventGroup_LogUpdate', #order = 'last', #stmttype = 'update'
But when I update a row in SQL Server Management Studio, I get a message:
The data in row 2 was not committed.
Error Source: .Net SqlClient Data Provider.
Error Message: Conversion failed when converting from a character string to uniqueidentifier.
I think that the trigger is attempting to insert the SUserSName() as the first column of the row but that is the PK NewId:
There are no other uniqueidentifier columns in the table.
If I add row from the SQL Management Studio's edit grid, the row gets added without me having to specify the NewId value.
So, why is the SQL Server trigger attempting to populate NewId with first item in the INSERT INTO clause rather than skipping it to let the normal IDENTITY operation provide a value?
(And how do I stop this happening so that the trigger works?)
Because the automatic skipping only applies to IDENTITY columns - a GUID column set with the NewSequentialId() constraint behaves similarly to IDENTITY in many ways but not this one.
You can achieve what you are looking for by specifying the columns for the INSERT explicitly.
If you're going to use a default value on your NewId column, you need to explicitly list the column names in the INSERT statements. By default, SQL Server will insert the columns in the order they're listed in the SELECT, unless you give it enough information to do otherwise. Listing out the columns explicitly is a best practice, one way or the other, in order to avoid just this sort of unanticipated result.
So your statements will end up looking like this:
INSERT INTO eventGroup_History
(
DoneBy,
Operation,
DoneAt,
<All the other columns that are masked by the *>
)
SELECT....

Data Type that automatically stores the DateTime of the last transaction done, SQL Server 2008

I need to add a new column to an existing table, so that whenever a new row is added, or an existing row is edited, this column will be filled with the exact date and time of the transaction. I tried using TimeStamp but apparently TimeStamp it has nothing to do with Date time
It's just a binary representation of a consecutive number - it's only good for making sure a row hasn't change since it's been read. (Quoted from How to convert SQL Server's timestamp column to datetime format
Any help is highly appreciated
It sounds like you need to create a trigger to populate/update this new column. See the following: How to: Create trigger for auto update modified date with SQL Server 2008
You have to create a trigger on INSERT and UPDATE for the table and in the trigger you can use
UPDATE myTable
SET myColumn = GETDATE()
Your trigger should look somethign like
CREATE TRIGGER trigger_updateTimestamp
ON myTable
AFTER INSERT, UPDATE
AS
UPDATE myTable
SET myColumn = GETDATE()
WHERE ID = 'xyz'
try
ALTER TABLE myTable ADD CONSTRAINT [DF_myTable_myColumn] DEFAULT (getdate()) FOR [myColumn]
GO
... and a good practice is to have an extra column for edited date, and update using a trigger
CREATE TRIGGER trigger_updateMyTable
ON myTable
AFTER UPDATE
AS
IF EXISTS (SELECT * FROM INSERTED)
BEGIN
IF ΝΟΤ EXISTS (SELECT * FROM DELETED)
BEGIN
UPDATE myTable
SET myEditColumn = GETDATE()
WHERE ID IN (
SELECT
ID
FROM INSERTED
)
END
END

Will datetime datatype in a table column update the current time automatically?

Will datetime datatype in a table column(last_modified_timestamp) update the current time automatically?
i have a table column as shown below , i need to know whether it will insert the current time in the column automatically?
How i know currently i have default settings in my table?
i changed it to insert ...not for updating !
No, it will not. How would you expect SQL to guess which datetime columns should be automatically updated like this, and yet others are meant to record, e.g. historic dates.
For INSERT purposes, you can have a DEFAULT constraint on the column that inserts the current date (Getdate()/CURRENT_TIMESTAMP).
But for UPDATEs to work, you'd have to implement a trigger.
For INSERT purposes, and using the table designer, you can look at the "Default Value or Binding" property - you'd set this to (CURRENT_TIMESTAMP) or (GetDate()) (they mean the same thing). Or in the Object Explorer, you can look at the constraints on the table - if there's a default set, it will appear in there.
Also, worth pointing out that a default is exactly as it sounds - there's nothing to prevent someone providing their own value for this column. If you want to prevent this, then trigger's are probably the answer (although a lot of people dislike triggers).
No, it wont.
You either need to specify a default like
DECLARE #Table TABLE(
ID INT,
LastDate DATETIME DEFAULT GETDATE()
)
INSERT INTO #Table (ID) SELECT 1
SELECT *
FROM #Table
Or make use of triggers, or update the values manually using GETDATE() in your INSERT/UPDATES
Definitely NOT.
You can use DEFAULT to be getdate() which will add your current datetime for your column
If default is set on a column, you can see that in your table constraints. Check in the table properties, or type sp_help <tablename>
If you use the use DEFAULT getdate() you must disable the nulls for that column, and if you sent a null then sql will set the default.
sounds like you might need a trigger to update the date when an update is made.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: David Forck
-- Create date: 12Nov10
-- Description: Update last modified date
-- =============================================
CREATE TRIGGER dbo.UpdateLastModified
ON dbo.table1
AFTER update
AS
BEGIN
SET NOCOUNT ON;
update dbo.table1
set last_modified_timestap=getdate()
where ID in (select ID from inserted)
END
GO

DateCreated column in Sql Server?

Is there a special way to declare a DateCreated column in a MS Sql Server table so that it will automatically fill it with the appropriate time-stamp when created?
Or.. do I have to provide the datetime to it when I do the query, manually?
Default values suffer from two major drawbacks.
if the insert statement specifies a value for the column, the default isn't used.
the column can be updated any time.
These mean that you can't be certain that the values haven't been modified outside of your control.
If you want true data integrity (so that you're sure the date in the row is the creation date), you need to use triggers.
An insert trigger to set the column to the current date and an update trigger to prevent changes to that column (or, more precisely, set it to its current value) are the way to implement a DateCreated column.
An insert and update trigger to set the column to the current date is the way to implement a DateModified column.
(edit from user Gabriel - here's my attempt to implement this as described - i'm not 100% sure it's correct but I'm hoping the OP reviews it...):
CREATE TRIGGER [dbo].[tr_Affiliate_IU]
ON [dbo].[Affiliate]
AFTER INSERT, UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Get the current date.
DECLARE #getDate DATETIME = GETDATE()
-- Set the initial values of date_created and date_modified.
UPDATE
dbo.Affiliate
SET
date_created = #getDate
FROM
dbo.Affiliate A
INNER JOIN INSERTED I ON A.id = I.id
LEFT OUTER JOIN DELETED D ON I.id = D.id
WHERE
D.id IS NULL
-- Ensure the value of date_created does never changes.
-- Update the value of date_modified to the current date.
UPDATE
dbo.Affiliate
SET
date_created = D.date_created
,date_modified = #getDate
FROM
dbo.Affiliate A
INNER JOIN INSERTED I ON A.id = I.id
INNER JOIN DELETED D ON I.id = D.id
END
You can set the default value of the column to "getdate()"
We have DEFAULT on CreatedDate and don't enforce with Triggers
There are times when we want to set the date explicitly - e.g. if we import data from some other source.
There is a risk that Application Bug could mess with the CreateDate, or a disgruntled DBA for that matter (we don't have non-DBAs connecting direct to our DBs)
I suppose you might set Column-level permissions on CreateDate.
A half-way-house might be to have an INSERT TRIGGER create a row in a 1:1 table, so that column was outside the main table. The second table could have SELECT permissions, where the main table has UPDATE permissions, and thus not need an UPDATE trigger to prevent changes to CreateDate - which would remove some "weight" when updating rows normally.
I suppose you coul have an UPDATE/DELETE trigger on the second table to prevent change (which would never be executed in normal circumstances, so "lightweight")
Bit of a pain to have the extra table though ... could have one table for all CreateDates - TableName, PK, CreateDate. Most database architects will hate that though ...
Certainly is.
Here is an example in action for you.
Create table #TableName
(
ID INT IDENTITY(1,1) PRIMARY KEY,
CreatedDate DATETIME NOT NULL DEFAULT GETDATE(),
SomeDate VARCHAR(100)
)
INSERT INTO #TableName (SomeDate)
SELECT 'Some data one' UNION ALL SELECT 'some data two'
SELECT * FROM #TableName
DROP TABLE #TableName
Setting the default value isn't enough, you should add a trigger to prevent updating:
CREATE TRIGGER UpdateRecord ON my_table
AFTER UPDATE AS UPDATE my_table
SET [CreatedDate] = ((SELECT TOP 1 [CreatedDate] FROM Deleted d where d.[id]=[id]))