Błędy: „Nie można zagnieżdżać instrukcji INSERT EXEC”. i „Nie można użyć instrukcji ROLLBACK w instrukcji INSERT-EXEC”. Jak to rozwiązać?

98

Mam trzy procedury przechowywane Sp1, Sp2i Sp3.

Pierwsza ( Sp1) wykona drugą ( Sp2) i zapisze zwrócone dane do, @tempTB1a druga wykona trzecią ( Sp3) i zapisze dane do @tempTB2.

Jeśli wykonam Sp2to zadziała i zwróci mi wszystkie moje dane z Sp3, ale problem tkwi w Sp1, gdy wykonam to wyświetli ten błąd:

Instrukcja INSERT EXEC nie może być zagnieżdżona

Próbowałem zmienić miejsce execute Sp2i wyświetla mi się kolejny błąd:

Nie można użyć instrukcji ROLLBACK w instrukcji INSERT-EXEC.

HAJJAJ
źródło

Odpowiedzi:

102

Jest to częsty problem podczas próby „bąbelkowania” danych z łańcucha procedur składowanych. Ograniczenie w SQL Server polega na tym, że w danym momencie może być aktywna tylko jedna instrukcja INSERT-EXEC. Polecam zapoznać się z artykułem Jak udostępniać dane między procedurami składowanymi, który jest bardzo dokładnym artykułem na temat wzorców obejścia tego typu problemu.

Na przykład obejściem może być przekształcenie Sp3 w funkcję wartościowaną w tabeli.

eddiegroves
źródło
1
uszkodzony link LUB nieodpowiadająca witryna.
SouravA
6
czy masz pojęcie, jaki jest techniczny powód, dla którego na to nie pozwalasz? Nie mogę znaleźć żadnych informacji na ten temat.
jtate
1
Niestety bardzo często nie jest to możliwe. Wiele typów ważnych informacji jest niezawodnie dostępnych tylko w procedurach składowanych w systemie (ponieważ w niektórych przypadkach odpowiedni widok zarządzania zawiera niewiarygodne / nieaktualne dane; przykładem są informacje zwrócone przez sp_help_jobactivity).
GSerg
21

To jedyny „prosty” sposób na zrobienie tego w SQL Server bez jakiejś gigantycznej, zawiłej utworzonej funkcji lub wykonanego wywołania ciągu sql, z których oba są okropnymi rozwiązaniami:

  1. utwórz tabelę tymczasową
  2. openrowset w nim dane procedury składowanej

PRZYKŁAD:

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

Uwaga : MUSISZ użyć opcji „set fmtonly off”, ORAZ NIE MOŻESZ dodać do tego dynamicznego sql ani w wywołaniu openrowset, ani dla ciągu zawierającego parametry procedury składowanej, ani dla nazwy tabeli. Dlatego musisz używać tabeli tymczasowej zamiast zmiennych tabeli, co byłoby lepsze, ponieważ w większości przypadków wykonuje ona tabelę tymczasową.

