SQL Server 2008 行插入和更新时间戳

Posted

技术标签:

【中文标题】SQL Server 2008 行插入和更新时间戳【英文标题】:SQL Server 2008 Row Insert and Update timestamps 【发布时间】:2013-06-11 13:35:11 【问题描述】:

我需要在 SQL Server 2008 R2 的数据库表中添加两列:

createTS - 插入行的日期和时间 updateTS - 更新行的日期和时间

我有几个问题:

    我应该为每一个使用哪种列数据类型? createTS 只需在插入行时设置一次。当我为此列尝试datetime 类型并添加getdate()默认值或绑定 时,列值已正确设置。这是实现本专栏目的的最佳方式吗?我考虑过timestamp data type,但在我看来,这几乎是用词不当! updateTS 需要设置为更新行的那一刻的日期和时间。在 SQL Server 中,没有 ON UPDATE CURRENT_TIMESTAMP(如在 mysql 中),所以看起来我不得不求助于使用触发器。这是正确的吗?我该怎么做?

因此,任何想回答这个问题的人都有一个起点,这里是创建表脚本:

CREATE TABLE [dbo].[names]
(
    [name] [nvarchar](64) NOT NULL,
    [createTS] [datetime] NOT NULL CONSTRAINT [DF_names_createTS]  DEFAULT (getdate()),
    [updateTS] [datetime] NOT NULL,
    CONSTRAINT [PK_names] PRIMARY KEY CLUSTERED 
    (
        [name] ASC
    )
    WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

【问题讨论】:

旁注:您应该使您的集群密钥 (a) 这么宽(nvarchar(64) = 128 字节!!)而且 (b) 也不建议这样做它是一个可变长度的列(因为它们有额外的开销)。 好的 聚类键应该是窄的、唯一的、静态的(不会改变),并且在理想情况下会不断增加 - INT IDENTITY 几乎是完美的,任何超过 8-16 个字节的都是 可怕的 糟糕的集群键... 【参考方案1】:

试试

CREATE TABLE [dbo].[Names]
(
    [Name] [nvarchar](64) NOT NULL,
    [CreateTS] [smalldatetime] NOT NULL CONSTRAINT CreateTS_DF DEFAULT CURRENT_TIMESTAMP,
    [UpdateTS] [smalldatetime] NOT NULL

)

PS 我认为 smalldatetime 就足够了。您可能会做出不同的决定。

你不能在“冲击时刻”这样做吗?

在 Sql Server 中,这很常见:

Update dbo.MyTable 
Set 

ColA = @SomeValue , 
UpdateDS = CURRENT_TIMESTAMP
Where...........

Sql Server 有一个“时间戳”数据类型。

但可能不是你想的那样。

这是一个参考:

http://msdn.microsoft.com/en-us/library/ms182776(v=sql.90).aspx

这里有一个小RowVersion (synonym for timestamp) 例子:

CREATE TABLE [dbo].[Names]
(
    [Name] [nvarchar](64) NOT NULL,
    RowVers rowversion ,
    [CreateTS] [datetime] NOT NULL CONSTRAINT CreateTS_DF DEFAULT CURRENT_TIMESTAMP,
    [UpdateTS] [datetime] NOT NULL

)


INSERT INTO dbo.Names (Name,UpdateTS)
select 'John' , CURRENT_TIMESTAMP
UNION ALL select 'Mary' , CURRENT_TIMESTAMP
UNION ALL select 'Paul' , CURRENT_TIMESTAMP

select *  ,  ConvertedRowVers = CONVERT(bigint,RowVers) from [dbo].[Names]

Update dbo.Names Set Name = Name

select *  ,  ConvertedRowVers = CONVERT(bigint,RowVers) from [dbo].[Names]

也许是一个完整的工作示例:

DROP TABLE [dbo].[Names]
GO


CREATE TABLE [dbo].[Names]
(
    [Name] [nvarchar](64) NOT NULL,
    RowVers rowversion ,
    [CreateTS] [datetime] NOT NULL CONSTRAINT CreateTS_DF DEFAULT CURRENT_TIMESTAMP,
    [UpdateTS] [datetime] NOT NULL

)

GO

CREATE TRIGGER dbo.trgKeepUpdateDateInSync_ByeByeBye ON dbo.Names
AFTER INSERT, UPDATE
AS

BEGIN

Update dbo.Names Set UpdateTS = CURRENT_TIMESTAMP from dbo.Names myAlias , inserted triggerInsertedTable where 
triggerInsertedTable.Name = myAlias.Name

END


GO






INSERT INTO dbo.Names (Name,UpdateTS)
select 'John' , CURRENT_TIMESTAMP
UNION ALL select 'Mary' , CURRENT_TIMESTAMP
UNION ALL select 'Paul' , CURRENT_TIMESTAMP

select *  ,  ConvertedRowVers = CONVERT(bigint,RowVers) from [dbo].[Names]

Update dbo.Names Set Name = Name , UpdateTS = '03/03/2003' /* notice that even though I set it to 2003, the trigger takes over */

select *  ,  ConvertedRowVers = CONVERT(bigint,RowVers) from [dbo].[Names]

匹配“名称”值可能不明智。

用 SurrogateKey 试试这个更主流的例子

DROP TABLE [dbo].[Names]
GO


CREATE TABLE [dbo].[Names]
(
    SurrogateKey int not null Primary Key Identity (1001,1),
    [Name] [nvarchar](64) NOT NULL,
    RowVers rowversion ,
    [CreateTS] [datetime] NOT NULL CONSTRAINT CreateTS_DF DEFAULT CURRENT_TIMESTAMP,
    [UpdateTS] [datetime] NOT NULL

)

GO

CREATE TRIGGER dbo.trgKeepUpdateDateInSync_ByeByeBye ON dbo.Names
AFTER UPDATE
AS

BEGIN

   UPDATE dbo.Names
    SET UpdateTS = CURRENT_TIMESTAMP
    From  dbo.Names myAlias
    WHERE exists ( select null from inserted triggerInsertedTable where myAlias.SurrogateKey = triggerInsertedTable.SurrogateKey)

END


GO






INSERT INTO dbo.Names (Name,UpdateTS)
select 'John' , CURRENT_TIMESTAMP
UNION ALL select 'Mary' , CURRENT_TIMESTAMP
UNION ALL select 'Paul' , CURRENT_TIMESTAMP

select *  ,  ConvertedRowVers = CONVERT(bigint,RowVers) from [dbo].[Names]

Update dbo.Names Set Name = Name , UpdateTS = '03/03/2003' /* notice that even though I set it to 2003, the trigger takes over */

select *  ,  ConvertedRowVers = CONVERT(bigint,RowVers) from [dbo].[Names]

【讨论】:

在影响的那一刻这样做是理想的,但从应用程序的角度来看,这需要在许多地方进行更改。我宁愿在数据库级别处理它......因为它被认为是数据库的责任来跟踪该信息。 然后你被一个触发器卡住了,正如你所怀疑的......据我所知。如果你走这条路,请确保编写“基于集合”的触发代码,而不是“逐行”触发代码。 好吧...我不知道基于集合和逐行触发代码的区别。 看看这个问题的接受答案:***.com/questions/662010/… 简而言之,您会看到我在上面对 John、Mary、Paul 所做的 INSERT。触发器将为该单个 INSERT 运行一次(因为我使用的是“union all”,但触发器代码中的所有 3 行都可供您使用。当我执行 UPDATE“set name = name”时,触发器将为该声明触发一次......以及。【参考方案2】:

作为使用触发器的替代方法,您可能想考虑创建一个存储过程来处理INSERTs,它将大部分列作为参数并获取它包含在最终INSERT 中的CURRENT_TIMESTAMP到数据库。您可以对CREATE 执行相同的操作。您还可以进行设置,使用户无法执行 INSERTCREATE 语句,除非通过存储过程。

我不得不承认我自己并没有真正做到这一点,所以我完全不确定细节。

【讨论】:

现有的存储过程是对应用程序业务逻辑的扩展,所以有很多需要更新的。这就是为什么触发器可能是一种更可取的方法,尽管我不喜欢在这个非常基本的用例中使用它们。

以上是关于SQL Server 2008 行插入和更新时间戳的主要内容,如果未能解决你的问题,请参考以下文章

如何查找在 SQL Server 表中插入或更新行的时间

在 sql server 2008 中创建和修改时间戳

SQL Server 2008 中的 While 循环遍历日期范围,然后插入

使用 SQL Server DTS 包有条件地在目标表中插入/更新行

sql server 2008中“Merge”子句的性能如何?

如何在 SQL Server 2008 上创建插入更新触发器