将存储过程的结果插入临时表中

1828

如何执行SELECT * INTO [临时表] FROM [存储过程],而不是使用FROM [表]并且无需定义[临时表]

SelectBusinessLine中的所有数据选择到tmpBusLine中是可行的。

select *
into tmpBusLine
from BusinessLine

我也在尝试同样的事情,但是使用返回数据的 存储过程 不太一样。

select *
into tmpBusLine
from
exec getBusinessLineHistory '16 Mar 2009'

输出信息:

Msg 156,级别 15,状态 1,行 2 关键字 'exec' 附近有语法错误。

我已经阅读了几个创建一个与存储过程输出具有相同结构的临时表的示例,它们可以正常工作,但最好不要提供任何列。


25
如果使用SELECT * INTO [TABLE NAME]命令,你会知道表的列名,因为它们是从原始表中复制而来的。如果我对存储过程执行相同的操作,这正是我想要的。 - Ferdeen
4
请查看http://www.sommarskog.se/share_data.html和我的帖子https://dev59.com/1FfUa4cB1Zd3GeqPLtbR。 - Triynko
12
想指出的是,"select * into tmpBusLine" 创建了一个永久性的表格。你可能想要使用 "select * into #tmpBusLine"。我相信原帖作者已经发现了这一点,但对于其他人来说,这篇文章可能会有所帮助,因为它是目前搜索 "select into temp table" 的最佳结果。 - ktam33
3
不知道此问题是否已经解决,但造成错误的原因是因为“from”关键字。 - Wes Palmer
36
微软需要添加 SELECT * INTO FROM EXEC!拜托了! - kjmerf
显示剩余5条评论
34个回答

766

你可以使用OPENROWSET来完成此操作。请查看。如果未启用Ad Hoc分布式查询,则我还包括了sp_configure代码以启用它。

CREATE PROC getBusinessLineHistory
AS
BEGIN
    SELECT * FROM sys.databases
END
GO

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

SELECT * INTO #MyTempTable FROM OPENROWSET('SQLNCLI', 'Server=(local)\SQL2008;Trusted_Connection=yes;',
     'EXEC getBusinessLineHistory')

SELECT * FROM #MyTempTable

35
这是正确的做法。OPENROWSET几乎是将存储过程结果作为表达式进行处理的唯一方法。 - Rob Farley
45
这似乎有点繁琐,只是为了插入一个表格。需要进行很多配置。而且当我尝试时出现了以下错误消息:"Msg 7357, Level 16, State 2, Line 1 Cannot process the object "EXEC GetPartyAnalysisData 146". The OLE DB provider "SQLNCLI" for linked server "(null)" indicates that either the object has no columns or the current user does not have permissions on that object."因此,您需要设置一个连接的服务器... - Ferdeen
14
您不需要使用链接服务器,但需要正确获取连接字符串...此外,还需指定包括数据库名称和存储过程所有者在内的完整路径。 - MartW
33
呃!引用同一服务器?很糟糕。这明显是一个更为粗糙的方法,比手动创建临时表还要差。 - Tim Abell
31
我同意这是一种对代码的不良修改,应该尽量避免,除非万不得已。将SP更改为函数可能是更好的选择。在我看来。 - greg
显示剩余13条评论

736

如果您想在不先声明临时表的情况下完成操作,可以尝试创建一个用户定义函数而不是存储过程,并使该用户定义函数返回一个表格。或者,如果您想使用存储过程,请尝试以下方法:

CREATE TABLE #tmpBus
(
   COL1 INT,
   COL2 INT
)

INSERT INTO #tmpBus
Exec SpGetRecords 'Params'

