【问题标题】:Combine command line and T-SQL - SQL Server结合命令行和 T-SQL - SQL Server
【发布时间】:2016-01-13 18:35:39
【问题描述】:

我正在尝试通过 SQL Server 2008 上的 T-SQL 执行一些 sqlcmd。我在代码的一部分中检查数据文件大小,如果该数据文件大小不等于 0,则开始删除特定的表,这样我就可以在新数据中进行 BCP。

下面是我没有被执行的代码:

SET @myVariable = '
SETLOCAL 
FOR %%R IN (X:\Main Folder\Data\'+@databaseName+'_'+@tableName+'.dat) DO SET size=%%~zR 
IF %size% NEQ 0 (
        SQLCMD -E -S my-server-name -Q "DELETE FROM '+@databaseName+'.'+@schemaName+'.'+@tableName+';" >> X:\Main Folder\Log\Log.txt 
)'

EXEC master..xp_cmdshell @myVariable

由于某种原因,当我执行我的存储过程时,上面的代码似乎被跳过了,因为它没有返回任何错误消息。

编辑:重新调整间距后,我的代码@myVariable 现在开始执行。但是,即使数据文件大小 = 0,它仍然会删除表,但它仍然不起作用。但是,当我在批处理文件中对其进行硬编码时,它工作得非常好。有什么想法吗?

【问题讨论】:

  • 为什么不向数据库中添加一个存储过程来执行检查、删除或截断。然后从命令行执行这个?
  • 引用文件名?在for 和重定向的输出中。
  • 根据您的配置和操作系统版本,在 IF 之后缺少 /i。试试 IF /i。

标签: sql-server sqlcmd


【解决方案1】:

您需要在for 循环中使用单个%,因为您没有在批处理文件中执行代码(需要%%),请参阅此post 以获得进一步说明。所以你的 for 循环应该是:

FOR %R IN (X:\Main Folder\Data\'+@databaseName+'_'+@tableName+'.dat) DO SET size=%~zR 

