Обходим ошибку An INSERT EXEC statement cannot be nested

by Alexey Knyazev 18. сентября 2011 00:09

В SQL Server есть ограничение на инструкцию INSERT EXEC - она не может быть вложенной. Т.е. если в теле процедуры мы уже используем код INSERT EXEC, то рекордсет из этой процедуры мы не сможем вставить в таблицу. На Microsoft Connect есть фитбек с этой проблемой (Cannot have nested INSERT ... EXEC) и совсем недавно эту проблему закрыли с пометкой as Won't Fix.

Но, что делать, если нам все-таки необходимо вывести результат работы процедуры в таблицу? Именно тому, как обойти одно из ограничений сиквела и посвящён этот пост.

Для иллюстрации создадим тестовую БД TestDB и две процедуры, одна будет возвращать небольшой рекордсет, а во второй мы будем вызывать эту процедуру с использованием инструкции INSERT EXEC.

create database TestDB
go

use TestDB
go

if object_id ( 'dbo.TestProc01', 'P' ) is not null
  drop procedure dbo.TestProc01 
go

create procedure dbo.TestProc01
as
set nocount on
declare @t table ( i int )
insert into @t
values (1),(2),(3)
select * from @t
go

if object_id ( 'dbo.TestProc02', 'P' ) is not null
  drop procedure dbo.TestProc02 
go

create procedure dbo.TestProc02
as
set nocount on
declare @t table ( i int )
insert into @t
exec dbo.TestProc01
select * from @t
go

Дальше небольшой скрипт, который и эмулирует, озвученную выше, ошибку:
declare @t table ( i int )
insert into @t
exec dbo.TestProc02



А теперь несколько способов обойти эту ошибку:

1) Первый и самый правильный
По возможности не использовать вложенных инструкций INSERT EXEC, либо вместо вызова процедуры, в которой уже используется такая конструкция, взять часть скрипта из тела этой самой процедуры. Как правило, это достаточно легко можно реализовать. В моём примере достаточно заменить вызов процедуры dbo.TestProc02 на dbo.TestProc01.

2) Используем OPENQUERY или OPENROWSET
Для этого нам потребуется создать Linked Server с ссылкой нашего сервера БД на самого себя ( в моём случае это IP 127.0.0.1).
use master
go

exec sp_addlinkedserver N'127.0.0.1'
                      , N'SQL Server';
go

use TestDB
go

declare @t table ( i int )
insert into @t
select * from OpenQuery ( [127.0.0.1], 'TestDB.dbo.TestProc02' )
select * from @t
3) Используем распределённый запрос
declare @t table ( i int )
insert into @t
exec [127.0.0.1].TestDB.dbo.TestProc02
select * from @t
go
--либо:
declare @t table ( i int )
insert into @t
exec ( 'TestDB.dbo.TestProc02' ) at [127.0.0.1]
select * from @t
go
Не забываем включить службу Координатор распределенных транзакций Иначе получим ошибку:



4) Используем процедуру xp_cmdshell и утилиту SQLCMD
Но для начала включим использование процедуры xp_cmdshell
exec sp_configure 'show advanced options', 1
reconfigure
exec sp_configure 'xp_cmdshell', 1
reconfigure
go
А теперь выгружаем результат работы процедуры dbo.TestProc02 в таблицу:
declare @t table ( val varchar(100) )
insert into @t
exec master..xp_cmdshell 'sqlcmd -E -q "exec TestDB.dbo.TestProc02" -h -1 -W'

select val from @t
where val is not null
5) Используем процедуру xp_cmdshell и утилиту BCP
--Выгружаем результат процедуры dbo.TestProc02 на диск
exec xp_cmdshell 'bcp "exec TestDB.dbo.TestProc02" queryout "c:\temp\Test.txt" -T -c -C RAW -r\n -t\char(3)'
--Создадим таблицу для получения результата 
if object_id ( 'dbo.tmpMyResult', 'U' ) is not null
drop table tmpMyResult 
go 
create table tmpMyResult ( val int )
--Загружаем результат с диска в таблицу
exec xp_cmdshell 'bcp TestDB.dbo.tmpMyResult in "c:\temp\Test.txt" -T -c -C RAW -r\n -t\char(3)'  
--Смотрим
select * from tmpMyResult
6) Используем CLR
Но этот вариант я не буду рассматривать в рамках этого поста.

Tags:

SQL Server

Комментарии (1) -

MikeCrazy
MikeCrazy Russia
06.12.2012 12:34:14 #

-- ФОРМАТ ПРОЦЕДУРЫ НА ЗАМЕНУ ТАБЛИЧНОЙ ФУНКЦИИ
CREATE PROCEDURE [SP_NAME](<CommonArgList>, @TmpTableId INT = 0)
AS BEGIN

      CREATE TABLE #result_table (<SP_NAME_RESULT_FIELDS>)

      IF isnull(@TmpTableId,0) = 0 BEGIN
            SELECT DISTINCT * FROM #result_table
      END ELSE BEGIN
        declare @sql as varchar(max)
            IF not exists(SELECT [name] FROM tempdb..sysobjects WHERE   id=@TmpTableId ) BEGIN
                  set @sql = 'select ''error'' '
                  SELECT
                        @sql =      'select DISTINCT * into ' + name +
                                               ' from #result_table'
                  FROM tempdb..sysobjects WHERE   id=@TmpTableId
                                              
                  exec (@sql)
            END ELSE BEGIN
                  set @sql = 'select ''error'' '
                  SELECT
                        @sql =      'insert into ' + name +
                                               ' select DISTINCT *
                                               from #result_table'
                  FROM tempdb..sysobjects WHERE   id=@TmpTableId
                  exec (@sql)
            END
      END
      
END

--ИСПОЛЬЗОВАНИЕ ХРАНИМОЙ ПРОЦЕДУРЫ ДАННОГО ФОРМАТА (РЕЗУЛЬТАТ ВО ВРЕМЕННУЮ ТАБЛИЦУ СОЗДАННУЮ ЗАРАНЕЕ)
<...>

      declare @<SP_NAME_ResultTable> INT
      CREATE TABLE #<SP_NAME_ResultTable> (SP_NAME_RESULT_FIELDS)      -- вобщемто любое имя, это не важно
      SET @<SP_NAME_ResultTable> = OBJECT_ID('tempdb..#<SP_NAME_ResultTable>')
      EXEC <SP_NAME> <CommonArgList>, @<SP_NAME_ResultTable>    
      
<...>

--ЕСЛИ НЕ ПЕРЕДАВАТЬ ПОСЛЕДНИЙ ПАРАМЕТР - ПРОСТО ВЫВЕДЕТСЯ НА ЭКРАН

Reply

Добавить комментарий

  Country flag

biuquote
  • Комментарий
  • Предпросмотр
Loading