结合命令行和T-SQL - SQL Server

时间:2016-01-13 18:35:39

标签: sql-server sqlcmd

我正在尝试通过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,它仍然无法删除表。但是,当我在批处理文件中对其进行硬编码时,它的工作方式非常好。有什么想法吗?

8 个答案:

答案 0 :(得分:3)

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

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

答案 1 :(得分:2)

我不能说你的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;

答案 2 :(得分: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循环会将其“set”(for /?中使用的术语)视为由空格分隔的两个项目。如果您当前的目录是X:\Main Folder\Data\,它仍然会工作,因为它将最后一个目录视为.dat文件的相对路径,然后set在最后一次传递时仍然是正确的值。

答案 3 :(得分:2)

不确定这是否适合您,但由于您使用的是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

我已经检查过,它似乎正在工作,具体取决于文件大小。

答案 4 :(得分:2)

你为什么要“down”到命令行? (默认情况下有sys.tables WHERE name LIKE ...被禁用的原因)

你能不能简单地遍历你的表(SELECT INTO)然后

  • 创建表格的影子副本(BULK INSERT
  • TRY..CATCH从(预期)文件进入shadow-table(在DELETE中处理文件不存在,或者为空或已损坏的情况..
  • 如果影子表中有数据,则DELETE实际表记录并移动数据
  • 如果影子表中没有数据,那么DROP实际的表记录(如果你假设丢失或空的bcp文件,则保留它们意味着它将在稍后到达并且你仍然坚持当前现在的版本)
  • 再次
  • makeRotationAxis()影子表

答案 5 :(得分:1)

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

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

答案 6 :(得分:1)

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

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

答案 7 :(得分:1)

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

  • STRING TEMPLATES - 构建字符串时,我喜欢构建一个“模板”,然后在字符串中替换。这是确保你有正确数量的引号,括号等的好方法。我在这里做了两次,一次构建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