Selecionar colunas do conjunto de resultados do procedimento armazenado

448

Eu tenho um procedimento armazenado que retorna 80 colunas e 300 linhas. Eu quero escrever um select que recebe 2 dessas colunas. Algo como

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

Quando usei a sintaxe acima, recebo o erro:

"Nome da coluna inválido".

Eu sei que a solução mais fácil seria alterar o procedimento armazenado, mas não o escrevi e não posso alterá-lo.

Existe alguma maneira de fazer o que eu quero?

  • Eu poderia criar uma tabela temporária para colocar os resultados, mas como existem 80 colunas, seria necessário criar uma tabela temporária de 80 colunas apenas para obter 2 colunas. Eu queria evitar rastrear todas as colunas retornadas.

  • Tentei usar WITH SprocResults AS ....como sugerido por Mark, mas obtive 2 erros

    Sintaxe incorreta perto da palavra-chave 'EXEC'.
    Sintaxe incorreta perto de ')'.

  • Tentei declarar uma variável de tabela e recebi o seguinte erro

    Erro de inserção: o nome da coluna ou o número de valores fornecidos não corresponde à definição da tabela

  • Se eu tentar
    SELECT * FROM EXEC MyStoredProc 'param1', 'param2'
    , recebo o erro:

    Sintaxe incorreta perto da palavra-chave 'exec'.

Rossini
fonte
Por curiosidade, essa consulta funciona: SELECT * FROM EXEC MyStoredProc 'param1', 'param2' Se sim, quais nomes de coluna são exibidos no conjunto de resultados e você pode usá-los na sua lista de seleção?
Dave Costa
5
Eu nunca encontrei uma resposta para isso.
Rossini
32
Bem, você nunca respondeu a uma pergunta muito importante! De que plataforma SQL você está perguntando? MySQL, Microsoft SQL Server, Oracle, etc. Parece-me que é o SQL Server, mas você precisa informar às pessoas ou elas não podem responder com segurança à sua pergunta.
JMTyler
6
Bem, deve ser MS-SQL. EXECnão é uma palavra-chave do MySQL (o equivalente do MySQL é instruções preparadas ). Embora eu gostaria de saber a resposta para o MySQL, as respostas abaixo são direcionadas ao T-SQL. Marcando novamente.
amigos estão dizendo sobre bobobobo
1
Eu nunca fiz encontrar uma resposta para isso
Rossini

Respostas:

186

Você pode dividir a consulta? Insira os resultados do processo armazenados em uma variável de tabela ou em uma tabela temporária. Em seguida, selecione as 2 colunas da variável da tabela.

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

SELECT col1, col2 FROM @tablevar
Gulzar Nazim
fonte
27
Ele também não funciona quando você não sabe a definição da tabela
Ian Boyd
não sabia sobre esse tipo. Eles são implementados da mesma forma que as tabelas temporárias? Ou é estritamente na memória?
precisa
Isso foi interessante: sqlnerd.blogspot.com/2005/09/…
d -_- b
Isso funciona bem se o número de colunas fornecidas na tabela temporária for o mesmo da saída do procedimento armazenado. Chagbert.
Chagbert 17/05
83

Aqui está um link para um documento muito bom, explicando todas as maneiras diferentes de resolver seu problema (embora muitas delas não possam ser usadas, pois você não pode modificar o procedimento armazenado existente).

Como compartilhar dados entre procedimentos armazenados

