J'ai un curseur qui génère un enregistrement du texte JSON à partir d'un groupe de tables. Le curseur a fabriqué un crash SSMS. Le script fonctionne pendant un temps puis SSMS échoue. Vous trouverez ci-dessous le code que j'ai écrit qui causait l'accident.
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
Du journal Windows:
L'erreur d'exécution .NET suivante s'est produite en premier:
Erreur 7/20/2018 2:27:58 PM .NET Runtime 1026 Aucun
Application: SSMS.EXE Framework Version: V4.0.30319 Description: Le processus a été résilié en raison d'une exception non gérée. Info d'exception: System.componentModel.win32Exception à System.Windows.Forms.NativeWindow.CreateHandle (System.Windows.Forms.Creatreparams) sur System.Windows.Forms.Control.CreateHandle () à System.Windows.Forms.TextBoxBase.CreateHandle ( ) à System.Windows.Forms.Control.createcontrol (Boolean) à System.Windows.Forms.Control.createcontrol (Boolean) à System.Windows.Forms.Control.createcontrol () sur System.Windows.Forms.Control.wmshowwindow ( System.windows.forms.message byref) sur system.windows.forms.control.wndproc (system.windows.forms.message byref) sur system.windows.forms.scrollablecontrol.wndproc (system.windows.forms.message byref) à System.Windows.Forms.ContainEnTrol.wndProc (System.Windows.Forms.Message BYREF) sur System.Windows.Forms.UPTOWNBASE.WNDPROC (System.Windows.Forms.Message Byref) sur System.Windows.Forms.Control + ControlNativeWindow. OnMessage (System.Windows.Forms.Message Byref) chez System.Windows.Forms.Control + ControlNativeWindow.wndProc (System.Windows.Forms.Message Byref) à SY stem.windows.forms.nativewindow.debuggableCallback (intPTR, int32, intptr, intPTR)
Deuxième erreur d'application:
Erreur 7/20/2018 2:27:58 PM Erreur d'application 1000 (100)
Nom de l'application erronée: ssms.exe, version: 2017.140.17277.0, Time Time: 0x5b304116 Nom de module de défaut: Kernelbase.dll, Version: 10.0.14393.2189, Time Time: 0x5ABDA7D6 Code d'exception: 0xE0434352 Décalage de défaut: 0x000Daa12 ID de processus de défaut: 0x3f6C Défautement Temps de début de l'application: 0x01D4205466D2B650 Défaut Chemin d'application: C:\Fichiers de programme (x86)\Microsoft SQL Server\140\Tools\Binn\Studingstudio\ssms.exe Chemin de module de défaut: C:\Windows\System32\kernelbase.dll ID de rapport: 03B3B0C6-0839-4562-A71F-F5B4FC0A3029 Package de défaillance Nom complet: ID d'application relative de paquet défaillant:
Le but de ce script est de produire une seule entrée dans JSON qui sera affichée sur un service de données dans le cloud. Également dans SSMS, j'envoie les résultats à la grille.
Voici la requête complète de la procédure stockée que je crée.
/****** 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
Pour dépasser la question SSMS, définissez Nocount sur et n'envoyez pas de résultats au client.
SERVERXMLHTTP.SEND, lorsque cela est appelé à partir de la dépréciation SP_OAXXX COMM des procédures stockées SP_OAXXXX COMB a une limite de 8 000 caractères, ce n'est donc pas un bon choix pour ce travail.
Ceci est trivial à faire dans une procédure stockée SQL CLR, ou dans de nombreux environnements de programmation client, comme PowerShell, Python, .NET, etc. Tous peuvent être invoqués à partir d'emplois SQL Agent.
En outre, vous ne détruisez pas correctement les objets COM que vous créez. Vous appelez sp_oacreate dans la boucle, mais pas sp_oadestroy.
Vous manquez également une ligne lorsque vous avez copié (probablement deuxième ou troisième main) mon Senet Post UseNet de 15 ans sur l'utilisation de ServerXMLHTTP à partir des procédures SP_OAXXXX COM.
exec @hr = sp_OAGetProperty @obj, 'status', @status OUT
if @hr <0 begin set @msg = 'sp_OAMethod read status failed' goto eh end
Dans l'ensemble, je vous conseillerais de vous arrêter et d'écrire ce processus dans une langue différente.
Voici un échantillon pour vous aider à démarrer comment poster une requête JSON vers un point d'extrémité HTTP et obtenir la réponse.
Comme de l'arrière-plan, un FOR JSON
La requête est diffusée sur le client sous forme de résultat à une colonne à plusieurs colonnes avec le JSON cassé sur les rangées. Vous pouvez donc simplement lire les lignes de résultat et poster le contenu au point d'extrémité HTTP. Peu importe la taille du document, il ne sera pas tamponné dans SQL.
Vous l'appelez comme ça
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
Voici le code source 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();
}
}
}
}
}
}
Vous pouvez créer et déployer cette utilisation Outils de données SQL Server . Cela vous donne une intégration complète de Visual Studio pour votre développement SQL Server, y compris SQL CLR, le codage de codage, le débogage, le déploiement et la gestion du contrôle de la source.
Ou minimalement, voici comment le faire avec rien que la ligne de commande sur votre serveur SQL.
Créez un répertoire sur votre serveur SQL appelé c:\PostJSON
, et là créer PostJSON.cs
avec le code source ci-dessus. Ensuite, ouvrez une invite de commande dans ce dossier et exécutez:
PS C:\PostJSON> C:\windows\Microsoft.NET\Framework64\v4.0.30319\csc /out:PostJson.dll /target:library PostJSON.cs
Compiler le PostJSON.cs
Dossier dans PostJSON.dll
.
Ensuite, à partir de votre base de données, exécutez ce script pour installer et tester la procédure stockée:
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