web-dev-qa-db-fra.com

Le curseur provoque l'accident de SSMS

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
3
ivGeo

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
5