230
我认为重点是在不必显式声明的情况下生成架构。 - Craig
8
我很想知道这种方法与@Aaron Alton上面的解决方案之间的区别。这个方法似乎简单得多,但我不确定是否有其他影响。 - funkymushroom
14
这样做是可行的,但如果你将来在SpGetRecords存储过程中添加额外的列,它将会出现问题。 - Brady Holt
20
每个调用堆栈只能使用一次INSERT INTO EXEC。SpGetRecords和任何其他它调用的存储过程在自己的代码中不能使用这种策略。这可能会给维护SpGetRecords的人带来惊喜。 - Matt Stephenson
48
这完全没有回答问题,我不明白为什么它被赞了这么多?原帖明确说明了“不定义临时表”,但是你的第一行却有一个创建临时表的语句。 - NickG
显示剩余8条评论

320

在SQL Server 2005中,您可以使用INSERT INTO ... EXEC将存储过程的结果插入到表中。来自MSDN的INSERT文档(实际上是针对SQL Server 2000的):

--INSERT...EXECUTE procedure example
INSERT author_sales EXECUTE get_author_sales

168
需要事先定义作者销售额。我试图避免这种情况。谢谢。 - Ferdeen
6
我早已料到。将数据插入临时表非常有用,但如果需要了解存储过程返回的数据集结构,则不再那么有用。谢谢你的帮助。 - Ferdeen
4
这里有一篇好文章:http://msdn.microsoft.com/en-us/library/aa175921.aspx - Rich Andrews
6
若要使用相同的架构,您可以按如下方式创建副本:从realTable选择top 0 *插入tempTable(https://dev59.com/jWox5IYBdhLWcg3wcT3r#9206463) - Even Mien
4
@EvenMien 我看到你的留言时兴奋了一瞬间...但遗憾的是,这仅适用于您的过程的结果实际上反映了一个真正的表格:) - BVernon
显示剩余3条评论

210
这是对您问题的稍作修改后的答案。如果您可以放弃使用存储过程,可以使用内联表值用户定义函数。这本质上是一个存储过程(将接受参数),它将返回一个表作为结果集;因此可以与INTO语句很好地配合使用。 这里有一篇关于此和其他用户定义函数的良好快速文章。如果您仍然需要存储过程,则可以使用存储过程包装内联表值用户定义函数。存储过程只是在调用select * from内联表值用户定义函数时传递参数。 因此,例如,您将拥有一个内联表值用户定义函数来获取特定区域客户的列表:
CREATE FUNCTION CustomersByRegion 
(  
    @RegionID int  
)
RETURNS TABLE 
AS
RETURN 
  SELECT *
  FROM customers
  WHERE RegionID = @RegionID
GO
你可以调用这个函数来获取你的结果,如下所示:
SELECT * FROM CustomersbyRegion(1)

或者执行 SELECT INTO:

SELECT * INTO CustList FROM CustomersbyRegion(1)
如果您仍需要存储过程,则可以将该函数包装如下:
CREATE PROCEDURE uspCustomersByRegion 
(  
    @regionID int  
)
AS
BEGIN
     SELECT * FROM CustomersbyRegion(@regionID);
END
GO
我认为这是获得所需结果的最"无hack"方法。它使用现有功能,没有额外的复杂性。通过将内联表值用户定义函数嵌套在存储过程中,您可以以两种方式访问功能。此外!实际SQL代码只需要一个维护点。 建议使用OPENROWSET,但这不是OPENROWSET函数的预期用途(来自Books Online): 包括所有连接信息,这些信息是从OLE DB数据源访问远程数据所需的。该方法是访问链接服务器中的表的替代方法,也是一种使用OLE DB连接和访问远程数据的一次性、即席方法。对于更频繁地引用OLE DB数据源,请改用链接服务器。 使用OPENROWSET可以完成工作,但它会产生一些额外的开销,如打开本地连接和编组数据。它也可能不是所有情况下的选项,因为它需要一个即席查询权限,这会带来安全风险,因此可能不被允许。此外,OPENROWSET方法将阻止返回多个结果集的存储过程的使用。将多个内联表值用户定义函数包装在单个存储过程中可以实现这一点。

