删除系统版本化时态表的过程

Posted

技术标签:

【中文标题】删除系统版本化时态表的过程【英文标题】:procedure to drop system-versioned temporal tables 【发布时间】:2019-04-04 17:56:34 【问题描述】:

我正在寻找,最好不使用动态 SQL。我查看了 Microsoft 文档并弄清楚了如何获取自动生成的历史表名称,但我对游标了解甚少,对动态 SQL 更是知之甚少。

You can't just drop a temporal table。您必须首先禁用版本控制,这将导致历史表变成普通表。然后你可以同时删除临时表及其对应的历史表。

ALTER TABLE [dbo].[TemporalTest] SET ( SYSTEM_VERSIONING = OFF )
GO
DROP TABLE [dbo].[TemporalTest]
GO
DROP TABLE [dbo].[TemporalTestHistory]
GO

我正在使用带有自动生成历史表的临时表,所以我不知道它们的名称。但是,Microsoft docs 提供了有关如何列出历史表的信息,因此我有办法获取这些名称。

select schema_name(t.schema_id) as temporal_table_schema,
     t.name as temporal_table_name,
    schema_name(h.schema_id) as history_table_schema,
     h.name as history_table_name,
    case when t.history_retention_period = -1 
        then 'INFINITE' 
        else cast(t.history_retention_period as varchar) + ' ' + 
            t.history_retention_period_unit_desc + 'S'
    end as retention_period
from sys.tables t
    left outer join sys.tables h
        on t.history_table_id = h.object_id
where t.temporal_type = 2
order by temporal_table_schema, temporal_table_name

我希望我可以使用带有 DROP 语句的子查询,例如DROP TABLE (SELECT '#t')。这会引发语法错误。

我正在寻找一个带有两个参数的存储过程:要删除的表的名称以及如果表中有任何数据(例如,必须 ROWCOUNT=0)是否应该进行删除。任何人都可以提供帮助,或推荐动态 SQL 上的游标,或推荐另一种技术吗?谢谢!

【问题讨论】:

【参考方案1】:

您不能针对数据发出 DDL(例如子查询的输出),或者将实体名称作为变量传递给语句;您需要动态 SQL。

CREATE PROCEDURE dbo.DropTemporalTable
  @schema sysname = N'dbo',
  @table  sysname
AS
BEGIN
  SET NOCOUNT ON;

  DECLARE @sql nvarchar(max) = N'';

  SELECT @sql += N'ALTER TABLE ' + src + N' SET (SYSTEM_VERSIONING = OFF);
    DROP TABLE ' + src  + N';
    DROP TABLE ' + hist + N';'
  FROM
  (
    SELECT src = QUOTENAME(SCHEMA_NAME(t.schema_id))
               + N'.' + QUOTENAME(t.name),
          hist = QUOTENAME(SCHEMA_NAME(h.schema_id))
               + N'.' + QUOTENAME(h.name)
    FROM sys.tables AS t
    INNER JOIN sys.tables AS h
    ON t.history_table_id = h.[object_id]
    WHERE t.temporal_type = 2
      AND t.[schema_id] = SCHEMA_ID(@schema)
      AND t.name = @table
  ) AS x;

  EXEC sys.sp_executesql @sql;
END
GO

【讨论】:

这太棒了,谢谢!我把它变成了一个过程,并在@t 上对其进行了参数化。 SELECT src = QUOTENAME(SCHEMA_NAME(t.schema_id)) + N'.' + QUOTENAME(t.name), hist = QUOTENAME(SCHEMA_NAME(h.schema_id)) + N'.' + QUOTENAME(h.name) FROM sys.tables AS t INNER JOIN sys.tables AS h ON t.history_table_id = h.[object_id] WHERE t.temporal_type = 2 AND t.name=@t【参考方案2】:

我正在使用带有自动生成历史表的临时表,所以我 不知道他们的名字。但是,Microsoft 文档提供 有关如何列出历史表的信息,所以我有一种方法 获取这些名称。

我不知道你是否知道,但你可以通过多种方式指定历史表的名称。

如果您创建自己的历史记录表:

ALTER TABLE [TemporalTable] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[HistoryTable]))

或者,如果您想为您创建历史记录表:

CREATE TABLE [dbo].[TemporalTable](
    [Id] UNIQUEIDENTIFIER NOT NULL CONSTRAINT [DF_TemporalTable_Id] DEFAULT(NEWID()),
    [SysStartTime] DATETIME2(2) GENERATED ALWAYS AS ROW START NOT NULL CONSTRAINT [DF_TemporalTable_SysStartTime] DEFAULT(SYSUTCDATETIME()),
    [SysEndTime] DATETIME2(2) GENERATED ALWAYS AS ROW END NOT NULL CONSTRAINT [DF_TemporalTable_SysEndTime] DEFAULT(CONVERT(DATETIME2(2),'9999-12-31 23:59:59.99')),
 CONSTRAINT [PK_Product] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 100) ON [PRIMARY],
    PERIOD FOR SYSTEM_TIME ([SysStartTime], [SysEndTime])
) ON [PRIMARY]
WITH
(
SYSTEM_VERSIONING = ON ( HISTORY_TABLE = [dbo].[HistoryTable] )
)

除此之外,我不知道有一种方法可以创建一个存储过程,该过程将表名作为参数并在不使用动态 SQL 的情况下删除它。像下面这样的东西将是必要的。根据您是否希望存储过程查找与其链接的历史表的名称,您可以添加您在帖子中提到的查询而不是参数。

