运行 针对没有动态的多个表的相同查询 sql

Run the same query against multiple tables without dynamic sql

我支持第三方软件包的 SQL 数据库。他们有很多他们称之为 "Shadow Tables" 的东西,实际上只是审计表。这一切都很好,但他们的系统不会清理这些表,所以这取决于我。他们还会在每次升级时添加新的 "Shadow Tables",恕不另行通知。我们清除表的旧方法是使用一长串 DELETE FROM 语句,但此列表变得非常长且难以维护。

为了尝试使清除过程更易于维护并自动捕获新的 "Shadow Tables" 我编写了以下存储过程。存储过程有效,但我更愿意找出一种不使用游标和动态查询的方法,因为这将是 运行 每天在很多不同的表上。有没有不使用游标和动态查询的替代方法?

DECLARE @workingTable varchar(128);
DECLARE @sqlText varchar(250);
DECLARE @CheckDate DATETIME = DATEADD(yy, -2, GETDATE());

DECLARE curKey SCROLL CURSOR FOR  
SELECT name AS TableName
FROM dataTEST.sys.tables
WHERE (name like '%[_]h' OR name like '%[_]dh')
ORDER BY name

OPEN curKey
WHILE @@fetch_status = 0
BEGIN
    FETCH NEXT FROM curKey INTO @workingTable
    SET @sqlText = 'DELETE FROM DataTEST.dbo.' + @workingTable + ' WHERE LAST_MOD < ''' + CONVERT(CHAR(10), @CheckDate, 101) + ''';'
    --PRINT @sqlText
    EXEC (@sqlText)
END
CLOSE curKey
DEALLOCATE curKey

我不认为在这里使用 cursordynamic query 是个坏主意

一种方法是附加删除查询并在生成所有删除查询后在最后执行它。

顺便说一句,游标只是用于构建动态查询,所以没什么大不了的

DECLARE @workingTable varchar(128);
DECLARE @sqlText nvarchar(max)='';
DECLARE @CheckDate DATETIME = DATEADD(yy, -2, GETDATE());

DECLARE curKey SCROLL CURSOR FOR  
SELECT name AS TableName
FROM dataTEST.sys.tables
WHERE (name like '%[_]h' OR name like '%[_]dh')
ORDER BY name

OPEN curKey
WHILE @@fetch_status = 0
BEGIN
    FETCH NEXT FROM curKey INTO @workingTable
    SET @sqlText += 'DELETE FROM DataTEST.dbo.' + @workingTable + ' WHERE LAST_MOD < ''' + CONVERT(CHAR(10), @CheckDate, 101) + ''';'
   
END
CLOSE curKey
DEALLOCATE curKey

--PRINT @sqlText
 EXEC (@sqlText)

您可以通过执行以下操作获得更好的性能:

DECLARE @workingTable SYSNAME;
DECLARE @sqlText nvarchar(MAX);
DECLARE @CheckDate DATETIME = DATEADD(YEAR, -2, GETDATE());

DECLARE curKey CURSOR LOCAL FAST_FORWARD FOR  
SELECT name AS TableName
FROM dataTEST.sys.tables
WHERE (name like '%[_]h' OR name like '%[_]dh')
ORDER BY name

OPEN curKey
WHILE @@fetch_status = 0
BEGIN
    FETCH NEXT FROM curKey INTO @workingTable
    SET @sqlText = 'DELETE FROM DataTEST.dbo.' + QUOTENAME(@workingTable) 
                  + ' WHERE LAST_MOD < @CheckDate'

     Exec sp_executesql @sqlText
                       ,N'@CheckDate DATETIME'
                       ,@CheckDate 
END
CLOSE curKey
DEALLOCATE curKey

改进:

  1. 为 sql 服务器对象名称 tables (SYSNAME) 使用适当的数据类型。
  2. 使用sp_executesql代替EXEC(@Sql)
  3. 将参数作为日期传递,不要将其转换为字符串,以便 sql 服务器可以使用在该列上定义的索引。
  4. 使用 QUOTENAME() 函数将 table 名称括在方括号中,以防任何 table 名称是 sql 服务器中的保留关键字,因此查询不会出错。
  5. 将光标设置为本地光标,fast_forward 光标默认设置为全局光标,您不需要那个,对吗?

当您提前不知道 table 名称时,我不知道如何摆脱动态 SQL。 SQL 服务器有一项功能,您可以在 select 语句中为每一行返回一次变量赋值。这可用于消除游标并将所有 delete 语句的一个字符串传递给 SQL 服务器以执行

DECLARE @sqlText nvarchar(MAX) = ''; -- initialize because NULL + 'x' is NULL
DECLARE @CheckDate DATETIME = DATEADD(YEAR, -2, GETDATE());

SELECT @sqlText = @SqlText + 'DELETE FROM dataTEST.dbo.' + QUOTENAME(name) 
    + ' WHERE LAST_MOD < @CheckDate ; '
FROM dataTEST.sys.tables
WHERE (name like '%[_]h' OR name like '%[_]dh')
ORDER BY name

IF @@ROWCOUNT > 0 
    EXEC sp_executesql @sqlText
        , N'@CheckDate DATETIME'
        , @CheckDate