Выберите столбцы из набора результатов хранимой процедуры


448

У меня есть хранимая процедура, которая возвращает 80 столбцов и 300 строк. Я хочу написать выбор, который получает 2 из этих столбцов. Что-то вроде

SELECT col1, col2 FROM EXEC MyStoredProc 'param1', 'param2'

Когда я использовал приведенный выше синтаксис, я получаю сообщение об ошибке:

«Неверное имя столбца».

Я знаю, что самым простым решением было бы изменить хранимую процедуру, но я ее не написал и не могу ее изменить.

Есть ли способ сделать то, что я хочу?

  • Я мог бы создать временную таблицу для размещения результатов, но поскольку там 80 столбцов, поэтому мне нужно было бы создать временную таблицу из 80 столбцов, чтобы получить 2 столбца. Я хотел избежать отслеживания всех возвращаемых столбцов.

  • Я пытался использовать WITH SprocResults AS ....как предложено Марком, но я получил 2 ошибки

    Неверный синтаксис рядом с ключевым словом «EXEC».
    Неверный синтаксис рядом с ')'.

  • Я попытался объявить переменную таблицы, и я получил следующую ошибку

    Ошибка вставки: имя столбца или количество предоставленных значений не соответствует определению таблицы

  • Если я пытаюсь,
    SELECT * FROM EXEC MyStoredProc 'param1', 'param2'
    я получаю ошибку:

    Неверный синтаксис рядом с ключевым словом «exec».


Из любопытства, работает ли этот запрос: SELECT * FROM EXEC MyStoredProc 'param1', 'param2' Если это так, какие имена столбцов отображаются в наборе результатов, и можете ли вы использовать эти имена столбцов в списке выбора?
Дейв Коста

5
Я так и не нашел ответа на это.
Россини

32
Ну, вы никогда не отвечали на очень важный вопрос! О какой платформе SQL вы спрашиваете? MySQL, Microsoft SQL Server, Oracle и т. Д. Мне кажется, что это SQL Server, но вы должны сказать людям, или они не могут надежно ответить на ваш вопрос.
JMTyler

6
Ну, это должен быть MS-SQL. EXECне является ключевым словом MySQL (эквивалент MySQL - подготовленные операторы ). Хотя я хотел бы знать ответ для MySQL, ответы ниже нацелены на T-SQL. Работа с тегами.
Бобобобо

1
Я так и не нашел ответ на этот вопрос
Россини

Ответы:


186

Вы можете разделить запрос? Вставьте сохраненные результаты процедуры в табличную переменную или временную таблицу. Затем выберите 2 столбца из табличной переменной.

