Errori: "L'istruzione INSERT EXEC non può essere nidificata." e "Impossibile utilizzare l'istruzione ROLLBACK all'interno di un'istruzione INSERT-EXEC". Come risolverlo?


98

Ho tre stored procedure Sp1, Sp2e Sp3.

Il primo ( Sp1) eseguirà il secondo ( Sp2) e salverà i dati restituiti in @tempTB1e il secondo eseguirà il terzo ( Sp3) e salverà i dati in @tempTB2.

Se eseguo il Sp2funzionerà e mi restituirà tutti i miei dati dal Sp3, ma il problema è nel Sp1, quando lo eseguo visualizzerà questo errore:

L'istruzione INSERT EXEC non può essere nidificata

Ho provato a cambiare il posto di execute Sp2e mi ha mostrato un altro errore:

Impossibile utilizzare l'istruzione ROLLBACK all'interno di un'istruzione INSERT-EXEC.

Risposte:


101

Questo è un problema comune quando si tenta di "raggruppare" i dati da una catena di procedure memorizzate. Una restrizione in SQL Server è che puoi avere solo un INSERT-EXEC attivo alla volta. Consiglio di guardare come condividere i dati tra le stored procedure, che è un articolo molto approfondito sui modelli per aggirare questo tipo di problema.

Ad esempio, una soluzione potrebbe essere quella di trasformare Sp3 in una funzione con valori di tabella.


1
link interrotto O sito che non risponde.
SouravA

6
hai idea di quale sia la ragione tecnica per non permetterlo? Non riesco a trovare alcuna informazione su questo.
jtate

1
Purtroppo molto spesso questa non è un'opzione. Molti tipi di informazioni importanti sono disponibili in modo affidabile solo dalle procedure memorizzate di sistema (perché in alcuni casi la rispettiva vista di gestione contiene dati inaffidabili / obsoleti; un esempio sono le informazioni restituite da sp_help_jobactivity).
GSerg

21

Questo è l'unico modo "semplice" per farlo in SQL Server senza una gigantesca funzione convoluta creata o una chiamata di stringa sql eseguita, entrambe soluzioni terribili:

  1. creare una tabella temporanea
  2. openrowset i dati della procedura memorizzata in esso

ESEMPIO:

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

Nota : DEVI usare 'set fmtonly off' E NON PUOI aggiungere sql dinamico a questo all'interno della chiamata openrowset, né per la stringa contenente i parametri della procedura memorizzata né per il nome della tabella. Ecco perché devi usare una tabella temporanea anziché le variabili di tabella, il che sarebbe stato meglio, poiché nella maggior parte dei casi esegue una tabella temporanea.


Non è necessario utilizzare SET FMTONLY OFF. Puoi semplicemente aggiungere un IF (1 = 0) che restituisce una tabella vuota con gli stessi tipi di dati restituiti normalmente dalla procedura.
Guillermo Gutiérrez

1
Le tabelle temporanee e le variabili di tabella memorizzano i propri dati in modo diverso. Le variabili di tabella dovrebbero essere utilizzate per piccoli set di risultati poiché Query Optimizer non mantiene le statistiche sulle variabili di tabella. Quindi per set di dati di grandi dimensioni è quasi sempre meglio utilizzare le tabelle temporanee. Ecco un bell'articolo del blog su di esso mssqltips.com/sqlservertip/2825/…
gh9

@ gh9 sì, ma questa è comunque un'idea orribile per set di risultati di grandi dimensioni. Le statistiche e l'utilizzo di una tabella effettiva nel database temporaneo possono causare un sovraccarico significativo. Ho una procedura che restituisce un recordset con 1 riga di valori correnti (interrogando diverse tabelle) e una procedura che lo memorizza in una variabile di tabella e lo confronta con i valori in un'altra tabella con lo stesso formato. Il passaggio da una tabella temporanea a una variabile di tabella ha accelerato il tempo medio da 8 ms a 2 ms, il che è importante quando viene chiamato più volte al secondo durante il giorno e 100.000 volte in un processo notturno.
Jason Goemaat

Perché vuoi che le statistiche vengano mantenute su una variabile di tabella? Il punto è creare una tabella temporanea nella RAM che verrà distrutta al termine della query. Per definizione, le statistiche create su tale tabella non sarebbero mai state utilizzate. In generale, il fatto che i dati in una variabile di tabella rimangano nella RAM ove possibile li rende più veloci delle tabelle temporanee in qualsiasi scenario in cui i tuoi dati sono inferiori alla quantità di RAM disponibile per SQL Server (che in questi giorni di 100 GB + pool di memoria per il nostro SQL Server, è quasi sempre)
Geoff Griswald

