오류 : "INSERT EXEC 문은 중첩 될 수 없습니다." 및 "INSERT-EXEC 문 내에서 ROLLBACK 문을 사용할 수 없습니다." 이것을 해결하는 방법?


98

나는 세 가지 저장 프로 시저를 가지고 Sp1, Sp2하고 Sp3.

첫 번째 항목 ( Sp1)은 두 번째 항목 ( )을 실행하고 Sp2반환 된 데이터를에 저장 @tempTB1하고 두 번째 항목은 세 번째 항목 ( Sp3)을 실행하고 데이터를에 저장합니다 @tempTB2.

실행 Sp2하면 작동하고에서 내 모든 데이터를 반환 Sp3하지만 문제는에 있습니다. Sp1실행하면 다음 오류가 표시됩니다.

INSERT EXEC 문은 중첩 될 수 없습니다.

장소를 변경하려고했는데 execute Sp2또 다른 오류가 표시됩니다.

INSERT-EXEC 문 내에서 ROLLBACK 문을 사용할 수 없습니다.

답변:


100

이것은 저장 프로 시저 체인에서 데이터를 '버블 링'하려고 할 때 흔히 발생하는 문제입니다. SQL Server의 제한 사항은 한 번에 하나의 INSERT-EXEC 만 활성화 할 수 있다는 것입니다. 이러한 유형의 문제를 해결하기 위해 패턴에 대한 매우 철저한 문서 인 저장 프로 시저간에 데이터를 공유하는 방법을 살펴 보는 것이 좋습니다 .

예를 들어 해결 방법은 Sp3를 테이블 반환 함수로 바꾸는 것입니다.


1
끊어진 링크 또는 응답하지 않는 사이트.
SouravA

6
그것을 허용하지 않는 기술적 이유가 무엇인지 아십니까? 이것에 대한 정보를 찾을 수 없습니다.
jtate

1
불행히도 이것은 종종 옵션이 아닙니다. 많은 유형의 중요한 정보는 시스템 저장 프로 시저 에서만 안정적으로 사용할 있습니다 (특정 경우에 각 관리 뷰에 신뢰할 수 없거나 쓸모없는 데이터가 포함되어 있기 때문입니다. 예는에서 반환 된 정보입니다 ). sp_help_jobactivity
GSerg

21

이것은 거대한 복잡한 생성 함수 나 실행 된 SQL 문자열 호출없이 SQL Server에서이 작업을 수행하는 유일한 "간단한"방법입니다. 둘 다 끔찍한 솔루션입니다.

  1. 임시 테이블 생성
  2. 저장 프로 시저 데이터를 저장하십시오.

예:

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

참고 : 반드시 'set fmtonly off'를 사용해야하며, 저장 프로 시저 매개 변수가 포함 된 문자열 또는 테이블 이름에 대해 openrowset 호출 내에서 동적 SQL을 추가 할 수 없습니다. 그렇기 때문에 대부분의 경우 임시 테이블을 수행하므로 테이블 변수 대신 임시 테이블을 사용해야합니다.


SET FMTONLY OFF를 반드시 사용해야하는 것은 아닙니다. 프로 시저가 일반적으로 반환하는 것과 동일한 데이터 유형을 가진 빈 테이블을 반환하는 IF (1 = 0)를 추가 할 수 있습니다.
Guillermo Gutiérrez 2013 년

1
임시 테이블과 테이블 변수는 데이터를 다르게 저장합니다. 쿼리 최적화 프로그램이 테이블 변수에 대한 통계를 유지하지 않으므로 테이블 변수는 작은 결과 집합에 사용됩니다. 따라서 큰 데이터 세트의 경우 거의 항상 임시 테이블을 사용하는 것이 좋습니다. 여기에 좋은 블로그 기사가 있습니다. mssqltips.com/sqlservertip/2825/…
gh9

@ gh9 예, 그러나 이것은 어쨌든 큰 결과 세트에 대한 끔찍한 아이디어입니다. 임시 데이터베이스에있는 실제 테이블의 통계 및 사용으로 인해 상당한 오버 헤드가 발생할 수 있습니다. 현재 값 1 행 (여러 테이블 쿼리)이있는 레코드 집합을 반환하는 프로 시저와이를 테이블 변수에 저장하고 동일한 형식의 다른 테이블 값과 비교하는 프로 시저가 있습니다. 임시 테이블에서 테이블 변수로 변경하면 평균 시간이 8ms에서 2ms로 빨라졌습니다. 이는 하루 동안 초당 여러 번 호출하고 야간 프로세스에서는 100,000 번 호출 할 때 중요합니다.
제이슨 Goemaat

통계가 테이블 변수에 유지되기를 원하는 이유는 무엇입니까? 요점은 RAM에 쿼리가 완료된 후 삭제 될 임시 테이블을 만드는 것입니다. 정의에 따라 이러한 테이블에 생성 된 통계는 사용되지 않습니다. 일반적으로 테이블 변수의 데이터가 가능한 한 RAM에 남아 있다는 사실은 데이터가 SQL Server에서 사용할 수있는 RAM의 양 (요즘에는 SQL에 대한 100GB 이상의 메모리 풀의 양보다 작은 시나리오)에서 임시 테이블보다 빠르게 만듭니다. 서버는 거의 항상)
Geoff Griswald

