关于sql server:从存储过程的结果集中选择列

Select columns from result set of stored procedure

我有一个返回80列和300行的存储过程。我想写一个选择,得到其中的2列。有点像

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

当我使用上面的语法时,我得到了错误:

"Invalid Column Name".

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

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

  • 我可以创建一个临时表来放入结果,但是因为有80列,所以我需要创建一个80列的临时表来获取2列。我想避免跟踪所有返回的列。

  • 我试着按照马克的建议使用WITH SprocResults AS ....,但我有2个错误。


    Incorrect syntax near the keyword 'EXEC'.Incorrect syntax near ')'.

  • 我试图声明一个表变量,但得到了以下错误


    Insert Error: Column name or number of supplied values does not match table definition

  • 如果我尝试江户十一〔一〕号我得到错误:


    Incorrect syntax near the keyword 'exec'.


你能把这个问题分成两半吗?将存储的过程结果插入表变量或临时表中。然后,从表变量中选择2列。

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

SELECT col1, col2 FROM @tablevar


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

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

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


1
2
3
4
5
6
7
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/


这对我很有用:(即我只需要sp_help_job返回的30+中的2列)

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

在这项工作开始之前,我需要运行这个:

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

….更新sys.servers表。(例如,默认情况下,在OpenQuery中使用自引用似乎被禁用。)

为了满足我的简单需求,我没有遇到Lance优秀链接的OpenQuery部分中描述的任何问题。

Rossini,如果您需要动态地设置这些输入参数,那么使用OpenQuery会变得更加复杂:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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而不是openrowset,因为openquery不需要在过程中定义连接字符串。

注2:说了这么多:通常我只使用insert…执行官:)是的,这是10分钟的额外打字时间,但如果我能帮上忙,我更不愿意和以下人员闲聊:(a)报价内报价,以及(B)sys表和/或偷偷的自引用链接服务器设置(即,对于这些设置,我需要向我们功能强大的DBA申诉:)

但是在这个例子中,我不能使用插入…exec构造,因为sp_help_job已经使用了一个。("不能嵌套insert exec语句。")


知道这为什么如此困难也许会有所帮助。存储过程只能返回文本(打印"text"),或者可以返回多个表,或者根本不返回任何表。

所以像SELECT * FROM (exec sp_tables) Table1这样的东西是行不通的。


如果可以修改存储过程,则可以轻松地将所需的列定义作为参数,并使用自动创建的临时表:

1
2
3
4
5
6
7
8
9
10
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

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


为了实现这一点,首先创建一个#test_table,如下所示:

1
2
3
4
5
6
7
8
create table #test_table(
    col1 int,
    col2 int,
   .
   .
   .
    col80 int
)

现在执行程序,在#test_table中计价:

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

现在您从#test_table中获取值:

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


(假设为SQL Server)

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


如果您这样做是为了手动验证数据,那么您可以使用LinqPad进行验证。

在linqpad中创建到数据库的连接,然后创建类似于以下内容的C语句:

1
2
3
4
5
6
7
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


一个快速的方法是添加一个新的参数'@Column_Name',并让调用函数定义要检索的列名。在存储过程的返回部分,您将拥有if/else语句,并且只返回指定的列,或者如果为空,则返回all。

1
2
3
4
5
6
7
8
9
10
11
12
13
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


对于SQL Server,我发现这样做很好:

创建一个临时表(或者永久表,并不重要),然后对存储过程执行insert-into语句。sp的结果集应该与表中的列匹配,否则会出错。

下面是一个例子:

1
2
3
4
5
6
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;

就这样!


正如问题中提到的,在执行存储过程之前很难定义80列的临时表。

因此,另一种方法是根据存储过程结果集填充表。

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

如果出现任何错误,则需要通过执行以下查询来启用特殊的分布式查询。

1
2
3
4
5
6
7
8
sp_configure 'Show Advanced Options', 1
GO
RECONFIGURE
GO
sp_configure 'Ad Hoc Distributed Queries', 1
GO
RECONFIGURE
GO

要使用两个参数执行sp_configure以更改配置选项或运行RECONFIGURE语句,必须授予ALTER SETTINGS服务器级权限。

现在,您可以从生成的表中选择特定的列

1
2
SELECT col1, col2
FROM #temp

试试这个

1
2
3
4
5
6
7
8
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


我知道从sp执行并插入到temp表或表变量中是一个选项,但我认为这不是您的要求。根据您的要求,下面的查询语句应该有效:

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

如果您有可信连接,请使用下面的查询语句:

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

如果运行上述语句时出错,请在下面运行此语句:

1
2
3
4
5
6
7
8
sp_configure 'Show Advanced Options', 1
GO
RECONFIGURE
GO
sp_configure 'Ad Hoc Distributed Queries', 1
GO
RECONFIGURE
GO

我希望这能帮助那些将要面对类似问题的人。如果有人尝试使用下面应该是这样的临时表或表变量,但是在这种情况下,您应该知道您的SP返回了多少列,那么您应该在临时表或表变量中创建那么多的列:

1
2
3
4
5
6
7
8
9
--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

对于任何拥有SQL 2012或更高版本的人,我都能够使用非动态的存储过程来完成这一任务,并且每次都输出相同的列。

一般的想法是,我构建动态查询来创建、插入、从中选择和除去临时表,并在生成完之后执行它。我首先从存储过程中检索列名和类型,动态地生成临时表。

注意:如果您愿意/能够更新SP或更改配置并使用OPENROWSET,那么有更好、更通用的解决方案可以使用更少的代码行。如果没有其他方法,请使用下面的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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)


如果只需要这样做一次,最简单的方法是:

在导入和导出向导中导出到Excel,然后将此Excel导入到表中。


我将剪切并粘贴原始SP,删除除2之外的所有列。或者。我会将结果集放回原处,将其映射到适当的业务对象,然后对这两列进行Linq。