Tuttavia, questo non funziona per le stored procedure estese. L'errore è Impossibile determinare i metadati perché l'istruzione "EXECUTE <procedurename> @retval OUTPUT" nella procedura ... "richiama una stored procedure estesa .
GSerg

11

OK, incoraggiato da jimhark ecco un esempio del vecchio approccio a tabella hash singola: -

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

*/

Ho usato anche questa soluzione. Grazie per l'idea!
SQL_Guy

Fantastica soluzione. Questo mi ha aiutato a saperne di più sull'ambito della tabella temporanea. Ad esempio, non mi ero reso conto che puoi usare una tabella temporanea in una stringa dynsql se fosse dichiarata al di fuori di essa. Concetto simile qui. Molte grazie.
jbd

9

Il mio lavoro intorno a questo problema è sempre stato quello di utilizzare il principio che le singole tabelle temporanee hash rientrano nell'ambito di qualsiasi proc chiamato. Quindi, ho un interruttore di opzione nei parametri proc (impostazione predefinita su off). Se questo è attivato, il proc chiamato inserirà i risultati nella tabella temporanea creata nel proc chiamante. Penso che in passato ho fatto un ulteriore passo avanti e ho inserito del codice nel proc chiamato per verificare se la singola tabella hash esiste nell'ambito, se lo fa, inserisci il codice, altrimenti restituisci il set di risultati. Sembra funzionare bene: il modo migliore per passare grandi set di dati tra i processi.


1
Mi piace questa risposta e scommetto che otterrai più voti se fornissi un esempio.
jimhark

Lo faccio da anni. Tuttavia è ancora necessario in SQL Azure?
Nick Allan

6

Questo trucco funziona per me.

Non hai questo problema sul server remoto, perché sul server remoto, l'ultimo comando di inserimento attende che il risultato del comando precedente venga eseguito. Non è il caso sullo stesso server.

Approfitta di quella situazione per una soluzione alternativa.

Se disponi dell'autorizzazione giusta per creare un server collegato, fallo. Crea lo stesso server del server collegato.

  • in SSMS, accedi al tuo server
  • vai a "Oggetto server
  • Fare clic con il tasto destro su "Server collegati", quindi "Nuovo server collegato"
  • nella finestra di dialogo, fornire un nome qualsiasi del server collegato: ad esempio: THISSERVER
  • il tipo di server è "Altra origine dati"
  • Provider: provider Microsoft OLE DB per SQL Server
  • Fonte dei dati: il tuo IP, può anche essere solo un punto (.), Perché è localhost
  • Vai alla scheda "Sicurezza" e scegli la terza "Da effettuare utilizzando l'attuale contesto di sicurezza del login"
  • Puoi modificare le opzioni del server (3a scheda) se lo desideri
  • Premi OK, il tuo server collegato viene creato

ora il tuo comando Sql nell'SP1 è

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

Credimi, funziona anche se hai un inserto dinamico in SP2


4

Ho trovato che un modo per aggirare è convertire uno dei pung in una funzione con valori di tabella. Mi rendo conto che non è sempre possibile e introduce i propri limiti. Tuttavia, sono stato in grado di trovare sempre almeno una delle procedure un buon candidato per questo. Mi piace questa soluzione, perché non introduce alcun "hack" nella soluzione.


ma uno svantaggio è un problema con la gestione delle eccezioni se la funzione è complessa, giusto?
Muflix

2

Ho riscontrato questo problema durante il tentativo di importare i risultati di uno Stored Proc in una tabella temporanea e lo Stored Proc inserito in una tabella temporanea come parte della propria operazione. Il problema è che SQL Server non consente allo stesso processo di scrivere contemporaneamente su due diverse tabelle temporanee.

La risposta OPENROWSET accettata funziona bene, ma dovevo evitare di utilizzare SQL dinamico o un provider OLE esterno nel mio processo, quindi ho scelto una strada diversa.

Una semplice soluzione alternativa che ho trovato è stata quella di modificare la tabella temporanea nella mia stored procedure in una variabile di tabella. Funziona esattamente come con una tabella temporanea, ma non è più in conflitto con il mio altro inserto di tabella temporanea.