5
一个表值函数是一个适当的解决方案。我们应该注意到它的一些小缺点:表值函数是额外的数据库对象,可能需要授予权限。 - spencer7593
3
喜欢这个解决方案。我遇到了一个小问题,就是我的表不能使用排序功能,而存储过程可以使用。哎,没关系,我会解决的。 - mrwaim
7
又遇到了一个问题 - “无法从函数内访问临时表” - mrwaim
8
原始问题是如何创建一个包含存储过程结果的临时表。这是一个好的模式,但并没有解决这个问题。 - greg
19
格雷格,我回答的第一行说:“这是对你问题稍作修改后的答案。” 你的评论是多余的。 - Christian Loris

150
Select @@ServerName
EXEC sp_serveroption @@ServerName, 'DATA ACCESS', TRUE

SELECT  *
INTO    #tmpTable
FROM    OPENQUERY(YOURSERVERNAME, 'EXEC db.schema.sproc 1')

2
获取“Msg 208,Level 16,State 1,Line 1 Invalid object name 'tmpBusLine'(可能是因为它没有事先定义)”。 - Ferdeen
1
@Ferds:抱歉,一开始没明白你的请求。现在已更新为另一个解决方案。 - Quassnoi
31
好的解决方案。有一个注意点,您需要在服务器上启用“数据访问”:执行sp_serveroption 'TheServerName','DATA ACCESS',TRUE - jcollum
10
您还需要允许远程访问服务器。这可能会带来安全风险。 - BraveNewMath
1
完美。这适用于现有的链接服务器,并允许我执行远程表值函数,这是不直接支持的。 - Mr. TA
9
如果目标存储过程使用临时表,那么这个方法将不起作用。 - Sal

132

最简单的解决方案:

CREATE TABLE #temp (...);

INSERT INTO #temp
EXEC [sproc];
如果您不知道架构,可以按以下步骤操作。请注意,这种方法存在严重的安全风险。
SELECT * 
INTO #temp
FROM OPENROWSET('SQLNCLI', 
                'Server=localhost;Trusted_Connection=yes;', 
                'EXEC [db].[schema].[sproc]')

3
如果我不知道返回结果集的列,那么就不能在逻辑中使用它。如果你不知道数据是什么,那么你将如何使用它? - Adriaan Davel
@AdriaanDavel 我同意你的观点,即你应该始终了解你的数据(最佳实践),但他可能想说的是有时存储过程返回动态列,而你并不总是知道模式会是什么样子。在这种情况下,你可以使用OPENROWSET来插入和创建一个临时表。然而,这样做显然存在安全风险... - Tigerjz32
1
@nurettin 有时候你不知道存储过程会返回什么。那么这种情况下会发生什么?如果你不知道存储过程会返回什么,你怎么创建一个临时表并从存储过程中插入数据呢? - Tigerjz32
我想,如果你必须插入数据,你可以匹配列名和类型,使用我向你展示的带有目标表的 select 语句来创建一个 insert 语句。 - nurettin
@Tigerjz32 我知道问题是什么,但我不能让网络上的某个人错了 :-) - nurettin
显示剩余9条评论

131

当存储过程返回大量列且你不想手动“创建”一个临时表来保存结果时,我发现最简单的方法是进入存储过程,在最后的select语句上添加一个“into”子句并在where子句中添加1=0。

运行存储过程一次,然后回去删除刚刚添加的SQL代码。现在,你将拥有一个与存储过程结果匹配的空表。你可以为临时表“脚本表作为创建”,或直接插入到该表中。