하지만 확장 저장 프로 시저에서는 작동하지 않습니다. 오류는 'EXECUTE <procedurename> @retval OUTPUT'in procedure ... '문이 확장 저장 프로 시저를 호출하므로 메타 데이터를 확인할 수 없습니다 .
GSerg

11

좋습니다. jimhark의 권장 사항은 이전 단일 해시 테이블 접근 방식의 예입니다.-

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

*/

이 해결 방법도 사용했습니다. 아이디어에 감사드립니다!
SQL_Guy 19

환상적인 해결 방법. 이것은 임시 테이블 범위 지정에 대해 더 많이 배우는 데 도움이되었습니다. 예를 들어, 외부에서 선언 된 경우 dynsql 문자열에서 임시 테이블을 사용할 수 있다는 것을 몰랐습니다. 여기에 비슷한 개념이 있습니다. 감사합니다.
jbd

9

이 문제에 대한 나의 해결 방법은 항상 단일 해시 임시 테이블이 호출 된 모든 procs의 범위 내에 있다는 원칙을 사용하는 것입니다. 따라서 proc 매개 변수에 옵션 스위치가 있습니다 (기본값은 off로 설정 됨). 이것이 켜져 있으면 호출 된 proc은 호출하는 proc에서 생성 된 임시 테이블에 결과를 삽입합니다. 과거에는 한 단계 더 나아가 호출 된 proc에 코드를 넣어 단일 해시 테이블이 범위에 있는지 확인하고 코드를 삽입하고 그렇지 않으면 결과 집합을 반환한다고 생각합니다. 잘 작동하는 것 같습니다-procs간에 대용량 데이터 세트를 전달하는 가장 좋은 방법입니다.


1
나는이 대답을 좋아하고 당신이 제공하고 모범을 보이면 더 많은 찬성표를 얻을 것이라고 장담합니다.
jimhark

나는이 일을 수년 동안 해왔습니다. 그래도 SQL Azure에서 여전히 필요합니까?
Nick Allan

6

이 트릭은 저에게 효과적입니다.

원격 서버에서는이 문제가 없습니다. 원격 서버에서는 마지막 삽입 명령이 이전 명령의 결과가 실행되기를 기다리기 때문입니다. 동일한 서버에서는 그렇지 않습니다.

해결 방법을 위해 해당 상황을 활용하십시오.

연결된 서버를 생성 할 수있는 권한이 있다면 그렇게하십시오. 연결된 서버와 동일한 서버를 만듭니다.

  • SSMS에서 서버에 로그인하십시오.
  • "서버 개체
  • "연결된 서버"를 마우스 오른쪽 버튼으로 클릭 한 다음 "새 연결 서버"를 클릭합니다.
  • 대화 상자에서 연결된 서버의 이름을 지정하십시오. 예 : THISSERVER
  • 서버 유형은 "기타 데이터 소스"입니다.
  • 공급자 : SQL 서버용 Microsoft OLE DB 공급자
  • 데이터 소스 : IP, 점 (.) 일 수도 있습니다. 로컬 호스트이기 때문입니다.
  • "보안"탭으로 이동하여 세 번째 "로그인의 현재 보안 컨텍스트를 사용하여 작성"을 선택하십시오.
  • 원하는 경우 서버 옵션 (세 번째 탭)을 편집 할 수 있습니다.
  • 확인을 누르면 연결된 서버가 생성됩니다.

이제 SP1의 Sql 명령은

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

저를 믿으십시오. SP2에 동적 삽입이 있어도 작동합니다.


4

해결 방법은 제품 중 하나를 테이블 값 함수로 변환하는 것입니다. 나는 그것이 항상 가능한 것은 아니라는 것을 깨닫고 그 자체의 한계를 도입합니다. 그러나 나는 항상 적어도 하나의 절차가 이에 적합한 후보임을 찾을 수있었습니다. 저는이 솔루션을 좋아합니다. 솔루션에 "핵"을 도입하지 않기 때문입니다.


하지만 단점은 함수가 복잡한 경우 예외 처리에 문제가 있다는 것입니다.
Muflix

2

Stored Proc의 결과를 임시 테이블로 가져 오려고 할 때이 문제가 발생했으며 해당 Stored Proc가 자체 작업의 일부로 임시 테이블에 삽입되었습니다. 문제는 SQL Server가 동일한 프로세스가 동시에 두 개의 다른 임시 테이블에 쓰는 것을 허용하지 않는다는 것입니다.

허용되는 OPENROWSET 답변은 제대로 작동하지만 프로세스에서 동적 SQL 또는 외부 OLE 공급자를 사용하지 않아야했기 때문에 다른 경로를 사용했습니다.

내가 찾은 쉬운 해결 방법 중 하나는 저장 프로 시저의 임시 테이블을 테이블 변수로 변경하는 것입니다. 임시 테이블과 똑같이 작동하지만 더 이상 다른 임시 테이블 삽입과 충돌하지 않습니다.

