结合命令行和 T-SQL - SQL 服务器

Combine command line and T-SQL - SQL Server

我正在尝试通过 T-SQL 在 SQL Server 2008 上执行一些 sqlcmd。我的代码中有一部分正在检查数据文件大小以及是否该数据文件大小不等于 0,然后开始删除特定的 table 这样我就可以在新数据中进行 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,它仍然会删除 table,但它仍然不起作用。但是,当我在批处理文件中对其进行硬编码时,它工作得很好。有什么想法吗?

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

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

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

首先您需要在您的 SQL 服务器实例上启用 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;

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

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

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

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 /? 中使用的术语)视为由 space 分隔的两项。如果您当前的目录是 X:\Main Folder\Data\ 它仍然可以工作,因为它将最后一个目录视为 .dat 文件的相对路径,然后 set 在最后一次通过时仍然是正确的值。

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

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

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

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

你为什么要 'down' 到命令行? (默认禁用xp_cmdshell是有原因的)

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

  • 创建一个 shadow-copy 的 table (SELECT INTO)
  • BULK INSERT 从(预期的)文件到 shadow-table(在 TRY..CATCH 中处理文件不存在、为空或损坏的情况,..
  • 如果shadow-table中有数据,则DELETE实际table记录并将数据移动到
  • 如果影子中没有数据 table 那么 DELETE 实际的 table 记录(或者如果您假设丢失或空的 bcp 文件意味着它将到达,则将它们保留在稍后,您暂时只能使用当前版本)
  • DROP又是阴影table

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

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

  • TRUNCATE - 虽然不是您的问题的一部分,但您可能希望使用 TRUNCATE 而不是 DELETE FROM。如果您的 table 中有一百万行,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