【讨论】:

    【解决方案2】:

    我认为问题在于您没有在文件名周围使用引号。该顶级目录中有一个空间。

    Jaco 的回答看起来是正确的,我确信这是问题的一部分。为了安全起见,您可能应该初始化 size

    SET @myVariable = '
    SETLOCAL
    SET size=0
    FOR %R IN ("X:\Main Folder\Data\'+@databaseName+'_'+@tableName+'.dat") DO SET size=%~zR 
    IF %size% NEQ 0 (
            SQLCMD -E -S my-server-name -Q "DELETE FROM '+@databaseName+'.'+@schemaName+'.'+@tableName+';" >> "X:\Main Folder\Log\Log.txt" 
    )'
    
    EXEC master..xp_cmdshell @myVariable
    

    如果没有引号,for 循环会将其“集合”(for /? 中使用的术语)视为由空格分隔的两个项目。如果您的当前目录是X:\Main Folder\Data\,它仍然可以工作,因为它将最后一个视为.dat 文件的相对路径,然后sets 仍然是最后一次传递的正确值。

    【讨论】:

    • 我已经添加了引号和双单引号,并删除了第二个百分比。还是没有运气
    • 您的环境中是否已有size 变量?您是否确认它实际上是在循环并获取价值?
    • 另一个想法:有没有可能你没有启用命令扩展?我相信这可能会导致它在if ... neq 上失败,然后无条件地运行删除操作。
    • 所以我想出了另一种无需检查数据文件即可工作的方法。我所做的是使用 SQLCMD -E -S ServerA - Q "IF(SELECT COUNT(*) FROM tableName) > 0 BEGIN DELETE FROM ServerB.TableName END"
    【解决方案3】:

    你为什么要“下”到命令行呢? (xp_cmdshell默认禁用是有原因的)

    你能不能简单地遍历你的表 (sys.tables WHERE name LIKE ...) 然后

    • 创建表的卷影副本 (SELECT INTO)
    • BULK INSERT 从(预期的)文件到影子表中(在 TRY..CATCH 中处理文件不存在、为空或损坏的情况,..
    • 如果影子表中有数据,则DELETE实际表记录并将数据移过来
    • 如果影子表中没有数据,则 DELETE 实际表记录(或者如果您认为丢失或为空的 bcp 文件意味着它将稍后到达,并且您坚持使用当前版本现在)
    • DROP又是影子桌

    【讨论】:

      【解决方案4】:

      不确定这是否适合您,但由于您使用的是 SQL 2008,因此您应该能够改用 powershell 命令:

      DECLARE @File varchar(100), @outputFile varchar(100)
      DECLARE @cmd varchar(1000)
      SELECT @File = 'path_to_file'
      SELECT @outputFile = 'path_to_output_file'
      
      SELECT @cmd = 'powershell.exe -command "if((Get-Item '''+@File+''').length -gt 0) {&sqlcmd.exe -E -S SERVERNAME -Q ''SELECT name FROM master.sys.databases ;'' -o '+@outputFile+'}"'
      
      
      SELECT @cmd
      
      exec master..xp_cmdshell @cmd
      

      我已经检查过了,它似乎根据文件大小工作。

      【讨论】:

        【解决方案5】:

        我不能对你的 DOS 命令说话。但是,我可以建议使用 Ole Automation Procedures 来获取文件大小。这样您就不必依赖运行批处理命令。

        首先你需要在你的 SQL Server 实例上启用Ole Automation Procedures,如下:

        sp_configure 'show advanced options', 1;
        GO
        RECONFIGURE;
        GO
        sp_configure 'Ole Automation Procedures', 1;
        GO
        RECONFIGURE;
        GO
        

        您只需执行一次。


        接下来是一个获取文件大小的脚本。该示例假定有一个名为C:\Temp\testfile.txt 的文件。如果文件存在,脚本选择大小,如果不存在,则选择 0。你可以以这个脚本为例,根据大小做你想做的事。

        这里是:

        DECLARE @hr INT;
        DECLARE @size INT;
        DECLARE @obj_file INT;
        DECLARE @obj_file_system INT;
        DECLARE @file_name VARCHAR(100)='C:\Temp\testfile.txt';
        
        -- Create a FileSystemObject. Create this once for all subsequent file manipulation. Don't forget to destroy this object once you're done with file manipulation (cf cleanup)
        EXEC @hr = sp_OACreate 'Scripting.FileSystemObject', @obj_file_system OUT;
        IF @hr<>0 GOTO __cleanup;
        
        -- Get a handle for the file. Don't forget to release the handle for each file you get a handle for (see cleanup). The return will be different from 0 if the file doesn't exist
        EXEC @hr = sp_OAMethod @obj_file_system, 'GetFile', @obj_file out, @file_name;
        IF @hr<>0 GOTO __print_file_size;
        
        -- Retrieve the file size.
        EXEC sp_OAGetProperty @obj_file, 'size', @size OUT;
        
        __print_file_size:
        SELECT ISNULL(@size,0) AS file_size;
        
        __cleanup:
        EXEC sp_OADestroy @obj_file_system;
        EXEC sp_OADestroy @obj_file;
        

        【讨论】:

          【解决方案6】:

          您在代码中使用了X:\。但是代码是在 SQL Server 的服务帐户下运行的。该帐户可能没有可用的x:

          我建议使用 UNC 而不是映射驱动器。此外,请确保您的服务在域帐户下运行,并且该域帐户对 UNC 具有所有必需的权限。

          【讨论】:

          • 我刚刚尝试使用 UNC 像 \\servername\X$\.... 但它不起作用。而且我认为这不是权限问题,因为我的所有其他 xp_cmdshell 命令都在执行。
          【解决方案7】:

          我意识到我可以使用这种方法检查表数而不是数据文件大小:

          SET @sqlCheck = 'SQLCMD -E -S ServerA -Q "IF (SELECT COUNT(*) FROM '+@databaseName+'.'+@schemaName+'.'+@tableName+') > 0 BEGIN DELETE FROM ServerB.'+@databaseName+'.'+@schemaName+'.'+@tableName+' END;"'
                          EXEC MASTER..xp_cmdshell @sqlcheck
          

          【讨论】:

          • 最好将此代码放在存储过程中,如我对您问题的评论中所述。如果您的代码接受任何类型的用户输入,那么您可能会面临 SQL 注入攻击。
          • 为什么要在删除前检查表是否为空?进行计数将需要一段时间,而且根本没有额外的好处。只需运行 DELETE 命令;无论您从多少条记录开始(零或数千),最终结果都是相同的:一个空表。
          • 另外,您正在创建一些动态 SQL 的命令行版本,然后使用 xp_cmshell 执行它?!?为什么不直接使用EXEC ('DELETE FROM ServerB.'+@databaseName+'.'+@schemaName+'.'+@tableName)
          • 我认为在不同的服务器上涉及两个表,这就是检查的原因。
          • @AznDevil92 仍然不确定为什么需要使用 cmdshell 来执行此操作。启用 cmdshell 会带来安全风险,并且您的动态 sql 会使您面临 SQL 注入攻击!如果您需要访问另一个 SQL Server,那么为什么不使用 OPENROWSET 或 Linked Server!
          【解决方案8】:

          您似乎已经知道数据库和表的名称,因此您可以使用以下内容,它基本上为您要查找的文件执行 DIR 并检查它是否为'0 bytes',如果是,则执行任何操作你要。注意事项:

          • 字符串模板 -- 构建字符串时,我喜欢构建一个“模板”,然后在字符串中进行替换。这是确保引号、括号等数量正确的好方法。我在这里做了两次,一次是构建 DIR 命令,然后再次构建 TRUNCATE 命令。

          • TRUNCATE -- 虽然不是您的问题的一部分,但您可能希望使用TRUNCATE 而不是DELETE FROM。如果您的表中有一百万行,DELETE FROM 可能需要 2 分钟才能运行,而 TRUNCATE 将始终需要 0 秒才能运行。

          你的答案:

          SET NOCOUNT ON;
          
          DECLARE @DatabaseName VARCHAR(50) = 'database1'
          DECLARE @TableName VARCHAR(50) = 'table1'
          
          DECLARE @PathTemplate VARCHAR(50) = 'dir c:\temp\{@DatabaseName}_{@TableName}.txt'
          
          SET @PathTemplate = REPLACE(@PathTemplate, '{@DatabaseName}', @DatabaseName);
          SET @PathTemplate = REPLACE(@PathTemplate, '{@TableName}', @TableName);
          
          DECLARE @FileNames AS TABLE (FileNames VARCHAR(100))
          
          INSERT @FileNames (FileNames)
          exec xp_cmdshell @PathTemplate
          
          IF EXISTS ( SELECT 1 FROM @FileNames WHERE FileNames LIKE '%0 bytes')
          BEGIN
              PRINT 'No Content/Missing File'
          END 
          ELSE BEGIN
          
              DECLARE @SqlExc VARCHAR(500) = 'TRUNCATE TABLE [{@DatabaseName}].[dbo].[{@TableName}]'
              SET @SqlExc = REPLACE(@SqlExc, '{@DatabaseName}', @DatabaseName);
              SET @SqlExc = REPLACE(@SqlExc, '{@TableName}', @TableName);
          
              PRINT @SqlExc
              -- sp_executesql @SqlExc  <-- Do this in production
          
          END
          

          【讨论】:

            猜你喜欢
            • 2011-01-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-02-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多