web-dev-qa-db-fra.com

Erreurs: "L'instruction INSERT EXEC ne peut pas être imbriquée." et "Impossible d'utiliser l'instruction ROLLBACK dans une instruction INSERT-EXEC." Comment résoudre ceci?

J'ai trois procédures stockées Sp1, Sp2 et Sp3.

Le premier (Sp1) exécute le second (Sp2) et enregistre les données renvoyées dans @tempTB1 et le second exécute le troisième (Sp3) et enregistre les données dans @tempTB2.

Si j'exécute le Sp2 cela fonctionnera et me retournera toutes mes données du Sp3, mais le problème est dans le Sp1, lorsque je l'exécuterai, cette erreur s'affichera:

INSERT EXEC instruction ne peut pas être imbriquée

J'ai essayé de changer l'emplacement de execute Sp2 et il m'affiche une autre erreur:

Impossible d'utiliser l'instruction ROLLBACK dans une instruction INSERT-EXEC.

84
HAJJAJ

Il s'agit d'un problème courant lorsque vous tentez de «masquer» les données d'une chaîne de procédures stockées. Une restriction dans SQL Server est que vous ne pouvez avoir qu'un seul INSERT-EXEC actif à la fois. Je recommande de regarder Comment partager des données entre des procédures stockées qui est un article très complet sur les modèles permettant de contourner ce type de problème.

Par exemple, un moyen de contourner le problème pourrait être de transformer Sp3 en une fonction table.

84
eddiegroves

C’est le seul moyen "simple" de procéder de la sorte dans SQL Server sans une fonction créée alambiquée gigantesque ou un appel de chaîne SQL exécuté, les deux solutions étant terribles:

  1. créer une table temporaire
  2. openrowset vos données de procédure stockée dans ce

EXEMPLE:

INSERT INTO #YOUR_TEMP_TABLE
SELECT * FROM OPENROWSET ('SQLOLEDB','Server=(local);TRUSTED_CONNECTION=YES;','set fmtonly off EXEC [ServerName].dbo.[StoredProcedureName] 1,2,3')

Remarque : Vous DEVEZ utiliser 'set fmtonly off' ET vous ne pouvez pas ajouter de code SQL dynamique à cet appel, ni dans l'appel openrowset, ni pour la chaîne contenant vos paramètres de procédure stockée, ni pour le nom de la table. C’est la raison pour laquelle vous devez utiliser une table temporaire plutôt que des variables de table, ce qui aurait été préférable, car elle exécute la table temporaire dans la plupart des cas.

16
Mitch Stokely

OK, encouragé par jimhark, voici un exemple de l'ancienne approche de table de hachage unique: -

CREATE PROCEDURE SP3 as

BEGIN

    SELECT 1, 'Data1'
    UNION ALL
    SELECT 2, 'Data2'

END
go


CREATE PROCEDURE SP2 as