Declare @tablevar table(col1 col1Type,..
insert into @tablevar(col1,..) exec MyStoredProc 'param1', 'param2'

SELECT col1, col2 FROM @tablevar

27
Это также не работает, когда вы не знаете определения таблицы
Ян Бойд

не знал об этом типе. Они реализованы так же, как временные таблицы? Или это строго в памяти?
d -_- b

Это было интересно: sqlnerd.blogspot.com/2005/09/…
d -_- b

Это работает нормально, если количество столбцов, предоставленных во временной таблице, такое же, как и в выходных данных хранимой процедуры. chagbert.
Чагберт

83

Вот ссылка на довольно хороший документ, объясняющий все различные способы решения вашей проблемы (хотя многие из них не могут быть использованы, поскольку вы не можете изменить существующую хранимую процедуру).

Как обмениваться данными между хранимыми процедурами

Ответ Гульзара будет работать (он задокументирован в ссылке выше), но его будет сложно написать (вам нужно будет указать все 80 имен столбцов в вашем операторе @tablevar (col1, ...). И в будущем если столбец добавляется в схему или изменяется выходной файл, его необходимо обновить в коде, иначе произойдет ошибка.


1
Я думаю, что предложение OPENQUERY в этой ссылке намного ближе к тому, что ищет OP.
Корин

80
CREATE TABLE #Result
(
  ID int,  Name varchar(500), Revenue money
)
INSERT #Result EXEC RevenueByAdvertiser '1/1/10', '2/1/10'
SELECT * FROM #Result ORDER BY Name
DROP TABLE #Result

Источник:
http://stevesmithblog.com/blog/select-from-a-stored-procedure/


@LawfulHacker Святой курит. Что вы делаете на SQL Server 2000 в 2014 году?
Джон Заброски

1
Крупные корпорации с унаследованными системами: D
LawfulHacker

39

Это работает для меня: (т.е. мне нужно только 2 столбца из 30+, возвращаемых sp_help_job)

SELECT name, current_execution_status 
FROM OPENQUERY (MYSERVER, 
  'EXEC msdb.dbo.sp_help_job @job_name = ''My Job'', @job_aspect = ''JOB''');  

Прежде чем это сработает, мне нужно запустить это:

sp_serveroption 'MYSERVER', 'DATA ACCESS', TRUE;

.... обновить sys.serversтаблицу. (т.е. использование собственной ссылки в OPENQUERY, по-видимому, отключено по умолчанию.)

По моему простому требованию я не столкнулся ни с одной из проблем, описанных в разделе ОТКРЫТИЯ превосходной ссылки Ланса.

Россини, если вам нужно динамически установить эти входные параметры, тогда использование OPENQUERY становится немного сложнее:

DECLARE @innerSql varchar(1000);
DECLARE @outerSql varchar(1000);

-- Set up the original stored proc definition.
SET @innerSql = 
'EXEC msdb.dbo.sp_help_job @job_name = '''+@param1+''', @job_aspect = N'''+@param2+'''' ;

-- Handle quotes.
SET @innerSql = REPLACE(@innerSql, '''', '''''');

-- Set up the OPENQUERY definition.
SET @outerSql = 
'SELECT name, current_execution_status 
FROM OPENQUERY (MYSERVER, ''' + @innerSql + ''');';

-- Execute.
EXEC (@outerSql);

Я не уверен в различиях (если таковые имеются) между использованием sp_serveroptionдля sys.serversнепосредственного обновления существующей ссылки на себя и использованием sp_addlinkedserver(как описано в ссылке Ланса) для создания дубликата / псевдонима.

Примечание 1: Я предпочитаю OPENQUERY, а не OPENROWSET, учитывая, что OPENQUERY не требует определения строки соединения в рамках proc.

Примечание 2: Сказав все это: обычно я бы просто использовал INSERT ... EXEC :) Да, это 10 минут дополнительной печати, но если я могу помочь, я предпочитаю не шутить:
(a) кавычки внутри кавычек внутри кавычки и
(б) sys-таблицы и / или хитрые самоссылочные настройки связанных серверов (т. е. для них мне нужно доверить свое дело нашим всемогущим администраторам баз данных :)

Однако в этом случае я не мог использовать конструкцию INSERT ... EXEC, как sp_help_jobона уже используется. («Оператор INSERT EXEC не может быть вложенным.»)


3
У меня было 13 одинарных кавычек подряд в динамически-sql-это-генерируется-динамически-sql-это-генерируется-динамически-sql ...
ErikE

Мне нужно проверить, если работа закончена. Msgstr "Оператор INSERT EXEC не может быть вложенным". Я ненавижу тебя, Microsoft.
Алексковельский

11

Чтобы достичь этого, сначала создайте #test_tableподобное ниже:

create table #test_table(
    col1 int,
    col2 int,
   .
   .
   .
    col80 int
)

Теперь выполните процедуру и введите значение в #test_table:

insert into #test_table
EXEC MyStoredProc 'param1', 'param2'

Теперь вы получаете значение из #test_table :

select col1,col2....,col80 from #test_table

2
Есть ли преимущество в создании временной таблицы вместо табличной переменной?
Дейв Келли

1
лучшее решение, которое я нашел на stackoverflow! :)
Умар

4
Что если мне нужен только один столбец из другой хранимой процедуры?
Кевал Патель

11

Если вы можете изменить хранимую процедуру, вы можете легко поместить необходимые определения столбцов в качестве параметра и использовать автоматически созданную временную таблицу:

CREATE PROCEDURE sp_GetDiffDataExample
      @columnsStatement NVARCHAR(MAX) -- required columns statement (e.g. "field1, field2")
AS
BEGIN
    DECLARE @query NVARCHAR(MAX)
    SET @query = N'SELECT ' + @columnsStatement + N' INTO ##TempTable FROM dbo.TestTable'
    EXEC sp_executeSql @query
    SELECT * FROM ##TempTable
    DROP TABLE ##TempTable
END

В этом случае вам не нужно создавать временную таблицу вручную - она ​​создается автоматически. Надеюсь это поможет.


Будьте осторожны при использовании таблиц ##, так как они совместно используются сессиями
eKelvin

1
Краткое описание различий между таблицами # и ## можно найти в stackoverflow.com/a/34081438/352820
eKelvin

Это склонно к инъекции SQL?
Бушрод

11

Может быть полезно знать, почему это так сложно. Хранимая процедура может только возвращать текст (печатать «текст»), или может возвращать несколько таблиц, или может вообще не возвращать таблицы.

Так что как SELECT * FROM (exec sp_tables) Table1 то не получится


12
SQL Server может выдавать ошибку, если это произойдет. например, если я напишу подзапрос, который возвращает более одного значения. Да, это может случиться, но на самом деле это не так. И даже если это произойдет: нетрудно вызвать ошибку.
Ян Бойд

9

(Предполагая SQL Server)

Единственный способ работать с результатами хранимой процедуры в T-SQL - это использовать INSERT INTO ... EXECсинтаксис. Это дает вам возможность вставить во временную таблицу или табличную переменную и оттуда выбрать нужные данные.


1
Это требует знания определения таблицы. Не полезно.
Трийнко

8

Быстрый способ - добавить новый параметр '@Column_Name'и заставить вызывающую функцию определить имя столбца для извлечения. В возвращаемой части вашего sproc вы должны иметь операторы if / else и возвращать только указанный столбец, или, если он пуст - возвращать все.

CREATE PROCEDURE [dbo].[MySproc]
        @Column_Name AS VARCHAR(50)
AS
BEGIN
    IF (@Column_Name = 'ColumnName1')
        BEGIN
            SELECT @ColumnItem1 as 'ColumnName1'
        END
    ELSE
        BEGIN
            SELECT @ColumnItem1 as 'ColumnName1', @ColumnItem2 as 'ColumnName2', @ColumnItem3 as 'ColumnName3'
        END
END

7

Если вы делаете это для ручной проверки данных, вы можете сделать это с помощью LINQPad.

Создайте соединение с базой данных в LinqPad, затем создайте операторы C #, подобные следующим:

DataTable table = MyStoredProc (param1, param2).Tables[0];
(from row in table.AsEnumerable()
 select new
 {
  Col1 = row.Field<string>("col1"),
  Col2 = row.Field<string>("col2"),
 }).Dump();

Ссылка http://www.global-webnet.net/blogengine/post/2008/09/10/LINQPAD-Using-Stored-Procedures-Accessing-a-DataSet.aspx


7

Я считаю, что для SQL Server это работает нормально:

Создайте временную таблицу (или постоянную таблицу, на самом деле не имеет значения) и вставьте оператор в хранимую процедуру. Результирующий набор SP должен соответствовать столбцам в вашей таблице, иначе вы получите ошибку.

Вот пример:

DECLARE @temp TABLE (firstname NVARCHAR(30), lastname nvarchar(50));

INSERT INTO @temp EXEC dbo.GetPersonName @param1,@param2;
-- assumption is that dbo.GetPersonName returns a table with firstname / lastname columns

SELECT * FROM @temp;

Это оно!


Для этого нужно создать копию определения таблицы. Есть ли способ избежать этого?
Hardik

7

Как уже упоминалось в этом вопросе, трудно определить временную таблицу из 80 столбцов перед выполнением хранимой процедуры.

Таким образом, другой способ - заполнить таблицу на основе набора результатов хранимой процедуры.

SELECT * INTO #temp FROM OPENROWSET('SQLNCLI', 'Server=localhost;Trusted_Connection=yes;'
                                   ,'EXEC MyStoredProc')

Если вы получаете какую-либо ошибку, вам нужно включить специальные распределенные запросы, выполнив следующий запрос.

sp_configure 'Show Advanced Options', 1
GO
RECONFIGURE
GO
sp_configure 'Ad Hoc Distributed Queries', 1
GO
RECONFIGURE
GO

Чтобы выполнить sp_configureоба параметра, чтобы изменить параметр конфигурации или выполнить RECONFIGUREинструкцию, вам необходимо предоставить ALTER SETTINGSразрешение на уровне сервера.

Теперь вы можете выбрать ваши конкретные столбцы из сгенерированной таблицы

SELECT col1, col2
FROM #temp

4

попробуй это

use mydatabase
create procedure sp_onetwothree as
select 1 as '1', 2 as '2', 3 as '3'
go
SELECT a.[1], a.[2]
FROM OPENROWSET('SQLOLEDB','myserver';'sa';'mysapass',
    'exec mydatabase.dbo.sp_onetwothree') AS a
GO

1
Лол - он не сделал. Вместо этого он закодировал ее в вызове хранимой процедуры, где ее гораздо проще получить без доступа к базе данных с помощью сниффинга сети.
Мартин Милан,

Довольно легко вытащить это из Github тоже.
номен

3

Я знаю, что выполнение из sp и вставка во временную таблицу или табличную переменную будет вариантом, но я не думаю, что это ваше требование. В соответствии с вашим требованием должен работать следующий запрос:

Declare @sql nvarchar(max)
Set @sql='SELECT   col1, col2 FROM OPENROWSET(''SQLNCLI'', ''Server=(local);uid=test;pwd=test'',
     ''EXEC MyStoredProc ''''param1'''', ''''param2'''''')'
 Exec(@sql)

если у вас есть доверенное соединение, используйте следующую инструкцию запроса:

Declare @sql nvarchar(max)
Set @sql='SELECT   col1, col2 FROM OPENROWSET(''SQLNCLI'', ''Server=(local);Trusted_Connection=yes;'',
     ''EXEC MyStoredProc ''''param1'''', ''''param2'''''')'
 Exec(@sql)

если вы получаете ошибку при выполнении вышеприведенного оператора, просто запустите этот оператор ниже:

sp_configure 'Show Advanced Options', 1
GO
RECONFIGURE
GO
sp_configure 'Ad Hoc Distributed Queries', 1
GO
RECONFIGURE
GO

Я надеюсь, что это поможет кому-то, кто столкнулся с подобной проблемой. Если кто-то попытается использовать временную таблицу или переменную таблицы, которая должна быть такой, как показано ниже, но в этом сценарии вы должны знать, сколько столбцов возвращает sp, тогда вам следует создать столько столбцов во временной таблице или переменной таблицы:

--for table variable 
Declare @t table(col1 col1Type, col2 col2Type)
insert into @t exec MyStoredProc 'param1', 'param2'
SELECT col1, col2 FROM @t

--for temp table
create table #t(col1 col1Type, col2 col2Type)
insert into #t exec MyStoredProc 'param1', 'param2'
SELECT col1, col2 FROM #t

1

Для тех, кто имеет SQL 2012 или более позднюю версию, я смог выполнить это с помощью хранимых процедур, которые не являются динамическими и каждый раз выдают одинаковые столбцы.

Основная идея заключается в том, что я создаю динамический запрос для создания, вставки, выбора и удаления временной таблицы и выполняю ее после того, как она все сгенерирована. Я динамически генерирую временную таблицу, сначала извлекая имена и типы столбцов из хранимой процедуры .

Примечание: есть гораздо лучшие, более универсальные решения, которые будут работать с меньшим количеством строк кода, если вы захотите / сможете обновить SP или изменить конфигурацию и использовать OPENROWSET. Используйте ниже, если у вас нет другого пути.

DECLARE @spName VARCHAR(MAX) = 'MyStoredProc'
DECLARE @tempTableName VARCHAR(MAX) = '#tempTable'

-- might need to update this if your param value is a string and you need to escape quotes
DECLARE @insertCommand VARCHAR(MAX) = 'INSERT INTO ' + @tempTableName + ' EXEC MyStoredProc @param=value'

DECLARE @createTableCommand VARCHAR(MAX)

-- update this to select the columns you want
DECLARE @selectCommand VARCHAR(MAX) = 'SELECT col1, col2 FROM ' + @tempTableName

DECLARE @dropCommand VARCHAR(MAX) = 'DROP TABLE ' + @tempTableName

-- Generate command to create temp table
SELECT @createTableCommand = 'CREATE TABLE ' + @tempTableName + ' (' +
    STUFF
    (
        (
            SELECT ', ' + CONCAT('[', name, ']', ' ', system_type_name)
            FROM sys.dm_exec_describe_first_result_set_for_object
            (
              OBJECT_ID(@spName), 
              NULL
            )
            FOR XML PATH('')
        )
        ,1
        ,1
        ,''
    ) + ')'

EXEC( @createTableCommand + ' '+ @insertCommand + ' ' + @selectCommand + ' ' + @dropCommand)

0

Самый простой способ сделать это, если вам нужно сделать это только один раз:

Экспорт в Excel в мастере импорта и экспорта, а затем импортировать этот Excel в таблицу.


4
Весь смысл создания хранимого процесса - возможность повторного использования. Ваш ответ полностью противоречит этому.
deutschZuid

6
Чтобы противостоять deutschZuid, в оригинальном сообщении он не упоминает, хочет ли он использовать это повторно или просто пытается просмотреть результаты сохраненного процесса. Мартин прав, это, наверное, самый простой способ, если ему нужно сделать это только один раз.
епископ

0

Создайте динамическое представление и получите от него результат .......

CREATE PROCEDURE dbo.usp_userwise_columns_value
(
    @userid BIGINT
)
AS 
BEGIN
        DECLARE @maincmd NVARCHAR(max);
        DECLARE @columnlist NVARCHAR(max);
        DECLARE @columnname VARCHAR(150);
        DECLARE @nickname VARCHAR(50);

        SET @maincmd = '';
        SET @columnname = '';
        SET @columnlist = '';
        SET @nickname = '';

        DECLARE CUR_COLUMNLIST CURSOR FAST_FORWARD
        FOR
            SELECT columnname , nickname
            FROM dbo.v_userwise_columns 
            WHERE userid = @userid

        OPEN CUR_COLUMNLIST
        IF @@ERROR <> 0
            BEGIN
                ROLLBACK
                RETURN
            END   

        FETCH NEXT FROM CUR_COLUMNLIST
        INTO @columnname, @nickname

        WHILE @@FETCH_STATUS = 0
            BEGIN
                SET @columnlist = @columnlist + @columnname + ','

                FETCH NEXT FROM CUR_COLUMNLIST
                INTO @columnname, @nickname
            END
        CLOSE CUR_COLUMNLIST
        DEALLOCATE CUR_COLUMNLIST  

        IF NOT EXISTS (SELECT * FROM sys.views WHERE name = 'v_userwise_columns_value')
            BEGIN
                SET @maincmd = 'CREATE VIEW dbo.v_userwise_columns_value AS SELECT sjoid, CONVERT(BIGINT, ' + CONVERT(VARCHAR(10), @userid) + ') as userid , ' 
                            + CHAR(39) + @nickname + CHAR(39) + ' as nickname, ' 
                            + @columnlist + ' compcode FROM dbo.SJOTran '
            END
        ELSE
            BEGIN
                SET @maincmd = 'ALTER VIEW dbo.v_userwise_columns_value AS SELECT sjoid, CONVERT(BIGINT, ' + CONVERT(VARCHAR(10), @userid) + ') as userid , ' 
                            + CHAR(39) + @nickname + CHAR(39) + ' as nickname, ' 
                            + @columnlist + ' compcode FROM dbo.SJOTran '
            END

        --PRINT @maincmd
        EXECUTE sp_executesql @maincmd
END

-----------------------------------------------
SELECT * FROM dbo.v_userwise_columns_value

-1

Я бы вырезал и вставил оригинальный SP и удалил все столбцы, кроме 2, которые вы хотите. Или. Я бы вернул набор результатов обратно, сопоставил бы его с соответствующим бизнес-объектом, а затем LINQ из двух столбцов.


1
Люди не делают этого. Это нарушит принцип СУХОГО. Когда все изменится, а не если, теперь вам нужно будет отслеживать и вводить изменения во всех местах.
jriver27
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.