从存储过程的结果集中选择列


447

我有一个存储过程返回80列和300行。我想编写一个选择,使这些列中有2个。就像是

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

当我使用以上语法时,出现错误:

“无效的列名”。

我知道最简单的解决方案是更改存储过程,但是我没有编写它,也无法更改它。

有什么办法可以做我想要的吗?

  • 我可以制作一个临时表来放入结果,但是因为有80列,所以我需要制作一个80列的临时表才能得到2列。我想避免跟踪所有返回的列。

  • 我尝试WITH SprocResults AS ....按照Mark的建议使用,但出现2个错误

    关键字“ EXEC”附近的语法不正确。
    ')'附近的语法不正确。

  • 我尝试声明一个表变量,但出现以下错误

    插入错误:列名或提供的值数与表定义不匹配

  • 如果我尝试
    SELECT * FROM EXEC MyStoredProc 'param1', 'param2'
    我得到错误:

    关键字“ exec”附近的语法不正确。


出于好奇,此查询是否起作用:SELECT * FROM EXEC MyStoredProc'param1','param2'如果是这样,它将在结果集中显示哪些列名,您可以在选择列表中使用这些列名吗?
戴夫·科斯塔

5
我从来没有找到答案。
罗西尼2010年

32
好吧,您从未回答过一个非常重要的问题!您在问什么SQL平台?MySQL,Microsoft SQL Server,Oracle等。在我看来,它就像SQL Server,但您需要告诉别人,否则他们不能可靠地回答您的问题。
JMTyler 2010年

6
好吧,它一定是MS-SQL。 EXEC不是MySQL关键字(相当于MySQL的prepare 语句)。尽管我想知道MySQL的答案,但下面的答案针对的是T-SQL。重新标记。
bobobobo

1
我从来没有找到答案
Rossini 2014年

Answers:


186

您可以拆分查询吗?将存储的proc结果插入表变量或临时表中。然后,从表变量中选择2列。

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

SELECT col1, col2 FROM @tablevar

27
当您不知道表定义时,它也不起作用
Ian Boyd 2010年

不知道那种类型。它们的实现与临时表相同吗?还是严格地在记忆中?
d -_- b 2012年


如果临时表中提供的列数与存储过程的输出中的列数相同,则可以正常工作。查伯特。
查伯特

83

这是指向一个很好的文档的链接,该文档解释了解决问题的所有不同方法(尽管由于无法修改现有存储过程,所以许多方法无法使用。)

如何在存储过程之间共享数据

Gulzar的答案会起作用(在上面的链接中有记录),但是编写起来很麻烦(您需要在@tablevar(col1,...)语句中指定所有80个列名。如果将列添加到架构或更改了输出,则需要在您的代码中进行更新,否则将出错。


1
我认为该链接中的OPENQUERY建议更接近OP的需求。
科林2012年


39

这对我有用:(即,我只需要由返回的30+中的2列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中使用自引用功能已被禁用。)

就我的简单要求而言,我没有遇到Lance优秀链接的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创建重复/别名(如Lance的链接中所述。

注意1:由于OPENQUERY不需要proc中的连接字符串定义,因此我更喜欢OPENQUERY而不是OPENROWSET。

注意2:说完所有这些:通常我只会使用INSERT ... EXEC :)是的,这需要额外输入10分钟,但是如果我可以帮忙,我宁愿不要随意摆弄:
(a)引号内的引号引号和
(b)sys表,和/或偷偷摸摸的自引用链接服务器设置(即,对于这些,我需要向功能强大的DBA辩护:)

但是在这种情况下,我不能像以前那样使用INSERT ... EXEC构造sp_help_job。(“不能嵌套INSERT EXEC语句。”)


3
我在dynamic-sql生成的动态sql之前生成的动态sql中连续有13个单引号...
ErikE 2013年

我需要检查工作是否完成。“不能嵌套INSERT EXEC语句”。我讨厌你微软。
alexkovelsky

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上找到的最佳解决方案!:)
Umar

4
如果我只需要其他存储过程中的一列怎么办?
Keval Patel

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

在这种情况下,您无需手动创建临时表-它是自动创建的。希望这可以帮助。


使用## tables时要小心,因为它们在会话之间共享
eKelvin


这容易发生SQL注入吗?
布什罗德

11

了解为什么如此困难可能会有所帮助。存储过程可能只返回文本(打印“文本”),或者可能返回多个表,或者根本不返回任何表。

所以类似的东西SELECT * FROM (exec sp_tables) Table1 将无法正常工作


12
如果发生这种情况,SQL Server可以随意引发错误。例如,如果我编写了一个返回多个值的子查询。是的,它可能发生,但实际上却没有。即使这样做:引发错误也不难。
伊恩·博伊德

8

(假设SQL Server)

在T-SQL中处理存储过程结果的唯一方法是使用INSERT INTO ... EXEC语法。这使您可以选择插入到临时表或表变量中,然后从中选择所需的数据。


1
这需要了解表定义。没用。
Triynko

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

对于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中删除也很容易。
nomen

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

如果只需要执行一次此操作,最简单的方法是:

在导入和导出向导中导出为ex​​cel,然后将此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
人们不这样做。这将违反DRY原则。当事情发生变化时(如果不是这样),您现在需要跟踪并在所有位置输入更改。
jriver27 '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.