如何将存储过程查询结果输出到文本文件

How to output stored procedure query results to a text file

我有一个简单的查询,可以从特定日期提取具有特定名称和状态的所有记录的 ID。就输出而言,我只需要 ID,最好是用逗号分隔的单行文本。目前我只能在 .txt 文档的新行中获取每条记录,但这也没关系。

这是我提取所需数据的查询(在 SSMS 中使用 SAP B1 数据):

SELECT T0.[DocNum] 
FROM OINV T0 
WHERE T0.[DocDate] = CAST(CURRENT_TIMESTAMP AS DATE) 
  AND T0.CardName LIKE '%Name%' 
ORDER BY T0.CardName

我已将此创建为存储过程,我希望按计划 运行。我的问题是,当查询结果包含多个记录时,我无法弄清楚如何让这个存储过程将结果输出到.txt 文件。

经过一番研究后,我找到了一种方法,可以根据需要使用 SQLCMD 并将 :OUT C:\file.txt 放在查询之前,将查询输出到 .txt 文件,但是此方法不能用作存储过程。

经过更多研究后,我找到了一种从存储过程本身调用 xp_cmdshell 以将输出写入文件的方法,但如果有多个结果,这就是出错的地方。

这是存储过程:

SET NOCOUNT ON;

DECLARE @timeStamp varchar(200) =  convert(varchar,getDate(), 112 )+'_'+ Replace(convert(varchar,getDate(), 114 ),':','') 
DECLARE @var NVARCHAR(MAX) = ''
DECLARE @fn varchar(500) = 'C:\file.txt';
DECLARE @cmd varchar(8000)

SET @var = (SELECT T0.[DocNum] FROM OINV T0 WHERE T0.[DocDate] = CAST(CURRENT_TIMESTAMP AS DATE) AND T0.CardName like '%Name%' ORDER BY T0.CardName)
SET @cmd = concat('echo ', @var, ' > "', @fn, '"');
    
EXEC xp_cmdshell @cmd;

这是我在查询 returns 多个记录时得到的错误:

Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.

我还阅读了有关使用 bcp 的信息,但我似乎无法让它与我目前正在处理的方式(通过存储过程)一起工作。我不会 post 在这里尝试这些尝试的代码,因为我确定它偏离了基础。

我看到的一些答案告诉我使用“TOP 1”或“WHERE”来确保只返回一条记录,但我需要存储此查询的所有结果,因此这些不是有效的解决方法对我来说。

谁能告诉我将存储过程查询结果(多个)输出到文本文件的最简单方法是什么?


编辑-

感谢大家迄今为止提供的所有意见和建议。我将列出我尝试过的所有其他解决方案以及我遇到的问题-

我尝试通过以下实现使用 BCP,但都失败并出现错误

Procedure expects parameter 'command_string' of type 'varchar'

实施 1-

DECLARE @strbcpcmd NVARCHAR(max)
SET @strbcpcmd = 'bcp  "SELECT T0.[DocNum] FROM OINV T0 WHERE T0.[DocDate] = CAST(CURRENT_TIMESTAMP AS DATE) AND T0.CardName like ''%Name%''" queryout "C:\test.txt" -w -C OEM -t"$" -T -S'+@@servername    
EXEC master..xp_cmdshell @strbcpcmd

实施 2-

DECLARE @bcp nvarchar(max)
DECLARE @timeStamp varchar(200) =  convert(varchar,getDate(), 112 )+'_'+ Replace(convert(varchar,getDate(), 114 ),':','') 

SET @bcp='bcp "SELECT T0.[DocNum] FROM OINV T0 WHERE T0.[DocDate] = CAST(CURRENT_TIMESTAMP AS DATE) AND T0.CardName like ''%Name%''" queryout "C:\Test.txt" -c -t, -ServerName01 -T'

EXEC master.dbo.xp_cmdshell @bcp, no_output /*optional, remove no_output for debugging */

我想在这里指出,根据 运行 时间,文件名最好是动态的,这就是为什么我想尽可能保留 CURRENT_TIMESTAMP AS DATE 的原因。

编辑 X2 - 我能够通过像这样 DECLARE @bcp nvarchar(1000) 那样更改变量类型来让 bcp “工作”,但看起来查询本身现在失败了(尽管它肯定在这之外工作)-