코멘트를 끝내기 위해 저는 여러분 중 일부가 성능 킬러로서 테이블 변수를 경고하면서 저에게 경고를 작성하려고한다는 것을 알고 있습니다. 제가 말할 수있는 것은 2020 년 에 테이블 변수를 두려워 하지 않도록 배당금을 지불 한다는 것입니다. 이것이 2008 년이고 내 데이터베이스가 16GB RAM과 5400RPM HDD로 실행되는 서버에서 호스팅 되었다면 동의 할 수 있습니다. 하지만 2020 년이고 SSD 어레이를 기본 스토리지로 사용하고 수백 기가 RAM을 사용합니다. 회사 전체의 데이터베이스를 테이블 변수에로드 할 수 있지만 여전히 충분한 RAM을 확보 할 수 있습니다.

테이블 변수가 메뉴로 돌아 왔습니다!


1

두 개 이상의 sproc에서 중복 코드에 대해 동일한 문제와 우려가있었습니다. 결국 "모드"에 대한 추가 속성을 추가했습니다. 이것은 공통 코드가 하나의 sproc 내부에 존재하고 모드 지향 흐름과 sproc의 결과 집합을 허용했습니다.


1

출력을 정적 테이블에 저장하는 것은 어떻습니까? 처럼

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

이상적이지는 않지만 너무 간단하고 모든 것을 다시 작성할 필요가 없습니다.

업데이트 : 이전 솔루션은 병렬 쿼리 (비동기 및 다중 사용자 액세스)에서 잘 작동하지 않으므로 이제 임시 테이블을 사용합니다.

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

중첩 된 spGetData저장 프로 시저 콘텐츠

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

일반적으로 테이블에서 할 수있는 것처럼 SProc 임시를 만들 수 없습니다. 이 접근 방식은 실제로 쉽게 알려 지거나 수용되지 않으므로 더 많은 참조를 통해 예제를 확장해야합니다. 또한 ANSI-SQL에서 Lambda 식 접근 방식을 허용하지 않는 SProc 실행보다 Lambda 식과 비슷합니다.
GoldBishop 2017-04-27

작동하지만 병렬 쿼리 (비동기 및 다중 사용자 액세스)에서도 잘 작동하지 않는다는 것을 알았습니다. 따라서 이제 임시 테이블 접근 방식을 사용하고 있습니다. 내 대답을 업데이트했습니다.
Muflix

1
Temp 테이블 논리는 훌륭합니다. 제가 염려했던 SProc 참조였습니다. Sproc은 본질적으로 직접 쿼리 할 수 ​​없습니다. 테이블 반환 함수는 직접 쿼리 할 수 ​​있습니다. 업데이트 된 논리에서 언급했듯이 가장 좋은 접근 방식은 임시 테이블, 세션, 인스턴스 또는 전역이며 해당 지점에서 작동합니다.
GoldBishop

0

내부 sp에 출력 커서 변수를 선언합니다.

@c CURSOR VARYING OUTPUT

그런 다음 리턴하려는 선택 항목에 커서 c를 선언하십시오. 그런 다음 커서를 엽니 다. 그런 다음 참조를 설정하십시오.

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

닫거나 재할 당하지 마십시오.

이제 다음과 같이 커서 매개 변수를 제공하는 외부 sp에서 내부 sp를 호출합니다.

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

내부 sp가 실행되면 @cOUT가져올 준비가 된 것입니다. 루프 한 다음 닫고 할당을 취소합니다.


0

C #과 같은 다른 관련 기술을 사용할 수 있다면 Transaction 매개 변수와 함께 기본 제공 SQL 명령을 사용하는 것이 좋습니다.

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

이 기능을 보여주는 간단한 콘솔 앱을 만들었습니다. https://github.com/hecked12/SQL-Transaction-Using-C-Sharp

간단히 말해 C #을 사용하면 각 저장 프로 시저의 출력을 검사하고 원하는 방식으로 해당 출력을 사용할 수있는 이러한 제한을 극복 할 수 있습니다. 예를 들어 다른 저장 프로 시저에이를 공급할 수 있습니다. 출력이 정상이면 트랜잭션을 커밋 할 수 있고 그렇지 않으면 롤백을 사용하여 변경 사항을 되돌릴 수 있습니다.


-1

SQL Server 2008 R2에서 롤백 오류를 일으킨 테이블 열이 일치하지 않습니다. 저장된 proc에서 반환 한 것과 일치하도록 insert-exec 문으로 채워진 sqlcmd 테이블 변수를 수정했을 때 사라졌습니다. org_code가 누락되었습니다. Windows cmd 파일에서 저장 프로 시저의 결과를로드하고 선택합니다.

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는 중첩 저장 프로 시저에서 insert-exec 문을 사용할 때 발생하는 오류에 대해 묻습니다. 문제는 "INSERT 문의 선택 목록에 삽입 목록보다 적은 항목이 포함되어 있습니다. SELECT 값의 수는 INSERT 열의 수와 일치해야합니다."와 같은 다른 오류를 반환합니다.
Losbear

이것은이 메시지를 잘못받을 수 있다는 경고에 가깝습니다.
user3448451
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.