SQL 服务器使用计算列和用户定义的函数根据另一列的变化获取日期时间

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

给定:给定一个 Microsoft SQL(2016 年及以上)数据库 table Log,其中包含多个列,包括以下重要列:id(主键),code(一个可以取多个值表示状态变化的整数),lastupdated(日期时间字段)...

我需要: 我需要添加一个计算列 ActiveDate 来存储代码更改为 10(即活动状态)的第一次准确时间。随着状态在未来不断变化,此列必须保持与它激活的确切时间相同的值(从而持久保持活动日期时间记录)。此时间戳值最初应以 NULL 开头。

我的做法 我希望 activedate 字段自动存储状态码变为 10 的日期时间,但是当状态再次发生变化时,我希望它保持不变。由于我无法从计算列中引用计算列,因此我创建了一个用户定义的函数来获取 activedate 的当前值,并在状态代码不是 10 时使用它。

限制:

这是我试过的。

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

我得到的:以下错误

我的做法有错吗?还是有更好的方法来达到相同的结果?请帮忙。

您不应尝试从自身计算列。

相反,我会使用触发器...

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

我建议您不要为此使用计算列或函数。

只需创建一个使用 window 函数的查询:

SELECT
  id,
  code,
  lastupdateddate,
  ActiveDate = MIN(CASE WHEN l.code = 10 THEN l.lastupdateddate END)
                 OVER (PARTITION BY l.id)
FROM dbo.Log;