Mitch Stokely
źródło
Nie jest konieczne używanie SET FMTONLY OFF. Możesz po prostu dodać IF (1 = 0), które zwraca pustą tabelę z tymi samymi typami danych, które normalnie zwraca procedura.
Guillermo Gutiérrez,
1
Tabele tymczasowe i zmienne tabel przechowują dane w inny sposób. Zmienne tabel powinny być używane dla małych zestawów wyników, ponieważ optymalizator zapytań nie utrzymuje statystyk dotyczących zmiennych tabeli. Dlatego w przypadku dużych zestawów danych prawie zawsze lepiej jest używać tabel tymczasowych. Oto fajny artykuł na ten temat na blogu mssqltips.com/sqlservertip/2825/…
gh9
@ gh9 tak, ale i tak jest to okropny pomysł dla dużych zestawów wyników. Statystyki i użycie rzeczywistej tabeli w tymczasowej bazie danych może spowodować znaczne obciążenie. Mam procedurę, która zwraca zestaw rekordów z 1 wierszem bieżących wartości (przeszukiwanie kilku tabel) oraz procedurę, która przechowuje to w zmiennej tabeli i porównuje z wartościami w innej tabeli o tym samym formacie. Zmiana z tabeli tymczasowej na zmienną tabeli przyspieszyła średni czas z 8 ms do 2 ms, co jest ważne, gdy jest wywoływana kilka razy na sekundę w ciągu dnia i 100 000 razy w procesie nocnym.
Jason Goemaat
Dlaczego chcesz, aby statystyki były utrzymywane w zmiennej tabeli? Chodzi o to, aby utworzyć tymczasową tabelę w pamięci RAM, która zostanie zniszczona po zakończeniu zapytania. Z definicji żadne statystyki utworzone dla takiej tabeli nigdy nie zostałyby użyte. Ogólnie rzecz biorąc, fakt, że dane w zmiennej tabeli pozostają w pamięci RAM, gdziekolwiek jest to możliwe, sprawia, że ​​są one szybsze niż tabele tymczasowe w każdym scenariuszu, w którym dane są mniejsze niż ilość pamięci RAM dostępnej dla SQL Server (która w dzisiejszych czasach wynosi ponad 100 GB puli pamięci dla naszego SQL Serwery, jest prawie zawsze)
Geoff Griswald
Nie działa to jednak w przypadku rozszerzonych procedur składowanych. Błąd: Nie można określić metadanych, ponieważ instrukcja „EXECUTE <nazwaprocedury> @retval OUTPUT” w procedurze ... ”wywołuje rozszerzoną procedurę składowaną .
GSerg
11

OK, zachęcony przez jimharka, oto przykład starego podejścia z pojedynczą tabelą mieszania: -

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

*/
Matt Luckham
źródło
Użyłem również tego obejścia. Dziękuję za pomysł!
SQL_Guy,
Fantastyczne obejście. Pomogło mi to dowiedzieć się więcej o określaniu zakresu tabeli tymczasowej. EG, nie zdawałem sobie sprawy, że możesz użyć tabeli tymczasowej w ciągu dynsql, jeśli została zadeklarowana poza nim. Podobna koncepcja tutaj. Wielkie dzięki.
jbd
9

Moje obejście tego problemu zawsze polegało na stosowaniu zasady, że pojedyncze tabele tymczasowe z mieszaniem są objęte zakresem wszystkich nazwanych procesów. Więc mam przełącznik opcji w parametrach proc (domyślnie wyłączony). Jeśli ta opcja jest włączona, wywołany proc wstawi wyniki do tabeli tymczasowej utworzonej w procesie wywołującym. Myślę, że w przeszłości poszedłem o krok dalej i umieściłem kod w wywołanym procencie, aby sprawdzić, czy pojedyncza tabela skrótów istnieje w zakresie, jeśli tak, to wstaw kod, w przeciwnym razie zwróć zestaw wyników. Wydaje się, że działa dobrze - najlepszy sposób na przekazywanie dużych zestawów danych między procesami.

Matt Luckham
źródło
1
Podoba mi się ta odpowiedź i założę się, że dostaniesz więcej głosów, jeśli podasz przykład.
jimhark
Robię to od lat. Czy jest to jednak nadal konieczne w SQL Azure?
Nick Allan
6

Ta sztuczka mi pasuje.

Nie masz tego problemu na serwerze zdalnym, ponieważ na serwerze zdalnym ostatnie polecenie wstawiania czeka na wykonanie poprzedniego polecenia. Nie dotyczy to tego samego serwera.

Skorzystaj z tej sytuacji, aby obejść ten problem.