Error = [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Invalid object name 'OINV'.

&

Error = [Microsoft][ODBC Driver 13 for SQL Server]Unable to resolve column level collations

这让我有点困惑,因为我不确定我在哪里将查询与输出指令分开。我尝试过的方法是创建包含我的查询的存储过程,然后基于该存储过程创建一个作业。我添加了一个类型为“操作系统(CmdExec)”的附加步骤,其中 :OUT C:\SQLOut\Test.txt 作为命令。

我还尝试一步添加完整查询以及 :OUT C:\SQLOut\Test.txt 命令。这会导致以下错误 -

Executed as user: NT Service\SQLSERVERAGENT. The process could not be created for step 1 of job 0x2A9232A6AF3E4F4B8C53CCF50419245D (reason: The system cannot find the file specified). The step failed.

我假设这个错误是由于文件不存在造成的。我希望通过此过程创建的文件在文件名本身中带有时间戳,但我不确定此方法是否可行。

我没有这方面的经验,但我愿意学习。不幸的是,尽管看起来我们没有在任何地方安装 BIDS,所以我必须在我们的环境中设置所有这些,以便我继续沿着这条路走下去。不过,我猜这将远远超过我想单独参与解决这个问题的问题。

这将是完美的,但是我们运行宁SQL Server 2016,所以只有一个版本没有这个功能。

我想我可能会让这个工作,尽管我的主要犹豫与日程安排有关。我想我可以使用 Task Scheduler,但这种方法似乎草率。


最终编辑 -

能够使用 bcp 使其正常工作。 bcp 方法绝对是可行的方法,因为它只需要对我的原始代码进行最少的修改即可开始工作。我的主要问题是语法。

这是我的最终(工作)代码实现 -

DECLARE @bcp nvarchar(1000)
DECLARE @timeStamp varchar(200) =  convert(varchar,getDate(), 112 )+'_'+ Replace(convert(varchar,getDate(), 114 ),':','') 

SET @bcp='bcp "SELECT T0.[DocNum] FROM MyDB.DBO.OINV T0 WHERE T0.[DocDate] = CAST(CURRENT_TIMESTAMP AS DATE) AND T0.CardName like ''%Name%''" queryout "C:\SQLOUT\Name' + @timeStamp + '.txt" -c -t, -Sservername -T'

EXEC master.dbo.xp_cmdshell @bcp--, no_output /*optional, remove no_output for debugging */

首先,让我谈谈您遇到的错误。

@var是单一变量,类型单一nvarchar(max)。您的 select 语句 returns 结果集...行和列。每个元素,结果集中某些行和某些列的每个交集,都是具有自己数据类型的单个值。您的查询 returns 一列,但多行。所以你有多个值。您不能为一个变量分配多个值,就像 excel 中的单个单元格也不能是 excel.

中的整列一样

就从 sql 中获取数据到文本文件而言,纯基于 TSQL 的解决方案并不适合这种情况。您需要某种“客户端应用程序”运行 SQL,然后与数据库外部的世界进行交互。

既然你说你想 运行 按计划执行此操作,你可能想使用 SQL Agent 作为你的“客户端应用程序”,因为它也是一个调度程序。

SQL 代理有一个名为“操作系统 (CmdExec)”的作业步骤类型。您可以将此作业步骤类型用于 运行 sqlcmd (have a read through this) 并让 sqlcmd 按照您在问题。

您还可以使用 SQL 服务器集成服务包。如果您已经在某个地方有一个集成服务目录,或者如果您希望进行更多导出,那么这是一个特别好的主意。否则,CmdExec 解决方案就可以了。

如果您选择使用带有 CmdExec 步骤的 SQL 代理,您可能需要设置一个具有有限权限的代理帐户来执行该作业。 This should get you started.

不要使用 xp_cmdshell。事实上,usual advice 是完全禁用它。

关于您遇到的错误...@var 只需要一个值,但您正试图用返回多行的查询来填充它。

根据您使用的 SQL 版本 运行,您可以使用 STRING_AGG(我相信在 SQL Server 2017 中引入)将结果填充到单个值,带有定义的分隔符(在本例中为管道,但您可以将其更改为任何您想要的)。

SET @var = (SELECT STRING_AGG(T0.[DocNum], '|') as DocNum FROM OINV T0 WHERE T0.[DocDate] = CAST(CURRENT_TIMESTAMP AS DATE) AND T0.CardName like '%Name%' ORDER BY T0.CardName)

如果你的版本不支持STRING_AGG,你可以尝试STUFF它:

DECLARE @var NVARCHAR(MAX) = ''
SET @var = 
STUFF(
    (
    SELECT DISTINCT  ', ' + T0.[DocNum]
    FROM OINV T0 
    WHERE T0.[DocDate] = CAST(CURRENT_TIMESTAMP AS DATE) 
    AND T0.CardName like '%Name%' 
    FOR XML PATH(''), TYPE
    ).value('.', 'NVARCHAR(MAX)'),1,1,''
)

抱歉,我无法将其导出到文本文件。如果是我,我会使用集成服务 (SSIS) 来做到这一点。

使用 BCP 非常简单,无需尝试 存储 查询结果。

您可能在存储过程中使用的基本 BCP 语句如下所示:

declare @bcp nvarchar(max)

set @bcp='bcp "SELECT T0.[DocNum] FROM DBNAME.DBO.OINV T0 WHERE T0.[DocDate] = CAST(CURRENT_TIMESTAMP AS DATE) AND T0.CardName like ''%Name%'' ORDER BY T0.CardName" queryout "c:\file.txt" -c -t, -Sservername -T'

exec master.dbo.xp_cmdshell @bcp, no_output /*optional, remove no_output for debugging */

在上面的BCP语法中,将servername替换为SQL Server的DNS名称或IP; -T 假定可信连接,您也可以使用 -U 和 -P 指定用户名和密码。

您可以直接在程序中使用它,然后 运行 使用 xp_cmdshell,或者 运行 使用 CmdExec[=26= 直接从代理作业中使用它]选项。

为了调试,您可以 运行 直接在 SSMS 查询中 window。