Получение ошибки «Оператор INSERT EXE C не может быть вложенным» - PullRequest
0 голосов
/ 20 февраля 2020

У меня возникла проблема со вставкой ... exe c, и я не могу найти онлайн-решение, которое будет работать. У меня есть хранимая процедура, которая извлекает данные из API. Это достигается путем построения командной строки, запуска ее через xp_cmdshell и записи вывода в таблицу (с использованием вставки ... exe c).

Хранимая процедура работает отлично и форматирует необходимые данные в хорошую таблицу

Я сейчас пытаюсь внедрить это в мою базу данных, но это нужно вызывать из ряда других хранимых процедур. Они должны иметь возможность видеть результаты начальной хранимой процедуры, но я наткнулся на ошибку «Оператор INSERT EXE C не может быть вложенным», и он не позволит мне перехватить вывод

Я пробовал различные решения, которые я видел, предлагаемые онлайн, но пока ни одно из них не сработало. Первоначальная хранимая процедура вызывает командную строку, поэтому я не могу найти какой-либо другой способ вызвать ее и записать вывод, кроме использования вставки ..... exe c, но мне все еще нужен форматированный вывод.

Я пытался преобразовать свою хранимую процедуру в функцию, но я не могу запустить xp_cmdshell. Я также посмотрел на получение исходной хранимой процедуры для возврата таблицы в качестве выходного параметра (даже если я создаю ее с типом), но хранимая процедура не позволит этого

Я также посмотрел при использовании openset, но мне нужно иметь возможность передать параметр хранимой процедуре, и я не думаю, что openset позволит это. Что я мог попробовать дальше?

РЕДАКТИРОВАТЬ: я собрал простой пример того, что я пытаюсь сделать. Хранимая процедура извлекает данные из командной строки. Я просто использую команду echo для фальсификации данных, но в действительности эта командная строка вызывает API и получает обратно JSON. Затем JSON форматируется в таблицу SQL и выводится. Поскольку это вызов API, я не вижу другого способа сделать это без вставки ... exe c xp_cmdshell, но это означает, что я не могу перехватить вывод хранимой процедуры и использовать его

create procedure usp_retrieveAPIdata

    @inparameter int

as

begin

    declare @APIcall varchar(200)

    --this would normally be an API call, returning a JSON array
    set @APICall='echo f1:"foo" & echo f2:"bar" & echo f1:"Hello" & echo f2:"World"'

    declare @resulttable table
    (outputfield varchar(100),ID int identity)

    insert into @resulttable
    exec xp_cmdshell @APICall

    declare @formattedtable table
    (field1 varchar(100),field2 varchar(100))

    declare @rownum int =0
    declare @field1 varchar(100)
    declare @field2 varchar(100)
    declare @currentfield varchar(100)

    while exists (select * from @resulttable where ID>@rownum)
    begin
        set @rownum=@rownum+1
        select @currentfield=outputfield from @resulttable where ID=@rownum

        if @currentfield like 'f1%'
        begin
            set @field1=replace(@currentfield,'f1:','')
        end

        if @currentfield like 'f2%' and @rownum<>1
        begin
            set @field2=replace(@currentfield,'f2:','')
            insert into @formattedtable (field1,field2) values (@field1,@field2)
        end
    end

    select * from @formattedtable
end

go

declare @resulttable table (field1 varchar(100),field2 varchar(100))

insert into @resulttable
exec usp_retrieveAPIdata 1

Ответы [ 2 ]

1 голос
/ 20 февраля 2020

Это проблема с INSERT EXEC Я сталкивался с этим много раз за эти годы. Вот несколько вариантов - ни один из них не идеален, у каждого есть свои плюсы и минусы, но, тем не менее, он поможет вам преодолеть конечную линию sh.

Образцы процедур:

USE tempdb
GO

-- Sample Procs
CREATE PROC dbo.proc1 @a INT, @b INT
AS
  SELECT x.a, x.b
  FROM  (VALUES(@a,@b))          AS x(a,b)
  CROSS JOIN (VALUES(1),(2),(3)) AS xx(x);
GO

CREATE PROC dbo.proc2 @a INT, @b INT
AS
  DECLARE @x TABLE (a INT, b INT);

  INSERT @x(a,b)
  EXEC dbo.proc1 5,10;

  SELECT x.a, x.b FROM @x AS x;

Эта ошибка завершится из-за вложенности INSERT EXE C:

DECLARE @a INT = 2, @b INT = 4;

DECLARE @t2 TABLE (a INT, b INT);
INSERT  @t2(a,b)
EXEC dbo.proc2 5,10;

Option # 1. Извлеките хранимую процедуру logi c и запустите ее напрямую

Здесь я просто беру logi c из dbo.proc2 и запускаю его ad-ho c

DECLARE @t2 TABLE (a INT, b INT);
DECLARE @a INT = 2, @b INT = 4;

INSERT @t2 (a,b)
  -- Logic Extracted right out of dbo.proc1:
  SELECT x.a, x.b
  FROM       (VALUES(@a,@b))     AS x(a,b)
  CROSS JOIN (VALUES(1),(2),(3)) AS xx(x);

SELECT t2.* FROM @t2 AS t2;

Вариант № 2 - Извлечь pro c logi c и запустить его как Dynami c SQL

DECLARE @t2  TABLE (a INT, b INT);
DECLARE @a   INT = 2, 
        @b   INT = 4;