Jeśli masz uprawnienia do tworzenia serwera połączonego, zrób to. Utwórz ten sam serwer, co serwer połączony.

  • w SSMS zaloguj się do swojego serwera
  • przejdź do „Server Object
  • Kliknij prawym przyciskiem myszy „Połączone serwery”, a następnie „Nowy połączony serwer”
  • w oknie dialogowym podaj dowolną nazwę połączonego serwera: np .: THISSERVER
  • typ serwera to „Inne źródło danych”
  • Dostawca: Dostawca Microsoft OLE DB dla serwera SQL
  • Źródło danych: Twój adres IP, może to być również kropka (.), Ponieważ jest to localhost
  • Przejdź do zakładki „Bezpieczeństwo” i wybierz trzecią opcję „Wykonaj przy użyciu bieżącego kontekstu zabezpieczeń logowania”
  • Jeśli chcesz, możesz edytować opcje serwera (trzecia zakładka)
  • Naciśnij OK, twój serwer połączony został utworzony

teraz twoje polecenie Sql w dodatku SP1 to

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

Uwierz mi, to działa nawet jeśli masz dynamiczną wstawkę w SP2

ainasiart
źródło
4

Znalazłem rozwiązanie polegające na przekształceniu jednego z produktów w funkcję wartościowaną w tabeli. Zdaję sobie sprawę, że nie zawsze jest to możliwe i wprowadza własne ograniczenia. Jednak zawsze przynajmniej jedna z procedur była do tego dobrym kandydatem. Podoba mi się to rozwiązanie, ponieważ nie wprowadza żadnych „hacków” do rozwiązania.

Roman K.
źródło
ale wadą jest problem z obsługą wyjątków, jeśli funkcja jest złożona, prawda?
Muflix
2

Napotkałem ten problem podczas próby zaimportowania wyników Stored Proc do tabeli tymczasowej, a ten Stored Proc został wstawiony do tabeli tymczasowej jako część własnej operacji. Problem polega na tym, że SQL Server nie pozwala temu samemu procesowi na zapis do dwóch różnych tabel tymczasowych w tym samym czasie.

Zaakceptowana odpowiedź OPENROWSET działa dobrze, ale musiałem uniknąć używania w moim procesie dowolnego Dynamic SQL lub zewnętrznego dostawcy OLE, więc poszedłem inną drogą.

Jednym z łatwych obejść, które znalazłem, była zmiana tabeli tymczasowej w mojej procedurze składowanej na zmienną tabeli. Działa dokładnie tak samo, jak w przypadku tabeli tymczasowej, ale nie powoduje już konfliktu z inną wstawką do tabeli tymczasowej.

Żeby zapomnieć o komentarzu, wiem, że kilku z was ma zamiar napisać, ostrzegając mnie przed zmiennymi tabeli jako zabójcami wydajności ... Mogę wam tylko powiedzieć, że w 2020 roku opłaca się nie bać się zmiennych tabeli. Gdyby to był rok 2008, a moja baza danych była hostowana na serwerze z 16 GB pamięci RAM i dyskami twardymi 5400 obr./min, mógłbym się z tobą zgodzić. Ale jest rok 2020 i mam macierz SSD jako moją podstawową pamięć masową i setki gigabajtów pamięci RAM. Mógłbym załadować całą bazę danych mojej firmy do zmiennej tabeli i nadal mieć dużo wolnego miejsca w pamięci RAM.

Zmienne tabeli wróciły do ​​menu!

Geoff Griswald
źródło
1

Miałem ten sam problem i niepokój związany z powieleniem kodu w dwóch lub więcej sprocesach. Skończyło się na dodaniu dodatkowego atrybutu dla „trybu”. Pozwoliło to na istnienie wspólnego kodu wewnątrz jednego sproc oraz przepływu skierowanego w trybie i zbioru wyników sproc.

phoenixAZ
źródło
1

co powiesz na zapisanie wyniku w tabeli statycznej? Lubić

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

nie jest to idealne rozwiązanie, ale jest tak proste i nie trzeba wszystkiego przepisywać.