BEGIN

    if exists (select  * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#tmp1'))
        INSERT INTO #tmp1
        EXEC SP3
    else
        EXEC SP3

END
go

CREATE PROCEDURE SP1 as

BEGIN

    EXEC SP2

END
GO


/*
--I want some data back from SP3

-- Just run the SP1

EXEC SP1
*/


/*
--I want some data back from SP3 into a table to do something useful
--Try run this - get an error - can't nest Execs

if exists (select  * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#tmp1'))
    DROP TABLE #tmp1

CREATE TABLE #tmp1 (ID INT, Data VARCHAR(20))

INSERT INTO #tmp1
EXEC SP1


*/

/*
--I want some data back from SP3 into a table to do something useful
--However, if we run this single hash temp table it is in scope anyway so
--no need for the exec insert

if exists (select  * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#tmp1'))
    DROP TABLE #tmp1

CREATE TABLE #tmp1 (ID INT, Data VARCHAR(20))

EXEC SP1

SELECT * FROM #tmp1

*/
5
Matt Luckham

Mon problème pour résoudre ce problème a toujours consisté à utiliser le principe voulant que les tables temporaires de hachage s'appliquent à tous les processus appelés. Donc, j'ai un commutateur d'option dans les paramètres proc (par défaut, désactivé). Si cette option est activée, le processus appelé insérera les résultats dans la table temporaire créée dans le processus appelant. Je pense que par le passé, je suis allé un peu plus loin et j'ai mis du code dans le proc appelé pour vérifier si la table de hachage unique existe dans la portée, si elle insère ensuite le code, sinon renvoie le résultat. Semble bien fonctionner - le meilleur moyen de faire passer de grands ensembles de données entre procs.

5
Matt Luckham

J'ai trouvé un moyen de contourner le problème est de convertir l'un des éléments en une fonction table. Je me rends compte que ce n'est pas toujours possible et introduit ses propres limites. Cependant, j'ai toujours pu trouver au moins une des procédures un bon candidat pour cela. J'aime cette solution, car elle n'introduit pas de "hacks" dans la solution.

4
Roman K

Cette astuce fonctionne pour moi.

Vous n'avez pas ce problème sur le serveur distant, car sur le serveur distant, la dernière commande d'insertion attend le résultat de la commande précédente à exécuter. Ce n'est pas le cas sur le même serveur.

Profitez de cette situation pour une solution de contournement.

Si vous avez le droit de créer un serveur lié, faites-le ..___ Créez le même serveur que le serveur lié.

  • dans SSMS, connectez-vous à votre serveur 
  • aller à "objet serveur
  • Clic droit sur "Serveurs liés", puis "Nouveau serveur lié"
  • dans la boîte de dialogue, indiquez le nom de votre serveur lié, par exemple: THISSERVER
  • le type de serveur est "Autre source de données"
  • Fournisseur: Fournisseur de bases de données Microsoft OLE pour SQL Server
  • Source de données: votre IP, cela peut être aussi juste un point (.), Parce que c'est localhost
  • Allez dans l'onglet "Sécurité" et choisissez le troisième "Être faite en utilisant le contexte de sécurité actuel du login"
  • Vous pouvez modifier les options du serveur (3ème onglet) si vous le souhaitez.
  • Appuyez sur OK, votre serveur lié est créé.

maintenant votre commande SQL dans le SP1 est

insert into @myTempTable
exec THISSERVER.MY_DATABASE_NAME.MY_SCHEMA.SP2

Croyez-moi, cela fonctionne même si vous avez un insert dynamique dans SP2

3
ainasiart

qu'en est-il simplement de stocker la sortie dans la table statique? Comme

-- SubProcedure: subProcedureName
---------------------------------
-- Save the value
DELETE lastValue_subProcedureName
INSERT INTO lastValue_subProcedureName (Value)
SELECT @Value
-- Return the value
SELECT @Value

-- Procedure
--------------------------------------------
-- get last value of subProcedureName
SELECT Value FROM lastValue_subProcedureName

ce n'est pas idéal, mais c'est tellement simple et vous n'avez pas besoin de tout réécrire.

UPDATE: la solution précédente ne fonctionnait pas bien avec les requêtes parallèles (accès asynchrone et multiutilisateur), par conséquent, j'utilise maintenant des tables temporaires

-- A local temporary table created in a stored procedure is dropped automatically when the stored procedure is finished. 
-- The table can be referenced by any nested stored procedures executed by the stored procedure that created the table. 
-- The table cannot be referenced by the process that called the stored procedure that created the table.
IF OBJECT_ID('tempdb..#lastValue_spGetData') IS NULL
CREATE TABLE #lastValue_spGetData (Value INT)

-- trigger stored procedure with special silent parameter
EXEC dbo.spGetData 1 --silent mode parameter

contenu de procédure stockée spGetData imbriqué

-- Save the output if temporary table exists.
IF OBJECT_ID('tempdb..#lastValue_spGetData') IS NOT NULL
BEGIN
    DELETE #lastValue_spGetData
    INSERT INTO #lastValue_spGetData(Value)
    SELECT Col1 FROM dbo.Table1
END

 -- stored procedure return
 IF @silentMode = 0
 SELECT Col1 FROM dbo.Table1
1
Muflix

J'avais le même problème et la même préoccupation concernant le code en double dans deux sprocs ou plus. J'ai fini par ajouter un attribut supplémentaire pour "mode". Cela permettait au code commun d'exister à l'intérieur d'un sproc et aux flux et ensembles de résultats orientés mode du sproc.

1
phoenixAZ

Déclarez une variable de curseur de sortie dans la sp interne:

@c CURSOR VARYING OUTPUT

Puis déclarez un curseur c à la sélection que vous souhaitez retourner . Ouvrez ensuite le curseur . Définissez ensuite la référence: 

DECLARE c CURSOR LOCAL FAST_FORWARD READ_ONLY FOR 
SELECT ...
OPEN c
SET @c = c 

NE PAS fermer ou réaffecter.

Appelez maintenant le sp interne de celui externe en fournissant un paramètre de curseur comme:

exec sp_abc a,b,c,, @cOUT OUTPUT

Une fois que le sp interne est exécuté, votre @cOUT est prêt à être récupéré. Boucle puis ferme et désalloue.

0