【问题标题】:Run the same query against multiple tables without dynamic sql在没有动态 sql 的情况下对多个表运行相同的查询
【发布时间】:2015-11-19 12:33:12
【问题描述】:

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

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

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

【问题讨论】:

  • 我认为在调用 fetch 之前不会设置 @@fetch_status,从而导致 while 条件在第一次传递时不成立。这是您程序的准确副本吗?
  • @ShannonSeverance 我已经删除了许多游标,但这是我写过的第一个游标,所以我不声称自己是专家,但这确实像写的那样工作。有一个奇怪的行为,它不能运行两次;第二次它什么也不做。为了解决这个问题,我必须关闭文件并重新打开它是 SSMS,但这超出了我的问题范围。
  • 我问是因为它不适合我。错误行为与我预期的不同。它第二次不起作用,因为@@fetch_status 仍然是该连接上最后一次提取的-1。那是完成先前运行的获取。请参阅msdn.microsoft.com/en-us/library/ms187308.aspx。为避免重复提取,我通常会使用declare ... open ... while (1=1) begin fetch ... if @@fetch_status&lt;&gt; 0 break /* else */ &lt;do work&gt; end close .... deallocate ...
  • @ShannonSeverance 谢谢,我希望我在调试这个东西的时候就知道了。我不得不关闭并打开 .sql 文件十几次或更多次。我添加了您建议的更改。原始代码仍然为我运行(一次),没有任何问题。如果有什么不同,我会使用 2012 年。

标签: sql sql-server tsql dynamic-sql


【解决方案1】:

我不认为在这里使用 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)

【讨论】:

    【解决方案2】:

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

    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 
    

    【讨论】:

    • 经过速度测试后,我没有最终使用这种方法,但我将此答案标记为正确,因为它最接近回答我最初的问题“是否有另一种方法可以在不使用光标和动态的情况下执行此操作查询?”感谢其他所有人,他们对我需要了解多少 SQL 提供了深刻的见解。
    【解决方案3】:

    执行以下操作可能会获得更好的性能:

    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 server 对象名称表 (SYSNAME) 使用适当的数据类型。
    2. 使用sp_executesql 而不是EXEC(@Sql)
    3. 将参数作为日期传递,不要将其转换为字符串,以便 sql server 可以使用在该列上定义的索引。
    4. 使用QUOTENAME() 函数将方括号放在表名周围,以防任何表名是sql server 中的保留关键字,因此查询不会出错。
    5. 使您的光标本地和 fast_forward 光标的默认设置是全局的,您不需要对吗?

    【讨论】:

    • 感谢您的编码建议,我一定会使用它。表名都回溯到 FoxPro,所以它们中没有空格,但 QUOTENAME 是一个很好的技巧,比我的正常 '...[' + @fieldNameVar + ']...' 更干净@
    猜你喜欢
    • 1970-01-01
    • 2021-08-28
    • 1970-01-01
    • 1970-01-01
    • 2020-08-09
    • 1970-01-01
    • 1970-01-01
    • 2015-10-12
    • 2014-07-03
    相关资源
    最近更新 更多