A resposta do Gulzar funcionará (está documentada no link acima), mas será um incômodo escrever (você precisará especificar todos os 80 nomes de coluna na sua declaração @tablevar (col1, ...). se uma coluna for adicionada ao esquema ou a saída for alterada, ela precisará ser atualizada no seu código ou ocorrerá um erro.

Lance McNearney
fonte
1
Acho que a sugestão OPENQUERY nesse link está muito mais próxima do que o OP está procurando.
Corin 31/07
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

Fonte:
http://stevesmithblog.com/blog/select-from-a-stored-procedure/

Peter Nazarov
fonte
@LawfulHacker Holy fuma. O que você está fazendo no SQL Server 2000 no ano de 2014?
John Zabroski 7/03/19
1
Grandes corporações com sistemas legados: D
LawfulHacker
39

Isso funciona para mim: (ou seja, eu só preciso de 2 colunas das mais de 30 retornadas por sp_help_job)

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

Antes que isso funcionasse, eu precisava executar o seguinte:

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

.... para atualizar a sys.serverstabela. (por exemplo, o uso de uma referência própria no OPENQUERY parece estar desativado por padrão.)

Por minha simples exigência, não encontrei nenhum dos problemas descritos na seção OPENQUERY do excelente link de Lance.

Rossini, se você precisar definir dinamicamente esses parâmetros de entrada, o uso do OPENQUERY se tornará um pouco mais complicado:

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

Não tenho certeza das diferenças (se houver) entre usar sp_serveroptionpara atualizar sys.serversdiretamente a auto-referência existente e usar sp_addlinkedserver(como descrito no link de Lance) para criar um duplicado / alias.

Nota 1: Prefiro OPENQUERY sobre OPENROWSET, pois o OPENQUERY não requer a definição da cadeia de conexão no proc.

Nota 2: Tendo dito tudo isso: normalmente eu usaria INSERT ... EXEC :) Sim, são 10 minutos de digitação extra, mas se eu puder ajudá-lo, prefiro não brincar com:
(a) aspas entre aspas dentro cotações e
(b) tabelas sys e / ou configurações de servidor vinculado autorreferenciosamente sorrateiras (ou seja, para essas, preciso defender meu caso com nossos DBAs todo-poderosos):

No entanto, neste caso, eu não poderia usar uma construção INSERT ... EXEC, como sp_help_jobjá está usando uma. ("Uma instrução INSERT EXEC não pode ser aninhada.")

Merenzo
fonte
3
Eu tive 13 aspas simples em uma fileira antes em dynamic-sql-que-gerado-dynamic-sql-que-gerado-dynamic-sql ...
ErikE
Preciso verificar se o trabalho está concluído. "Uma instrução INSERT EXEC não pode ser aninhada". Eu te odeio Microsoft.
alexkovelsky 10/09
11

Para conseguir isso, primeiro crie um #test_tablelike abaixo:

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

Agora execute o procedimento e coloque valor em #test_table:

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

Agora você busca o valor de #test_table:

select col1,col2....,col80 from #test_table
Navneet
fonte
2
Existe vantagem em criar uma tabela temporária em vez de uma variável de tabela?
Dave Kelly
1
melhor solução que encontrei no stackoverflow! :)
Umar
4
E se eu precisar de apenas uma coluna de outro Procedimento Armazenado?
Keval Patel
11

Se você conseguir modificar o procedimento armazenado, poderá colocar facilmente as definições de colunas necessárias como parâmetro e usar uma tabela temporária criada automaticamente:

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

Nesse caso, você não precisa criar uma tabela temporária manualmente - ela é criada automaticamente. Espero que isto ajude.

dyatchenko
fonte
Tenha cuidado ao usar ## mesas como eles são compartilhados entre sessões
eKelvin
1
Você pode encontrar uma breve descrição para diferenças entre # e ## mesas em stackoverflow.com/a/34081438/352820
eKelvin
Isso é propenso a injeção de SQL?
Bushrod 03/01
11

Pode ser útil saber por que isso é tão difícil. Um procedimento armazenado pode retornar apenas texto (imprimir 'texto') ou retornar várias tabelas ou retornar nenhuma tabela.

Então, algo como SELECT * FROM (exec sp_tables) Table1 não vai funcionar

newbie007
fonte
12
O SQL Server pode liberar um erro se isso acontecer. por exemplo, se eu escrever uma subconsulta que retorne mais de um valor. Sim, isso pode acontecer, mas na realidade não. E mesmo que tenha acontecido: não é difícil gerar um erro.
Ian Boyd
8

(Assumindo o SQL Server)

A única maneira de trabalhar com os resultados de um procedimento armazenado no T-SQL é usar a INSERT INTO ... EXECsintaxe. Isso oferece a opção de inserir em uma tabela temporária ou variável de tabela e, a partir daí, selecionar os dados necessários.

Brannon
fonte
1
Isso requer conhecer a definição da tabela. Nao é útil.
Triynko 29/03
8

Um hack rápido seria adicionar um novo parâmetro '@Column_Name'e fazer com que a função de chamada defina o nome da coluna a ser recuperada. Na parte de retorno do seu sproc, você teria instruções if / else e retornaria apenas a coluna especificada ou, se vazia - retornaria tudo.

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
Samir Basic
fonte
7

Se você estiver fazendo isso para validação manual dos dados, poderá fazê-lo com o LINQPad.

