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.
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.
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:
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.
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
*/
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.
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.
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é.
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
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
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.
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.