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

Posted

技术标签:

【中文标题】如何将存储过程查询结果输出到文本文件【英文标题】:How to output stored procedure query results to a text file 【发布时间】:2021-08-30 19:26:55 【问题描述】:

我有一个简单的查询,它从特定日期提取具有特定名称和状态的所有记录的 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;

当该查询返回多条记录时,我得到的错误如下:

子查询返回超过 1 个值。当子查询跟随 =、!=、、>= 或子查询用作表达式时,这是不允许的。

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

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

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


编辑 -

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

使用 BCP

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

过程需要“varchar”类型的参数“command_string”

实施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 “工作”,但看起来查询本身现在失败了(尽管它肯定在此之外工作)-

错误 = [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]无效 对象名称“OINV”。

&

错误 = [Microsoft][ODBC Driver 13 for SQL Server]无法解决 列级排序规则

使用 SQL 作业代理

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

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

以用户身份执行:NT Service\SQLSERVERAGENT。该过程无法 为作业 0x2A9232A6AF3E4F4B8C53CCF50419245D 的第 1 步创建(原因: 该系统找不到指定的文件)。步骤失败。

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

使用 SSIS

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

使用 STRING_AGG

这将是完美的,但我们正在运行 SQL Server 2016,因此只有一个版本才能拥有此功能。

使用 PowerShell

我想我可能会完成这项工作,尽管我主要的犹豫与日程安排有关。我想我可以使用任务计划程序,但是这种方法似乎很草率。


最终编辑 -

能够使用 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 */

【问题讨论】:

bcp 是你的答案。 Powershell 作业可能是更好的解决方案,请查看Invoke-SqlCmd 可能值得分享您当前的 BCP 尝试,因为使用 BCP 非常简单,我不明白为什么您的简单示例会出现问题。 @Brady 在您的 BCP 命令中,它会与您的 SQL Server 建立单独的连接,这会将您置于 default 数据库中;你的命令应该指定databasename.schemaname.tablename 【参考方案1】:

使用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 选项直接从代理作业运行它。

对于调试,您可以直接在 SSMS 查询窗口中运行它。

【讨论】:

感谢您逐步了解 bcp Stu,但我认为我的操作方式缺少一些非常基本的内容,因为我不断收到错误“程序需要参数'command_string ' 类型为 'varchar'。”。我编辑了 OP,添加了一些关于我在这里卡住的附加信息。 @Brady - 您不能将 (max) 用于 BCP 命令 - 尝试使用合适的长度,例如 varchar(200) 并重试 @Brady 还请注意,根据您在编辑中的评论,您应该指定完整的 database.schema.table,我在上面编辑了我的答案。 太棒了!在你的帮助下,我能够让它工作。我将在 OP 中发布我的最终 bcp 实现。非常感谢斯图。【参考方案2】:

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

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

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

既然您说您希望按计划运行它,您可能希望将 SQL 代理用作您的“客户端应用程序”,因为它也是一个计划程序。

SQL 代理具有称为“操作系统 (CmdExec)”的作业步骤类型。您可以使用此作业步骤类型运行 sqlcmd (have a read through this),并让 sqlcmd 以您在问题中提到的方式将查询结果输出到文本文件。

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

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

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

【讨论】:

感谢您解释我使用的方法失败的原因以及替代解决方案。我编辑了 OP 以解释我在哪里遇到了 BCP,并详细说明了为什么我不能使用 SSIS。 在我们的数据库中 xp_cmdshell 被禁用,因此我们被告知通过 CMD 提示符使用 BCP 命令或将其作为 Windows 中的批处理文件。我使用以下 bcp 命令使其在没有 xp_cmdshell bcp "exec [dbo].[sp_testl]" queryout "c:\camelout\testreadable.xml" -S serverIP,port -T -d myDBname -C 1252 - 的情况下工作c -x 我是直接在windows CMD里给的【参考方案3】:

关于您遇到的错误...@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) 来做到这一点。

【讨论】:

stuff 与字符串聚合无关,在这种情况下,它只是删除了前缀逗号分隔符。 谢谢斯文。我认为 STRING_AGG 在这里绝对是完美的,这正是我所需要的,但不幸的是我们正在运行 SQL Server 2016。

以上是关于如何将存储过程查询结果输出到文本文件的主要内容,如果未能解决你的问题,请参考以下文章

Kernprof(line_profiler):如何将结果输出为文本而不是二进制文件

Eclipse MyEclipse Scala IDEA for Eclipse里如何将控制台console输出的过程记录全程保存到指定的文本文件(图文详解)

如何使用python3将输入数据存储到文本文件中并打印数据输出?

将 Delphi BDE Paradox *.db 查询结果导出到文本文件?

将 top 的结果输出到文件中

如何让 refcursor 结果/输出显示为文本?