CREATE PROCEDURE DeleteTemporalTable (@tableName NVARCHAR(MAX), @historyTableName) AS
BEGIN
    EXEC ('
    IF EXISTS (
        SELECT
            1
        FROM
            SYSOBJECTS
        WHERE
            id = OBJECT_ID(N''[dbo].[' + @tableName + ']'') AND
            OBJECTPROPERTY(id, N''IsTable'') = 1 AND
            OBJECTPROPERTY(id, N''TableTemporalType'') = 2
    )
    BEGIN
        ALTER TABLE [dbo].[' + @tableName + '] SET (SYSTEM_VERSIONING = OFF)
    END
    DROP TABLE IF EXISTS [dbo].[' + @tableName + ']
    DROP TABLE IF EXISTS [dbo].[' + @historyTableName + ']')
END

如果您只是在寻找一种删除所有表的方法,并且想要一些适用于临时表的东西,那么这是我使用的脚本:

DECLARE @temporalTableName varchar(max) = (SELECT TOP 1 [name] FROM sys.tables WHERE [temporal_type_desc] = 'SYSTEM_VERSIONED_TEMPORAL_TABLE')
WHILE @temporalTableName IS NOT NULL
BEGIN
    EXEC('ALTER TABLE [dbo].[' + @temporalTableName + '] SET (SYSTEM_VERSIONING = OFF)')
    SET @temporalTableName = (SELECT TOP 1 [name] FROM sys.tables WHERE [temporal_type_desc] = 'SYSTEM_VERSIONED_TEMPORAL_TABLE')
END
DECLARE @tableName varchar(max) = (SELECT TOP 1 [name] FROM sys.tables)
WHILE @tableName IS NOT NULL
BEGIN
    EXEC('DROP TABLE [dbo].[' + @tableName + ']')
    SET @tableName = (SELECT TOP 1 [name] FROM sys.tables)
END

【讨论】:

非常有用的见解,谢谢!在这种情况下,我不关心历史表的名称,所以更喜欢它是自动生成的。正如微软产品经常发生的那样,我发现自己很惊讶他们忽略了一些重要的基础知识。 @Frank T-SQL 的主要重点是存储和检索数据,而不是在不知道其名称的情况下删除和创建表。 我知道 T-SQL 的用途。我也知道微软在没有扩展 DROP TABLE 的情况下实现了临时表以很好地发挥作用,所以我只是尝试编写脚本来使用自动生成的历史表干净地删除临时表。 我的意思是 T-SQL 的主要用例不是删除表,不管是否引入了时间。模式绑定对象、外键等也会发生同样的情况。【参考方案3】:

我改编了 Aaron 的回答。这是最终的解决方案:

CREATE OR ALTER PROCEDURE [dbo].[DropTemporalTable]
  @t as nvarchar(max),
  @s as nvarchar(max) = 'dbo'
-- See https://***.com/questions/55522278/procedure-to-drop-system-versioned-temporal-tables/55524067#55524067
AS
BEGIN
-- TODO: check if src table is empty first
 DECLARE @sql nvarchar(max) = N'';

SELECT @sql += N'ALTER TABLE ' + src + N' SET (SYSTEM_VERSIONING = OFF);
DROP TABLE ' + src  + N';
DROP TABLE ' + hist + N';
'
FROM
(
  SELECT src = QUOTENAME(SCHEMA_NAME(t.schema_id))
             + N'.' + QUOTENAME(t.name),
        hist = QUOTENAME(SCHEMA_NAME(h.schema_id))
             + N'.' + QUOTENAME(h.name)
  FROM sys.tables AS t
  INNER JOIN sys.tables AS h
  ON t.history_table_id = h.[object_id]
  WHERE t.temporal_type = 2 AND t.name=@t AND t.schema_id=SCHEMA_ID(@s)
) AS x;

PRINT @sql;
--EXEC sys.sp_executesql @sql;
END

【讨论】:

公平地说,您的问题是您正在派生一个表名列表,因为您不知道它们,并且想要对该集中的所有表执行删除。制作一个将单个表名作为参数的存储过程意味着(a)您必须首先知道表名,并且(b)现在您必须编写代码来为集合中的每个表名调用存储过程。 这也假设除了调用者的默认架构之外,您永远不会有任何架构中的表。 我至少在一定程度上修复了架构问题,使其成为默认为“dbo”的可选参数。抱歉,如果我的提示不清楚:我的意思是我不知道 history 表的名称,因为它是自动生成的。非常感谢您的帮助。【参考方案4】:

/****** 对象:表 [Intake].[MER_SF_Asset_Temporal] 脚本日期:04/05/2021 12:33:57 PM ******/

ALTER TABLE [Intake].[MER_SF_Asset_Temporal] SET ( SYSTEM_VERSIONING = OFF  )
GO

/****** 对象:表 [Intake].[MER_SF_Asset_Temporal] 脚本日期:04/05/2021 12:33:57 PM ******/

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Intake].[MER_SF_Asset_Temporal]') AND type in (N'U'))
DROP TABLE [Intake].[MER_SF_Asset_Temporal]
GO

/****** 对象:表 [Intake].[MERSFAssetTemporalHistory] ​​脚本日期:04/05/2021 12:33:58 PM ******/

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Intake].[MERSFAssetTemporalHistory]') AND type in (N'U'))
DROP TABLE [Intake].[MERSFAssetTemporalHistory]
GO

【讨论】:

以上是关于删除系统版本化时态表的过程的主要内容,如果未能解决你的问题,请参考以下文章

在 Entity Framework Core 中查询系统版本时态表中的数据

1.19.5.3.时态表关联一张版本表关联一张普通表时态表声明版本表声明版本视图声明普通表时态表函数等

记一次SVN误删除操作和Tomcat版本与操作系统不兼容 问题分析及解决的过程

FlinkSQL--时态表或版本表(Temporal Tables 或 Versioned Tables)

视图中的系统版本化(临时)表

Linux CentOS系统安装 node 版本管理工具 nvm