テーブルのグループからJSONテキストの1つのレコードを生成するカーソルがあります。カーソルが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
Windowsログから:
次の.NETランタイムエラーが最初に発生しました:
エラー7/20/2018 2:27:58 PM .NETランタイム1026なし
アプリケーション:Ssms.exeフレームワークバージョン:v4.0.30319説明:未処理の例外が原因でプロセスが終了しました。例外情報:System.Windows.Forms.TextBoxBase.CreateHandle()でSystem.Windows.Forms.Control.CreateHandle()でSystem.Windows.Forms.NativeWindow.CreateHandle(System.Windows.Forms.CreateParams)でSystem.ComponentModel.Win32Exception )System.Windows.Forms.Control.CreateControl(Boolean)at System.Windows.Forms.Control.CreateControl(Boolean)at System.Windows.Forms.Control.CreateControl()at System.Windows.Forms.Control.WmShowWindow( System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)でSystem.Windows.Forms.Message ByRef)にSystem.Windows.Forms.ScrollableControl.WndProc(System.Windows.Forms.Message ByRef)でSystem.Windows.Forms.Control + ControlNativeWindowのSystem.Windows.Forms.UpDownBase.WndProc(System.Windows.Forms.Message ByRef)のSystem.Windows.Forms.ContainerControl.WndProc(System.Windows.Forms.Message ByRef) OnMessage(System.Windows.Forms.Message ByRef)at System.Windows.Forms.Control + ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef)Sy stem.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr、Int32、IntPtr、IntPtr)
2番目のアプリケーションエラー:
エラー7/20/2018 2:27:58 PMアプリケーションエラー1000(100)
障害が発生しているアプリケーション名:Ssms.exe、バージョン:2017.140.17277.0、タイムスタンプ:0x5b304116障害が発生しているモジュール名:KERNELBASE.dll、バージョン:10.0.14393.2189、タイムスタンプ:0x5abda7d6例外コード:0xe0434352障害オフセット:0x000daa12障害が発生しているプロセスID:0x3f6c障害が発生していますアプリケーションの開始時刻:0x01d4205466d2b650障害のあるアプリケーションパス:C:\ Program Files(x86)\ Microsoft SQL Server\140\Tools\Binn\ManagementStudio\Ssms.exe障害のあるモジュールパス:C:\ WINDOWS\System32\KERNELBASE.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
最初に、ここで大量の追加作業を行っているようです。カーソル内のクエリは、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;
次に、CURSOR
を完全に取り除くことができます。これは、とにかく合計行数を取得するためにのみ使用されるため、WHILE
ループを単純なカウンターに変更するためです。
最後に、これら2つの部分をまとめると、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;
言われていることすべて:
@Records
変数を再宣言しないでください。ループの前に一度宣言し、ループ内で毎回設定するだけです。@Body
変数は何もしないので必要ありません。 CPUとRAM(および時間)を浪費する@Records
の値に設定するだけです。sp_OACreate
を実行しますが、sp_OADestroy
(またはそれが何であれ)を呼び出すことはありません。これにより、メモリ内に多くのオブジェクトが作成されることになると思います。また、mightは、Destroy
の直前でもう一度sp_OAMethod
を実行して、リクエストを閉じる必要があります。そうであるかどうかを確認する必要があります。孤立したネットワークソケットを開いたままにしたくない。実際にこれが機能する可能性はありますが、OLE Automationストアドプロシージャ(つまりsp_OA*
)を使用することはかなり危険です。SQLC2005のリリース以降、代わりにSQLCLRを使用することをお勧めします。 OLE Automation procsの代わりにSQLCLRを使用することには、次のような多くの利点があります。
NVARCHAR(MAX)
およびVARCHAR(8000)
でスタックする代わりに、NVARCHAR(4000)
を使用できる。実際、XML
を送信することもできます。 OLEオートメーションプロシージャは、SQL Server 2000以降に追加されたデータ型を処理しません。.NET HttpWebRequest
クラスを使用できます。または、何もコーディングしたくない場合は、あらかじめ作成されたSQLCLR関数が SQL# (作成したもの)に存在します。関数はINET_GetWebPagesであり、さまざまなシナリオを処理します(つまり、カスタムヘッダーを渡したり、コンテンツをGET
またはPOST
として送信したりできます)。ただし、明確にするために、INET_GetWebPagesはフルバージョンでのみ利用できます(つまり、無料バージョンでは利用できません)。
EXEC sp_OA*
ステートメントをコメント化できます。すべてのEXEC sp_OA*
ステートメントと、その後に続くIF
ステートメントをコメント化することから始めます。次に、プロシージャでエラーが発生しなくなった場合は、ステートメントのEXEC
/IF
の各ペアのコメントを1つずつ解除します(最初のセットを除く:sp_OACreate
のコメントを解除すると、あなたが必要になります同じスコープ内のsp_OADestroy
とペアにする!!)これらすべての提案はさておき、クラッシュの原因となった実際の問題( chat で発見された)は、 SSMSに返される結果セットの完全な数。質問の最初のコードブロックには、クエリによって2775行が返されます。ストアドプロシージャのコンテキスト内では、結果セットはクライアントに返されません。すべての結果はVARCHAR(8000)
変数に格納されます。しかし、テストでは、それは単なるSELECT
です。各結果セットを個別のグリッドにダンプしない場合、SSMSはクラッシュしませんでした。
また、これを自分でコーディングする場合、SQL Server 2017(またはそれ以降)を使用している場合、コードをデプロイするときに「問題」が発生します。SQLServer 2016を使用している場合でも、アセンブリをEXTERNAL_ACCESS
に設定する必要があります議会への署名とその他のいくつかの小さなステップが必要になります。 Visual Studioを使用して、または使用せずに、SQL Server 2017(以降)で機能する方法でこれを処理する方法については、こちらの投稿を参照してください。
SQLCLRとSQL Server 2017、パート2:「CLR厳密なセキュリティ」–ソリューション1 (セキュリティとアセンブリを処理する単一の自己完結型インストールスクリプトを目標としていますandは、外部依存関係がないため、バージョン管理や、開発、テスト、本番システム間での移行が容易になります)
SSMSの問題を回避するには、SET NOCOUNT ONを設定し、結果をクライアントに送信しないでください。
ServerXMLHTTP.Sendは、非推奨のsp_OAxxx COM相互運用ストアドプロシージャから呼び出される場合、8000文字の制限があるため、このジョブには適していません。
これは、SQL CLRストアドプロシージャ、またはPowerShell、Python、.NETなどの多くのクライアントプログラミング環境で簡単に実行できます。これらはすべてSQLエージェントジョブから呼び出すことができます。
また、作成しているCOMオブジェクトを適切に破棄していません。ループでsp_OACreateを呼び出しますが、sp_OADestroyは呼び出しません。
また、(おそらく2番目または3番目の手)私 15歳のUSENETの投稿 をコピーしたときに、sp_OAxxx COMからServerXmlHttpを使用する方法に関する行がありません。相互運用手順。
exec @hr = sp_OAGetProperty @obj, 'status', @status OUT
if @hr <0 begin set @msg = 'sp_OAMethod read status failed' goto eh end
全体として、このプロセスを停止して別の言語で書くことをお勧めします。
FOR JSONクエリをHTTPエンドポイントに投稿して応答を取得する方法を始めるためのサンプルを以下に示します。
背景として、FOR JSON
クエリは、JSONが行間で分割された1列の複数行の結果セットとしてクライアントにストリーミングされます。したがって、結果行を読み取って、コンテンツを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
これが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();
}
}
}
}
}
}
これは SQL Server Data Tools を使用して構築およびデプロイできます。これにより、SQL CLRを含むSQL Server開発のVisual Studioと完全に統合し、ソース管理のコーディング、デバッグ、展開、管理をカバーできます。
または、最低限、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
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