ivG*_*Geo 3 sql-server ssms cursors json
我有一个游标,它从一组表中生成一条 JSON 文本记录。光标一直在使 SSMS 崩溃。该脚本运行了一段时间,然后 SSMS 失败。下面是我编写的导致崩溃的代码。
DECLARE @ROW_ID int -- Here we create a variable that will contain the ID of each row.
DECLARE JSON_CURSOR CURSOR -- Here we prepare the cursor and give the select statement to iterate through
FOR
SELECT -- Our select statement (here you can do whatever work you wish)
ROW_NUMBER() OVER (ORDER BY NAME_2-1,NAME_2-2,FIELD_1-1,FIELD_1-2) AS ROWID
FROM
(
SELECT
FIELD_1-1
,FIELD_1-2
,NAME_1-1
,NAME_1-2
FROM
(
SELECT FIELD_1-1,FIELD_1-2,NAME,VALUE
FROM TABLE_1
WHERE NAME IN ('NAME_1-1','NAME_1-2')
) AS SRC
PIVOT
(
MAX(VALUE_1) FOR NAME IN ([FIELD_1-1],[FIELD_1-2])
) AS PVT
) AS T0
LEFT JOIN TABLE_2 AS [P] ON T0.NAME_1-2=P.NAME_2-2;
OPEN JSON_CURSOR -- This charges the results to memory
FETCH NEXT FROM JSON_CURSOR INTO @ROW_ID -- We fetch the first result
WHILE @@FETCH_STATUS = 0 --If the fetch went well then we go for it
BEGIN
SELECT * FROM
(
SELECT -- Our select statement (here you can do whatever work you wish)
FIELD_2-1
,FIELD_2-2
,FIELD_1-1
,FIELD_1-2
,T0.NAME_1-1
,ROW_NUMBER() OVER (ORDER BY FIELD_2-1,FIELD_2-2,FIELD_1-1,FIELD_1-2) AS ROWID
FROM
(
SELECT
FIELD_1-1
,FIELD_1-2
,NAME_1-1
,NAME_1-2
FROM
(
SELECT FIELD_1-1,FIELD_1-2,NAME,VALUE
FROM TABLE_1
WHERE NAME IN ('NAME_1-1','NAME_1-2')
) AS SRC
PIVOT
(
MAX(VALUE_1) FOR NAME IN ([FIELD_1-1],[FIELD_1-2])
) AS PVT
) AS T0
LEFT JOIN TABLE_2 AS [P] ON T0.NAME_1-2=P.NAME_2-2
) AS T1
WHERE ROWID = @ROW_ID -- In regards to our latest fetched ID
order by (FIELD_2-1,FIELD_2-2,FIELD_1-1,FIELD_1-2)
FOR JSON PATH, ROOT('FIELD_2-1');
FETCH NEXT FROM JSON_CURSOR INTO @ROW_ID -- Once the work is done we fetch the next result
END
-- We arrive here when @@FETCH_STATUS shows there are no more results to treat
CLOSE JSON_CURSOR
DEALLOCATE JSON_CURSOR -- CLOSE and DEALLOCATE remove the data from memory and clean up the process
Run Code Online (Sandbox Code Playgroud)
从 Windows 日志:
首先发生以下 .NET 运行时错误:
错误 7/20/2018 2:27:58 PM .NET 运行时 1026 无
应用程序:Ssms.exe 框架版本:v4.0.30319 描述:由于未处理的异常,进程被终止。异常信息:System.Windows.Forms.NativeWindow.CreateHandle(System.Windows.Forms.CreateParams) 处 System.Windows.Forms.Control.CreateHandle() 处 System.Windows.Forms.TextBoxBase.CreateHandle() 处的 System.ComponentModel.Win32Exception ) 在 System.Windows.Forms.Control.CreateControl(Boolean) 在 System.Windows.Forms.Control.CreateControl(Boolean) 在 System.Windows.Forms.Control.CreateControl() 在 System.Windows.Forms.Control.WmShowWindow( System.Windows.Forms.Message ByRef) at System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef) at System.Windows.Forms.ScrollableControl.WndProc(System.Windows.Forms.Message ByRef) at System.Windows.Forms.ContainerControl。
第二个应用程序错误:
错误 7/20/2018 下午 2:27:58 应用程序错误 1000 (100)
错误的应用程序名称:Ssms.exe,版本:2017.140.17277.0,时间戳:0x5b304116 错误的模块名称:KERNELBASE.dll,版本:10.0.14393.2189,时间戳:0x5abda7d6 异常代码:430x0x0620 错误代码:0x5b304120应用程序启动时间:0x01d4205466d2b650 错误应用程序路径:C:\Program Files (x86)\Microsoft SQL Server\140\Tools\Binn\ManagementStudio\Ssms.exe 错误模块路径:C:\WINDOWS\System32\KERSELBASE.dll 报告 ID: 03b3b0c6-0839-4562-a71f-f5b4fc0a3029 故障包全名: 故障包相关应用程序 ID:
此脚本的目的是以 JSON 格式输出单个条目,该条目将发布到云中的数据服务。同样在 SSMS 中,我将结果发送到网格。
这是我正在创建的存储过程的完整查询。
/****** Object: StoredProcedure [dbo].[sp_acQ-Zerion_POST_HTTP] Script Date: 6/15/2018 10:48:28 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
/* FILL IN WITH DB */
ALTER PROCEDURE [dbo].[sp_acQ-Zerion_POST_HTTP] --@ID varchar(50)
AS
/* define variables */
Declare @hr int;
Declare @Object as Int;
Declare @ResponseText as Varchar(8000);
Declare @src varchar(255), @desc varchar(255),@status int,@msg varchar(255);
-------------------------------------------------------------------------------------
/* Cursor Pt 1 */
-------------------------------------------------------------------------------------
DECLARE @ROW_ID int -- Here we create a variable that will contain the ID of each row.
DECLARE JSON_CURSOR CURSOR -- Here we prepare the cursor and give the select statement to iterate through
FOR
SELECT -- Our select statement (here you can do whatever work you wish)
ROW_NUMBER() OVER (ORDER BY NAME_2-1,NAME_2-2,FIELD_1-1,FIELD_1-2) AS ROWID
FROM
(
SELECT
FIELD_1-1
,FIELD_1-2
,NAME_1-1
,NAME_1-2
FROM
(
SELECT FIELD_1-1,FIELD_1-2,NAME,VALUE
FROM TABLE_1
WHERE NAME IN ('NAME_1-1','NAME_1-2')
) AS SRC
PIVOT
(
MAX(VALUE_1) FOR NAME IN ([FIELD_1-1],[FIELD_1-2])
) AS PVT
) AS T0
LEFT JOIN TABLE_2 AS [P] ON T0.NAME_1-2=P.NAME_2-2
WHERE NAME_1-1 IN ('True','False');
OPEN JSON_CURSOR -- This charges the results to memory
FETCH NEXT FROM JSON_CURSOR INTO @ROW_ID -- We fetch the first result
WHILE @@FETCH_STATUS = 0 --If the fetch went well then we go for it
BEGIN
-------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------
Declare @Records as Varchar(8000)=
(
-------------------------------------------------------------------------------------
/* Cursor Pt 2 */
-------------------------------------------------------------------------------------
SELECT * FROM
(
SELECT -- Our select statement (here you can do whatever work you wish)
FIELD_2-1
,FIELD_2-2
,FIELD_1-1
,FIELD_1-2
,T0.NAME_1-1
,ROW_NUMBER() OVER (ORDER BY FIELD_2-1,FIELD_2-2,FIELD_1-1,FIELD_1-2) AS ROWID
FROM
(
SELECT
FIELD_1-1
,FIELD_1-2
,NAME_1-1
,NAME_1-2
FROM
(
SELECT FIELD_1-1,FIELD_1-2,NAME,VALUE
FROM TABLE_1
WHERE NAME IN ('NAME_1-1','NAME_1-2')
) AS SRC
PIVOT
(
MAX(VALUE_1) FOR NAME IN ([FIELD_1-1],[FIELD_1-2])
) AS PVT
) AS T0
LEFT JOIN TABLE_2 AS [P] ON T0.NAME_1-2=P.NAME_2-2
WHERE NAME_1-1 IN ('True','False')
) AS T1
WHERE ROWID = @ROW_ID -- In regards to our latest fetched ID
order by (FIELD_2-1,FIELD_2-2,FIELD_1-1,FIELD_1-2)
FOR JSON PATH, ROOT('FIELD_2-1');
-------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------
)
/* wrap records in JSON object */
Declare @Body as varchar(8000) = @Records
/* create XMLHTTP object and send object via HTTP POST */
Exec @hr=sp_OACreate 'MSXML2.ServerXMLHTTP', @Object OUT;
if @hr <> 0 begin Raiserror('sp_OACreate MSXML2.ServerXMLHttp.3.0 failed', 16,1) return end
Exec @hr = sp_OAMethod @Object, 'open', NULL, 'post','https://dataflownode.zerionsoftware.com/domain/solutions/services/webhooks/4b9b0f4b8a4b4387ec1642fdaabec7b400d5c938-7be9d5a63b5cba8ab72cd3410429e2635f68a687', 'false'
if @hr <>0 begin set @msg = 'sp_OAMethod Open failed' goto eh end
Exec @hr = sp_OAMethod @Object, 'setRequestHeader', null, 'Content-Type', 'application/json'
if @hr <>0 begin set @msg = 'sp_OAMethod setRequestHeader failed' goto eh end
Exec @hr = sp_OAMethod @Object, 'send', null, @Body
if @hr <>0 begin set @msg = 'sp_OAMethod Send failed' goto eh end
if @status <> 200 begin set @msg = 'sp_OAMethod http status ' + str(@status) goto eh end
Exec @hr = sp_OAMethod @Object, 'responseText', @ResponseText OUT--PUT
Select @ResponseText
-------------------------------------------------------------------------------------
/* Cursor Pt 3 */
-------------------------------------------------------------------------------------
FETCH NEXT FROM JSON_CURSOR INTO @ROW_ID -- Once the work is done we fetch the next result
END
-- We arrive here when @@FETCH_STATUS shows there are no more results to treat
CLOSE JSON_CURSOR
DEALLOCATE JSON_CURSOR -- CLOSE and DEALLOCATE remove the data from memory and clean up the process
-------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------
--if @hr <>0 begin set @msg = 'sp_OAMethod read response failed' goto
IF @hr <> 0
BEGIN
EXEC sp_OAGetErrorInfo @object
RETURN
goto
eh end
/* clean-up after data is sent */
Exec @hr=sp_OADestroy @Object
return
eh:
Raiserror(@msg, 16, 1)
return
IF @hr <> 0
BEGIN
EXEC sp_OAGetErrorInfo @object, @src OUT, @desc OUT
raiserror('Error Creating COM Component 0x%x, %s, %s',16,1, @hr, @src, @desc)
RETURN
END;
GO
Run Code Online (Sandbox Code Playgroud)
首先,看起来你在这里做了大量的额外工作。游标中的查询是WHILE循环中查询的子集,数据在任何时间点都不会发生变化。所以它只是一遍又一遍地执行相同的查询,只是针对不同的行。将初始查询的结果存储到本地临时表中,然后将其用于 CURSOR 和 WHILE 循环查询应该效率更高:
CREATE TABLE #Data
(
[RowID] INT NOT NULL IDENTITY(1, 1),
[FIELD_2-1] {data_type},
[FIELD_2-2] {data_type},
[FIELD_1-1] {data_type},
[FIELD_1-2] {data_type},
[T0.NAME_1-1] {data_type}
);
DECLARE @TotalRows INT;
INSERT INTO #Data ([FIELD_2-1], [FIELD_2-2], [FIELD_1-1], [FIELD_1-2],
[T0.NAME_1-1])
SELECT [FIELD_2-1], [FIELD_2-2], [FIELD_1-1], [FIELD_1-2], [T0.NAME_1-1]
FROM (
SELECT -- Our select statement (here you can do whatever work you wish)
FIELD_2-1
,FIELD_2-2
,FIELD_1-1
,FIELD_1-2
,T0.NAME_1-1
FROM
(
SELECT
FIELD_1-1
,FIELD_1-2
,NAME_1-1
,NAME_1-2
FROM
(
SELECT FIELD_1-1,FIELD_1-2,NAME,VALUE
FROM TABLE_1
WHERE NAME IN ('NAME_1-1','NAME_1-2')
) AS SRC
PIVOT
(
MAX(VALUE_1) FOR NAME IN ([FIELD_1-1],[FIELD_1-2])
) AS PVT
) AS T0
LEFT JOIN TABLE_2 AS [P] ON T0.NAME_1-2=P.NAME_2-2
) AS T1
ORDER BY [FIELD_2-1], [FIELD_2-2], [FIELD_1-1], [FIELD_1-2];
SET @TotalRows = @@ROWCOUNT;
Run Code Online (Sandbox Code Playgroud)然后,您可以CURSOR完全摆脱 ,因为它无论如何只用于获取总行数,并将WHILE循环更改为简单计数器。
最后,将这两部分放在一起,WHILE循环变为:
DECLARE @Index INT = 1,
@Records VARCHAR(8000);
WHILE (@Index <= @TotalRows)
BEGIN
SET @Records = (
SELECT [FIELD_2-1], [FIELD_2-2], [FIELD_1-1], [FIELD_1-2], [T0.NAME_1-1], [RowID]
FROM #Data
WHERE [RowID] = @Index
FOR JSON PATH, ROOT('FIELD_2-1')
);
SET @Index += 1;
END;
Run Code Online (Sandbox Code Playgroud)综上所述:
@Records在循环的每次迭代中重新声明变量。在循环之前声明一次,并且每次在循环内都设置它。@Body变量,因为它不执行任何操作。您只需将其设置为 的值@Records,这会浪费 CPU 和 RAM(以及时间)。sp_OACreate,但您从不调用sp_OADestroy(或任何调用)。我猜这最终会在内存中创建许多对象。您可能还需要再执行sp_OAMethod一次,就在Destroy关闭请求之前。您需要四处检查以查看是否是这种情况。您不想让任何孤立的网络套接字保持打开状态。虽然您实际上可能会使其工作,但使用 OLE 自动化存储过程(即sp_OA*)是相当危险的。自 SQL Server 2005 发布以来,它们已被弃用,转而使用 SQLCLR。使用 SQLCLR 代替 OLE 自动化过程有很多优点,包括:
NVARCHAR(MAX)而不是被VARCHAR(8000)和卡住NVARCHAR(4000)。事实上,你甚至可以发送XML. OLE 自动化过程不处理任何在 SQL Server 2000 之后添加的数据类型。您可以使用 .NETHttpWebRequest类。或者,如果您不想编写任何代码,SQL#(我创建的)中存在一个预先完成的 SQLCLR 函数。该函数是INET_GetWebPages,它处理各种各样的场景(即,您可以传入自定义标头、将内容发送为GETorPOST等)。但是,需要明确的是,INET_GetWebPages仅在完整版中可用(即不在免费版中)
EXEC sp_OA*语句。首先注释掉所有EXEC sp_OA*语句以及IF每个语句之后的语句。然后,如果这使 proc 不再出错,则开始一个一个地取消注释每个EXEC/IF对语句(除了第一组:如果取消注释sp_OACreate,则必须将其与sp_OADestroy 同一范围内的an 配对!)抛开所有这些建议不谈,导致崩溃的实际问题(正如我们在聊天中发现的那样)最终是返回到 SSMS 的结果集数量太多。在问题的第一个代码块中,查询返回了 2775 行。在存储过程的上下文中,没有结果集返回给客户端;所有结果都存储在一个VARCHAR(8000)变量中。但在测试中,它只是一个直线SELECT。当不将每个结果集转储到单独的网格时,SSMS 不会崩溃。
此外,如果您自己去这个代码,你会如果使用SQL Server 2017年(或更新版本)遇到“问题”部署代码时,即使使用SQL Server 2016年,您还需要设置大会EXTERNAL_ACCESS哪将需要签署大会和其他一些小步骤。请在此处查看我的帖子,了解有关如何使用或不使用 Visual Studio 以及以适用于 SQL Server 2017(或更新版本)的方式处理此问题的说明:
SQLCLR 与 SQL Server 2017,第 2 部分:“CLR 严格安全性”——解决方案 1(目标是处理安全性和程序集的单一、自包含安装脚本,并且没有任何外部依赖项,因此很容易开发、测试和生产系统之间的版本和/或转移)
要解决 SSMS 问题,请设置 NOCOUNT ON 并且不要将结果发送给客户端。
ServerXMLHTTP.Send,当从不推荐使用的 sp_OAxxx COM 互操作存储过程调用时,有 8000 个字符的限制,因此它不是这项工作的好选择。
在 SQL CLR 存储过程或许多客户端编程环境(如 PowerShell、Python、.NET 等)中执行此操作是微不足道的。所有这些都可以从 SQL 代理作业调用。
此外,您没有正确销毁您正在创建的 COM 对象。您在循环中调用 sp_OACreate,但不调用 sp_OADestroy。
此外,当您复制(可能是二手或三手)我15 年前关于如何使用 sp_OAxxx COM 互操作程序中的 ServerXmlHttp 的USENET 帖子时,您还缺少一行。
exec @hr = sp_OAGetProperty @obj, 'status', @status OUT
if @hr <0 begin set @msg = 'sp_OAMethod read status failed' goto eh end
Run Code Online (Sandbox Code Playgroud)
总而言之,我建议您停止并用不同的语言编写此过程。
这是一个示例,可让您开始了解如何将 FOR JSON 查询发布到 HTTP 端点并获取响应。
作为背景,FOR JSON查询作为一列、多行的结果集流式传输到客户端,其中 JSON 跨行。因此,您可以通读结果行并将内容发布到 HTTP 端点。无论文档有多大,都不会在 SQL 中进行缓冲。
你这样称呼
declare @rc int = 0
declare @body nvarchar(max)
exec postjson 'select * from sys.objects for json path', 'http://localhost:51801/api/values', @rc out, @body out
select @rc, @body
Run Code Online (Sandbox Code Playgroud)
这是 C# 源代码
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Text;
using Microsoft.SqlServer.Server;
using System.Net;
using System.IO;
public partial class StoredProcedures
{
[Microsoft.SqlServer.Server.SqlProcedure]
public static void PostJSON(string sqlForJSONQuery, string targetURI, out int responseCode, out string responseBody)
{
using (var con = new SqlConnection("Context Connection=true"))
{
con.Open();
var cmd = con.CreateCommand();
cmd.CommandText = sqlForJSONQuery;
using (var rdr = cmd.ExecuteReader())
{
var req = WebRequest.CreateHttp(targetURI);
req.Method = "Post";
req.ContentType = "application/json";
using (var rs = req.GetRequestStream())
{
while (rdr.Read())
{
var val = rdr.GetString(0);
//SqlContext.Pipe.Send(val);
var buf = Encoding.UTF8.GetBytes(val);
rs.Write(buf, 0, buf.Length);
}
}
HttpWebResponse resp;
try
{
resp = (HttpWebResponse)req.GetResponse();
}
catch (WebException ex)
{
resp = (HttpWebResponse)ex.Response;
}
responseCode = (int)resp.StatusCode;
using (var respStream = resp.GetResponseStream())
{
using (var sr = new StreamReader(respStream, Encoding.UTF8))
{
responseBody = sr.ReadToEnd();
}
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
您可以使用SQL Server Data Tools构建和部署它。这为您的 SQL Server 开发提供了完整的 Visual Studio 集成,包括 SQL CLR,涵盖编码、调试、部署和管理源代码控制。
或者至少,这里是如何只用 SQL Server 上的命令行来做到这一点。
在您的 SQL Server 上创建一个名为 的目录c:\PostJSON,并在其中PostJSON.cs使用上述源代码创建。然后在该文件夹中打开命令提示符并运行:
PS C:\PostJSON> C:\windows\Microsoft.NET\Framework64\v4.0.30319\csc /out:PostJson.dll /target:library PostJSON.cs
Run Code Online (Sandbox Code Playgroud)
要将PostJSON.cs文件编译为PostJSON.dll.
然后从您的数据库运行此脚本以安装和测试存储过程:
drop procedure if exists postjson
if exists (select * from sys.assemblies where name = 'PostJSON')
drop assembly PostJSON
exec sp_configure 'show advanced options', 1
reconfigure
exec sp_configure 'clr enabled', 1;
reconfigure
go
DECLARE @asmBin varbinary(max) = (
SELECT BulkColumn
FROM OPENROWSET (BULK 'c:\PostJSON\PostJson.dll', SINGLE_BLOB) a
);
DECLARE @hash varbinary(64);
SELECT @hash = HASHBYTES('SHA2_512', @asmBin);
declare @description nvarchar(4000) = N'PostJSON';
if not exists (select * from sys.trusted_assemblies where hash = @hash)
begin
EXEC sys.sp_add_trusted_assembly @hash = @hash,
@description = @description;
end
CREATE ASSEMBLY [PostJSON]
AUTHORIZATION [dbo]
FROM @asmBin
WITH PERMISSION_SET = EXTERNAL_ACCESS;
exec('
CREATE PROCEDURE PostJson @sqlForJSONQuery nvarchar(max),
@targetURI nvarchar(max),
@responseCode int out,
@responseBody nvarchar(max) out
AS EXTERNAL NAME PostJSON.StoredProcedures.PostJSON
')
go
--test
declare @rc int
declare @body nvarchar(max)
exec postjson 'select ''Hello world'' msg for json path', 'http:\\bing.com', @rc out, @body out
select @rc, @body
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1704 次 |
| 最近记录: |