Tanto per concludere il commento so che alcuni di voi stanno per scrivere, mettendomi in guardia dalle variabili di tabella come killer delle prestazioni ... Tutto quello che posso dirvi è che nel 2020 paga i dividendi non aver paura delle variabili di tabella. Se questo era il 2008 e il mio database era ospitato su un server con 16 GB di RAM e funzionava con HDD da 5400 RPM, potrei essere d'accordo con te. Ma è il 2020 e ho un array SSD come memoria principale e centinaia di GB di RAM. Potrei caricare l'intero database della mia azienda su una variabile di tabella e avere ancora molta RAM da risparmiare.

Le variabili di tabella sono tornate nel menu!


1

Ho avuto lo stesso problema e preoccupazione per il codice duplicato in due o più sproc. Ho finito per aggiungere un attributo aggiuntivo per "mode". Ciò ha permesso che il codice comune esistesse all'interno di uno sproc e il flusso diretto dalla modalità e il set di risultati dello sproc.


1

che ne dici di memorizzare l'output nella tabella statica? Piace

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

non è l'ideale, ma è così semplice e non è necessario riscrivere tutto.

AGGIORNAMENTO : la soluzione precedente non funziona bene con query parallele (accesso asincrono e multiutente) quindi ora sto usando tabelle temporanee

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

spGetDatacontenuto della stored procedure annidata

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

In genere, non è possibile creare uno SProc ad-hoc come è possibile con le tabelle. Avrai bisogno di espandere il tuo esempio con più riferimenti, poiché questo approccio non è conosciuto o accettato facilmente. Inoltre, assomiglia più a un'espressione Lambda che a un'esecuzione SProc, che ANSI-SQL non consente gli approcci Lambda Expression.
GoldBishop

Funziona ma ho scoperto che non funziona bene nemmeno con le query parallele (accessi asincroni e multiutente). Pertanto ora sto usando l'approccio della tabella temporanea. Ho aggiornato la mia risposta.
Muflix

1
La logica della tabella Temp è buona, era il riferimento SProc che mi interessava. Non è possibile interrogare direttamente Sproc's. È possibile eseguire query direttamente sulle funzioni con valori di tabella. Deve come hai accennato nella tua logica aggiornata, l'approccio migliore è una tabella temporanea, una sessione, un'istanza o globale e funziona da quel punto.
GoldBishop

0

Dichiara una variabile del cursore di output alla sp interna:

@c CURSOR VARYING OUTPUT

Quindi dichiarare un cursore c alla selezione che si desidera restituire. Quindi apri il cursore. Quindi imposta il riferimento:

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

NON chiudere o riallocare.

Ora chiama la sp interna da quella esterna fornendo un parametro del cursore come:

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

Una volta eseguito lo sp interno, sei @cOUTpronto per il recupero. Effettua il loop, quindi chiudi e dealloca.


0

Se sei in grado di utilizzare altre tecnologie associate come C #, ti suggerisco di utilizzare il comando SQL integrato con il parametro Transaction.

var sqlCommand = new SqlCommand(commandText, null, transaction);

Ho creato una semplice app console che dimostra questa capacità che può essere trovata qui: https://github.com/hecked12/SQL-Transaction-Using-C-Sharp

In breve, C # ti consente di superare questa limitazione in cui puoi ispezionare l'output di ciascuna stored procedure e utilizzare quell'output come preferisci, ad esempio puoi alimentarlo a un'altra stored procedure. Se l'output è ok, puoi eseguire il commit della transazione, altrimenti puoi annullare le modifiche utilizzando rollback.


-1

Su SQL Server 2008 R2, ho riscontrato una mancata corrispondenza nelle colonne della tabella che ha causato l'errore di rollback. È andato via quando ho corretto la mia variabile di tabella sqlcmd popolata dall'istruzione insert-exec in modo che corrispondesse a quella restituita dal proc memorizzato. Mancava il codice_org. In un file cmd di Windows, carica il risultato della stored procedure e lo seleziona.

set SQLTXT= declare @resets as table (org_id nvarchar(9), org_code char(4), ^
tin(char9), old_strt_dt char(10), strt_dt char(10)); ^
insert @resets exec rsp_reset; ^
select * from @resets;

sqlcmd -U user -P pass -d database -S server -Q "%SQLTXT%" -o "OrgReport.txt"

OP chiedeva informazioni su un errore che si verifica quando si utilizzano istruzioni insert-exec in stored procedure annidate. Il problema restituiva un errore diverso, ad esempio "L'elenco di selezione per l'istruzione INSERT contiene meno elementi rispetto all'elenco di inserimento. Il numero di valori SELECT deve corrispondere al numero di colonne INSERT".
Losbear

Questo è più un avvertimento che è possibile ricevere questo messaggio in modo errato.
user3448451
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.