AKTUALIZACJA : poprzednie rozwiązanie nie działa dobrze z równoległymi zapytaniami (asynchroniczne i dostęp dla wielu użytkowników), dlatego teraz używam tabel tymczasowych

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

spGetDatazawartość zagnieżdżonej procedury składowanej

-- 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
Muflix
źródło
Ogólnie rzecz biorąc, nie można utworzyć SProc ad-hoc, tak jak w przypadku tabel. Będziesz musiał rozszerzyć swój przykład o więcej odniesień, ponieważ takie podejście nie jest tak łatwo znane ani akceptowane. Ponadto bardziej przypomina Lambda Expression niż wykonanie SProc, na które ANSI-SQL nie pozwala na podejście Lambda Expression.
GoldBishop
Działa, ale stwierdziłem, że nie działa dobrze z zapytaniami równoległymi (asynchroniczne i dostępami dla wielu użytkowników). Dlatego teraz używam metody tabeli tymczasowej. Zaktualizowałem odpowiedź.
Muflix
1
Logika tabeli Temp jest dobra, martwiłem się o referencję SProc. Ze Sproc nie można bezpośrednio zapytać. Funkcje wartościowane w tabeli mogą być bezpośrednio sprawdzane z. Musi być tak, jak wspomniałeś w zaktualizowanej logice, najlepszym podejściem jest tabela tymczasowa, sesja, instancja lub globalna i działaj od tego momentu.
GoldBishop
0

Zadeklaruj wyjściową zmienną kursora do wewnętrznego sp:

@c CURSOR VARYING OUTPUT

Następnie zadeklaruj kursor c do zaznaczenia, które chcesz zwrócić. Następnie otwórz kursor. Następnie ustaw odniesienie:

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

NIE zamykaj ani nie przydzielaj ponownie.

Teraz wywołaj wewnętrzne sp z zewnętrznego, podając parametr kursora, taki jak:

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

Po wykonaniu wewnętrznego sp, twój @cOUTjest gotowy do pobrania. Zapętl, a następnie zamknij i zwolnij przydział.

Stefanos Zilellis
źródło
0

Jeśli potrafisz korzystać z innych powiązanych technologii, takich jak C #, sugeruję użycie wbudowanego polecenia SQL z parametrem Transaction.

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

Stworzyłem prostą aplikację konsolową, która demonstruje tę umiejętność, którą można znaleźć tutaj: https://github.com/hecked12/SQL-Transaction-Using-C-Sharp

Krótko mówiąc, C # pozwala przezwyciężyć to ograniczenie, w którym można sprawdzić dane wyjściowe każdej procedury składowanej i użyć ich w dowolny sposób, na przykład można je przekazać do innej procedury składowanej. Jeśli wynik jest w porządku, możesz zatwierdzić transakcję, w przeciwnym razie możesz cofnąć zmiany za pomocą wycofywania.

spidernet12
źródło
-1

W SQL Server 2008 R2 wystąpiła niezgodność w kolumnach tabeli, która spowodowała błąd wycofywania zmian. To zniknęło, kiedy naprawiłem moją zmienną tabeli sqlcmd wypełnioną instrukcją insert-exec, aby pasowała do tej zwróconej przez przechowywany proc. Brakowało kodu_organizacji. W pliku cmd systemu Windows ładuje wynik procedury składowanej i wybiera go.

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"
user3448451
źródło
OP pytał o błąd, który występuje podczas używania instrukcji insert-exec w zagnieżdżonych procedurach składowanych. Twój problem zwróciłby inny błąd, na przykład „Lista wyboru dla instrukcji INSERT zawiera mniej elementów niż lista wstawiania. Liczba wartości SELECT musi odpowiadać liczbie kolumn INSERT”.
Losbear
Jest to raczej ostrzeżenie, że możliwe jest błędne otrzymanie tej wiadomości.
user3448451