DECLARE @SQL NVARCHAR(4000) = N'
    SELECT     x.a, x.b
    FROM       (VALUES(@a,@b))     AS x(a,b)
    CROSS JOIN (VALUES(1),(2),(3)) AS xx(x);',
  @ParmDefinition NVARCHAR(500) = N'@a INT, @b INT';

INSERT @t2    
EXEC sys.sp_executesql @SQL, @ParmDefinition, @a=@a, @b=@b;

SELECT t2.* FROM @t2 AS t2; -- validation

Опция # 3 - вариант # 2 с pro c кодом непосредственно из метаданных

DECLARE @t2  TABLE (a INT, b INT);
DECLARE @a   INT = 2, 
        @b   INT = 4;

DECLARE 
  @SQL NVARCHAR(4000) = 
    ( SELECT SUBSTRING(f.P, CHARINDEX('SELECT',f.P),LEN(f.P))
      FROM   (VALUES(OBJECT_DEFINITION(OBJECT_ID('proc1')))) AS f(P)),
   @ParmDefinition NVARCHAR(500) = N'@a INT, @b INT';

EXEC sys.sp_executesql @SQL, @ParmDefinition, @a=@a, @b=@b;

Недостатком является разбор того, что мне нужно. Я сделал простой пример с логикой c, начинающейся с предложения SELECT, реальный мир не такой добрый. Положительным моментом по сравнению с добавлением logi c вручную является то, что ваш код будет обновлен. Изменения в pro c автоматически меняют логи c (но также могут нарушить код).

Опция № 4: Глобальная временная таблица

Я на самом деле не пробовал, но это должно работать. Вы можете переписать pro c (в моем примере proc2) следующим образом:

ALTER PROC dbo.proc2 @a INT, @b INT, @output BIT = 1
AS
  IF OBJECT_ID('tempdb..##x','U') IS NOT NULL DROP TABLE ##x;
  CREATE TABLE ##x(a INT, b INT);

  INSERT ##x(a,b)
  EXEC dbo.proc1 5,10;

  IF @output = 1
  SELECT x.a, x.b FROM ##x AS x;
GO

Я заполняю глобальную временную таблицу набором результатов, затем добавляю опцию для отображения вывода или нет. Когда @output = 0, набор результатов будет находиться в ## x, на который можно ссылаться следующим образом:

DECLARE @t2 TABLE (a INT, b INT);

EXEC dbo.proc2 5,10,0;
INSERT  @t2(a,b)
SELECT * FROM ##x;

SELECT * FROM @t2;
0 голосов
/ 20 февраля 2020

Я думаю, что взломал это. Странно, что вы проводите весь день, глядя на SQL, а затем приходит к вам ответ, когда вы чистите фантастический sh резервуар

Мне нужно разделить мое spro c на две части. Первая часть вызывает API и получает ответ в виде массива JSON. JSON в основном текст, поэтому вместо того, чтобы преобразовать это в таблицу, я должен просто вернуться как NVARCHAR (MAX) к вызывающему spro c.

Затем вызывающий spro c может вызвать второе spro c для форматирования этого JSON в табличный формат.

Поскольку первое spro c не возвращает таблицу, SQL не будет заботиться о вложенной вставке .. .exe c, а поскольку второе spro c не использует командную оболочку, ему не требуется вставка ... exe c, поэтому он может получать результаты в таблицу

Вот пример выше, но с spro c, разделенным на 2 ...

begin tran

go

create procedure usp_retrieveAPIdata

    @inparameter int,
    @resultstring varchar(max) output

as

begin

    declare @APIcall varchar(200)

    --this would normally be an API call, returning a JSON array
    set @APICall='echo f1:"foo" & echo f2:"bar" & echo f1:"Hello" & echo f2:"World"'

    declare @resulttable table
    (outputfield varchar(100),ID int identity)

    insert into @resulttable
    exec xp_cmdshell @APICall

    set @resultstring=''

    select @resultstring=@resultstring + isnull(outputfield,'') + '¶' from @resulttable order by ID     --using '¶' as a random row delimiter


end
go



create procedure usp_formatdata (@instring varchar(max))

as 
begin

    print @instring

    declare @resulttable table
    (outputfield varchar(100),ID int)

    insert into @resulttable (outputfield,ID)
    select value,idx+1 from dbo.fn_split(@instring,'¶');



    declare @formattedtable table
    (field1 varchar(100),field2 varchar(100))

    declare @rownum int =0
    declare @field1 varchar(100)
    declare @field2 varchar(100)
    declare @currentfield varchar(100)

    while exists (select * from @resulttable where ID>@rownum)
    begin
        set @rownum=@rownum+1
        select @currentfield=outputfield from @resulttable where ID=@rownum

        if @currentfield like 'f1%'
        begin
            set @field1=replace(@currentfield,'f1:','')
        end

        if @currentfield like 'f2%' and @rownum<>1
        begin
            set @field2=replace(@currentfield,'f2:','')
            insert into @formattedtable (field1,field2) values (@field1,@field2)
        end
    end

    select field1,field2 from @formattedtable
end

go




declare @resulttable table (field1 varchar(100),field2 varchar(100))
declare @outstring varchar(max)


exec usp_retrieveAPIdata 110,@resultstring=@outstring output


insert into @resulttable
exec usp_formatdata @outstring

select  * from @resulttable

rollback

Большое спасибо всем, кто нашел время, чтобы внести свой вклад в эту тему

...