9
+1,很好的建议。您甚至可以为存储过程添加一个名为@TableCreate或类似名称的快速可选变量,当该变量不为空时,执行上述步骤。这样一来,一旦设置完成,就无需更改存储过程。 - Ian Roke
1
@dotjoe 您是否会使用 SELECT INTO 语句将数据插入到临时表中,并从临时表创建脚本表?虽然临时表显示在 tempdb 中,但我无法右键单击并创建脚本。谢谢您的帮助。 - DotnetDude
2
@DotNetDude 你可以使用 select ... into new_table 隐式地创建一个实际的表。 - dotjoe
然后从空表模式中获取粗略列定义;将末尾的'...'替换为合法的TABLE_NAME:declare @s varchar(max)='';select @s=@s+','+COLUMN_NAME+' '+DATA_TYPE+isnull('('+case CHARACTER_MAXIMUM_LENGTH when -1 then 'max' else cast(CHARACTER_MAXIMUM_LENGTH as varchar(10))end+')','')from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME ='...';select @s - user423430
1
这是最好的解决方案! - Lucas925
迄今为止最好的解决方案。我会把它放在我的技巧袋里,哈哈。 - thedude

68
declare @temp table
(
    name varchar(255),
    field varchar(255),
    filename varchar(255),
    filegroup varchar(255),
    size varchar(255),
    maxsize varchar(255),
    growth varchar(255),
    usage varchar(255)
);
INSERT @temp  Exec sp_helpfile;
select * from @temp;

6
未回答原始问题,未定义临时表即进行插入操作。 - t.durden
这正是我所需要的。当我无法控制过程时,这使我能够向结果集添加分页。 - TheRealChx101

56
如果您的存储过程结果表太复杂,无法手动输入“create table”语句,并且不能使用OPENQUERY或OPENROWSET,则可以使用sp_help为您生成列和数据类型列表。一旦您获得了列的列表,只需要将其格式化以适应您的需求即可。 步骤1:在输出查询中添加“into #temp”(例如,“select [...] into #temp from [...]”)。 最简单的方法是直接编辑存储过程中的输出查询。如果您无法更改存储过程,则可以将内容复制到新的查询窗口中并在那里修改查询。 步骤2:对临时表运行sp_help。(例如,“exec tempdb..sp_help #temp”) 创建临时表后,在临时表上运行sp_help以获取列和数据类型列表,包括varchar字段的大小。 步骤3:复制数据列和类型到create table语句中 我有一个Excel表格,用于将sp_help的输出格式化为“create table”语句。您不需要任何高级工具,只需将其复制并粘贴到SQL编辑器中。使用列名、大小和类型构建一个“Create table #x [...]”或“declare @x table [...]”语句,您可以使用它来INSERT存储过程的结果。 步骤4:插入到新创建的表中 现在,您将拥有一个类似于本主题中描述的其他解决方案的查询。
DECLARE @t TABLE 
(
   --these columns were copied from sp_help
   COL1 INT,
   COL2 INT   
)

INSERT INTO @t 
Exec spMyProc 

这个技巧也可以用来将临时表(#temp)转换为表变量(@temp)。虽然这可能比自己编写create table语句多了一些步骤,但它可以防止在大型流程中手动出现的错误,如拼写错误和数据类型不匹配。调试一个拼写错误可能需要比一开始编写查询耗费更多的时间。


49

您的存储过程仅用于检索数据还是还会进行修改?如果仅用于检索,您可以将存储过程转换为函数,并使用通用表达式(CTE),而无需声明它,如下所示:

with temp as (
    select * from dbo.fnFunctionName(10, 20)
)
select col1, col2 from temp

无论如何,需要从CTE中检索的内容只能在一个语句中使用。您不能执行 with temp as ... ,然后尝试在几行SQL之后使用它。您可以在一个语句中使用多个CTE以进行更复杂的查询。

例如:

with temp1020 as (
    select id from dbo.fnFunctionName(10, 20)
),
temp2030 as (
    select id from dbo.fnFunctionName(20, 30)
)
select * from temp1020 
where id not in (select id from temp2030)

1
这些不是临时表,而是CTE。 http://technet.microsoft.com/en-us/library/ms190766%28v=sql.105%29.aspx - yucer
6
谢谢@yucer...我相信那时候我不知道它们被称为CTEs :) - Rashmi Pandit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,