Crie uma conexão com o banco de dados no LinqPad e crie instruções C # semelhantes à seguinte:

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();

Referência http://www.global-webnet.net/blogengine/post/2008/09/10/LINQPAD-Using-Stored-Procedures-Accessing-a-DataSet.aspx

ShawnFeatherly
fonte
7

Para o SQL Server, acho que isso funciona bem:

Crie uma tabela temporária (ou tabela permanente, realmente não importa) e insira uma instrução no procedimento armazenado. O conjunto de resultados do SP deve corresponder às colunas da sua tabela, caso contrário, você receberá um erro.

Aqui está um exemplo:

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;

É isso aí!

LTMOD
fonte
Para isso, é necessário criar uma cópia da definição da tabela. Existe alguma maneira de evitá-lo?
Hardik
7

Como foi mencionado na pergunta, é difícil definir a tabela temporária de 80 colunas antes de executar o procedimento armazenado.

Portanto, o contrário é preencher a tabela com base no conjunto de resultados do procedimento armazenado.

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

Se você estiver recebendo algum erro, precisará ativar as consultas distribuídas ad hoc executando a consulta a seguir.

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

Para executar sp_configurecom os dois parâmetros para alterar uma opção de configuração ou executar a RECONFIGUREinstrução, você deve receber a ALTER SETTINGSpermissão no nível do servidor

Agora você pode selecionar suas colunas específicas na tabela gerada

SELECT col1, col2
FROM #temp
sqluser
fonte
4

tente isso

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
SelvirK
fonte
1
Lol - ele não fez. Ele o codificou na invocação do procedimento armazenado, em vez disso, onde ele pode ser obtido com muito mais facilidade, sem acesso ao banco de dados pelo sniffing da rede.
Martin Milan
É muito fácil tirá-lo do Github também.
nomen
3

Eu sei que executar a partir de sp e inserir na tabela temporária ou variável de tabela seria uma opção, mas não acho que esse seja seu requisito. Conforme sua exigência, esta instrução de consulta abaixo deve funcionar:

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

Se você possui uma conexão confiável, use esta instrução de consulta abaixo:

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

se você estiver recebendo um erro ao executar a instrução acima, basta executar esta instrução abaixo:

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

Espero que isso ajude alguém que enfrentou esse tipo de problema semelhante. Se alguém tentar com a tabela temporária ou variável de tabela que deve ser assim abaixo, mas nesse cenário você deve saber quantas colunas seu sp está retornando, então você deve criar tantas colunas na tabela temp ou na variável de tabela:

--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
Humayoun_Kabir
fonte
1

Para quem possui o SQL 2012 ou posterior, consegui fazer isso com procedimentos armazenados que não são dinâmicos e têm as mesmas colunas sempre.

A idéia geral é criar a consulta dinâmica para criar, inserir, selecionar e soltar a tabela temporária e executá-la depois de gerada. Giro dinamicamente a tabela temporária, primeiro recuperando nomes e tipos de colunas do procedimento armazenado .

Nota: existem soluções muito melhores e mais universais que funcionarão com menos linhas de código se você estiver disposto / apto a atualizar o SP ou alterar a configuração e o uso OPENROWSET. Use o abaixo se você não tiver outra maneira.

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)
Emil
fonte
0

A maneira mais fácil de fazer, se você precisar fazer isso apenas uma vez:

Exporte para o Excel no assistente Importar e Exportar e, em seguida, importe esse excel para uma tabela.

Martijn Tromp
fonte
4
O objetivo de criar um processo armazenado é a reutilização. Sua resposta contradiz totalmente isso.
precisa saber é o seguinte
6
Para combater o deutschZuid, na postagem original, ele não menciona se deseja ou não reutilizar isso ou se está apenas tentando analisar os resultados de um processo armazenado. Martin está certo, esta é provavelmente a maneira mais fácil se ele precisar fazer apenas uma vez.
Bishop
0

Crie uma visão dinâmica e obtenha resultados dela .......

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
Nilesh Umaretiya
fonte
-1

Recortaria e colaria o SP original e excluiria todas as colunas, exceto as 2 desejadas. Ou. Eu traria o conjunto de resultados de volta, mapeá-lo para um objeto de negócios adequado e, em seguida, LINQ fora das duas colunas.

Andrew
fonte
1
As pessoas não fazem isso. Isso violará o princípio DRY. Quando as coisas mudam, não se for, agora você precisará rastrear e inserir suas alterações em todos os locais.
jriver27