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

我如何做一个SELECT * INTO [temp table] FROM [stored procedure]?不是FROM [Table],没有定义[temp table]

SelectBusinessLinetmpBusLine的所有数据都正常工作。

select *into tmpBusLinefrom BusinessLine

我正在尝试相同的方法,但使用返回数据的stored procedure并不完全相同。

select *into tmpBusLinefromexec getBusinessLineHistory '16 Mar 2009'

输出消息:

Msg 156, Level 15, State 1, Line 2关键字附近的语法不正确“执行”。

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

2382159 次浏览

在SQLServer 2005中,可以使用INSERT INTO ... EXEC将存储过程的结果插入表中。

--INSERT...EXECUTE procedure exampleINSERT author_sales EXECUTE get_author_sales
Select @@ServerNameEXEC sp_serveroption @@ServerName, 'DATA ACCESS', TRUE
SELECT  *INTO    #tmpTableFROM    OPENQUERY(YOURSERVERNAME, 'EXEC db.schema.sproc 1')

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

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

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

CREATE TABLE #tmpBus(COL1 INT,COL2 INT)
INSERT INTO #tmpBusExec SpGetRecords 'Params'

您可以为此使用OPENROWSET。看一看。我还包含了sp_configure代码,以启用自组织分布式查询,以防它尚未启用。

CREATE PROC getBusinessLineHistoryASBEGINSELECT * FROM sys.databasesENDGO
sp_configure 'Show Advanced Options', 1GORECONFIGUREGOsp_configure 'Ad Hoc Distributed Queries', 1GORECONFIGUREGO
SELECT * INTO #MyTempTable FROM OPENROWSET('SQLNCLI', 'Server=(local)\SQL2008;Trusted_Connection=yes;','EXEC getBusinessLineHistory')
SELECT * FROM #MyTempTable

这是对你问题的一个稍微修改版本的回答。如果你可以放弃对用户定义函数使用存储过程,你可以使用内联表值用户定义函数。这本质上是一个存储过程(将接受参数),它将表作为结果集返回;因此将与INTO语句很好地放在一起。

这里有一个很好的快速文章和其他用户定义函数。如果你仍然需要存储过程,你可以将内联表值用户定义函数包装为存储过程。存储过程在调用内联表值用户定义函数的选择*时只是传递参数。

因此,例如,您将有一个内联表值用户定义函数来获取特定区域的客户列表:

CREATE FUNCTION CustomersByRegion(@RegionID int)RETURNS TABLEASRETURNSELECT *FROM customersWHERE RegionID = @RegionIDGO

然后你可以调用这个函数来得到你的结果:

SELECT * FROM CustomersbyRegion(1)

或者做一个SELECT INTO:

SELECT * INTO CustList FROM CustomersbyRegion(1)

如果您仍然需要存储过程,则将函数包装为:

CREATE PROCEDURE uspCustomersByRegion(@regionID int)ASBEGINSELECT * FROM CustomersbyRegion(@regionID);ENDGO

我认为这是获得所需结果的最“少黑客”的方法。它使用现有功能,因为它们旨在使用而没有额外的复杂性。通过在存储过程中嵌套内联表值用户定义函数,您可以通过两种方式访问该功能。另外!您对实际SQL代码只有一个维护点。

有人建议使用OPENROWSET,但这不是OPENROWSET函数的目的(来自Books Online):

包含所有连接信息这是访问远程数据所必需的从OLE DB数据源。这方法是访问的替代方法链接服务器中的表,并且是一次性、即席的连接方法并使用OLE访问远程数据DB。更频繁地引用OLE DB数据源,使用链接服务器。

使用OPENROWSET可以完成工作,但它会在打开本地连接和编组数据方面产生一些额外的开销。它也可能不是所有情况下的选择,因为它需要临时查询权限,这会带来安全风险,因此可能不理想。此外,OPENROWSET方法将排除使用返回多个结果集的存储过程。在单个存储过程中包装多个内联表值用户定义函数可以实现这一点。

你的存储过程只检索数据还是也修改数据?如果它只用于检索,你可以将存储过程转换为函数并使用公用表表达式(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 temp1020where id not in (select id from temp2030)

我找到了将数组/数据表传递到存储过程中,这可能会给你另一个关于如何解决问题的想法。

链接建议使用图片类型参数传入存储过程。然后在存储过程中,图像被转换为包含原始数据的表变量。

也许有一种方法可以与临时表一起使用。

另一种方法是创建一个类型并使用PIPELINED然后传回您的对象。然而,这仅限于知道列。但它的优点是能够做到:

SELECT *FROM TABLE(CAST(f$my_functions('8028767') AS my_tab_type))

Quassnoi把我大部分时间都放在那里,但缺少一件事:

****我需要在存储过程中使用参数。

而OPENQUERY不允许这种情况发生:

因此,我找到了一种工作系统的方法,并且不必使表定义如此僵化,并在另一个存储过程中重新定义它(当然,它可能会崩溃)!

是的,您可以通过以下方式动态创建从存储过程返回的表定义使用带有虚假变量的OPENQUERY语句(只要无结果集返回与具有良好数据的数据集相同数量的字段和相同位置)。

创建表后,您可以整天在临时表中使用exec存储过程。


请注意(如上所述)您必须启用数据访问,

EXEC sp_serveroption 'MYSERVERNAME', 'DATA ACCESS', TRUE

代码:

declare @locCompanyId varchar(8)declare @locDateOne datetimedeclare @locDateTwo datetime
set @locDateOne = '2/11/2010'set @locDateTwo = getdate()
--Build temporary table (based on bogus variable values)--because we just want the table definition and--since openquery does not allow variable definitions...--I am going to use bogus variables to get the table defintion.
select * into #tempCoAttendanceRpt20100211FROM OPENQUERY(DBASESERVER,'EXEC DATABASE.dbo.Proc_MyStoredProc 1,"2/1/2010","2/15/2010 3:00 pm"')
set @locCompanyId = '7753231'
insert into #tempCoAttendanceRpt20100211EXEC DATABASE.dbo.Proc_MyStoredProc @locCompanyId,@locDateOne,@locDateTwo
set @locCompanyId = '9872231'
insert into #tempCoAttendanceRpt20100211EXEC DATABASE.dbo.Proc_MyStoredProc @locCompanyId,@locDateOne,@locDateTwo
select * from #tempCoAttendanceRpt20100211drop table #tempCoAttendanceRpt20100211

感谢最初提供的信息…是的,我终于不用制造这些虚假的东西了(严格)表定义,当使用数据从另一个存储过程或数据库,是的您也可以使用参数。

搜索参考标签:

  • SQL2005存储过程到临时表

  • OpenQuery与存储过程和变量2005

  • 带变量的OpenQuery

  • 执行存储过程到临时表

更新:这将不适用于临时表,所以我不得不手动创建临时表。

糟糕的通知:这不适用于临时表http://www.sommarskog.se/share_data.html#OPENQUERY

参考:接下来是定义LOCALSERVER。它可能看起来像示例中的关键字,但实际上只是一个名称。这是您的操作方式:

sp_addlinkedserver @server = 'LOCALSERVER',  @srvproduct = '',@provider = 'SQLOLEDB', @datasrc = @@servername

要创建链接服务器,您必须具有ALTER ANY SERVER权限,或者是任何固定服务器角色sysadmin或setupadmin的成员。

OPENQUERY打开到SQL服务器的新连接。

使用OPENQUERY调用的过程不能引用在当前连接中创建的临时表。

新连接有自己的默认数据库(用sp_addlinkedserver定义,默认为master),因此所有对象规范都必须包含库名。

如果您有一个打开的事务并且在调用OPENQUERY时持有锁,则被调用的过程无法访问您锁定的内容。也就是说,如果您不小心,您将阻塞自己。

连接不是免费的,因此存在性能损失。

代码

CREATE TABLE #T1(col1 INT NOT NULL,col2 NCHAR(50) NOT NULL,col3 TEXT NOT NULL,col4 DATETIME NULL,col5 NCHAR(50) NULL,col6 CHAR(2) NULL,col6 NCHAR(100) NULL,col7 INT NULL,col8 NCHAR(50) NULL,col9 DATETIME NULL,col10 DATETIME NULL)
DECLARE @Para1 intDECLARE @Para2 varchar(32)DECLARE @Para3 varchar(100)DECLARE @Para4 varchar(15)DECLARE @Para5 varchar (12)DECLARE @Para6 varchar(1)DECLARE @Para7 varchar(1)

SET @Para1 = 1025SET @Para2 = N'6as54fsd56f46sd4f65sd'SET @Para3 = N'XXXX\UserName'SET @Para4 = N'127.0.0.1'SET @Para5 = N'XXXXXXX'SET @Para6 = N'X'SET @Para7 = N'X'
INSERT INTO #T1(col1,col2,col3,col4,col5,col6,col6,col7,col8,col9,col10,)EXEC [dbo].[usp_ProcedureName] @Para1, @Para2, @Para3, @Para4, @Para5, @Para6, @Para6

我希望这有帮助。请酌情限定。

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;

这个存储的过程完成了这项工作:

CREATE PROCEDURE [dbo].[ExecIntoTable](@tableName          NVARCHAR(256),@storedProcWithParameters   NVARCHAR(MAX))ASBEGINDECLARE @driver         VARCHAR(10)DECLARE @connectionString   NVARCHAR(600)DECLARE @sql            NVARCHAR(MAX)DECLARE @rowsetSql      NVARCHAR(MAX)
SET @driver = '''SQLNCLI'''
SET @connectionString ='''server=' +CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(256)) +COALESCE('\' + CAST(SERVERPROPERTY('InstanceName') AS NVARCHAR(256)), '') +';trusted_connection=yes'''
SET @rowsetSql = '''EXEC ' + REPLACE(@storedProcWithParameters, '''', '''''') + ''''
SET @sql = 'SELECT*INTO' + @tableName + 'FROMOPENROWSET(' + @driver + ',' + @connectionString + ',' + @rowsetSql + ')'
EXEC (@sql)ENDGO

这是一个轻微的返工:将存储过程结果插入表,所以它实际上是有效的。

如果您希望它与临时表一起工作,那么您需要使用##GLOBAL表并在之后删除它。

如果存储过程的结果表太复杂,无法手动键入“createtable”语句,并且您不能使用OPENQUERY或OPENROWSET,您可以使用sp_help为您生成列和数据类型的列表。一旦您有了列列表,只需将其格式化以满足您的需求即可。

步骤1:在输出查询中添加“into#temp”(例如“选择[…]into#temp from[…]”)。

最简单的方法是直接编辑proc中的输出查询。如果您不能更改存储的过程,您可以将内容复制到新的查询窗口并在那里修改查询。

第2步:在temp表上运行sp_help(例如“exec temdb…sp_help#temp”)

创建临时表后,在临时表上运行sp_help以获取列和数据类型的列表,包括varchar字段的大小。

步骤3:将数据列和类型复制到创建表语句中

我有一个Excel工作表,用于将sp_help的输出格式化为“创建表”语句。您不需要任何花哨的东西,只需复制并粘贴到SQL编辑器中。使用列名、大小和类型来构造“创建表#x[…]”或“声明@x表[…]”语句,您可以使用它来插入存储过程的结果。

第4步:插入新创建的表

现在您将有一个类似于本线程中描述的其他解决方案的查询。

DECLARE @t TABLE(--these columns were copied from sp_helpCOL1 INT,COL2 INT)
INSERT INTO @tExec spMyProc

此技术也可用于将临时表(#temp)转换为表变量(@temp)。虽然这可能比自己编写create table语句需要更多步骤,但它可以防止大型进程中的错字和数据类型不匹配等手动错误。调试错字可能比首先编写查询需要更多时间。

  1. 我正在使用以下模式和数据创建一个表。

  2. 创建存储过程。

  3. 现在我知道我的过程的结果是什么,所以我执行以下查询。

     CREATE TABLE [dbo].[tblTestingTree]([Id] [int] IDENTITY(1,1) NOT NULL,[ParentId] [int] NULL,[IsLeft] [bit] NULL,[IsRight] [bit] NULL,CONSTRAINT [PK_tblTestingTree] PRIMARY KEY CLUSTERED([Id] ASC) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]) ON [PRIMARY]GOSET IDENTITY_INSERT [dbo].[tblTestingTree] ONINSERT [dbo].[tblTestingTree] ([Id], [ParentId], [IsLeft], [IsRight]) VALUES (1, NULL, NULL, NULL)INSERT [dbo].[tblTestingTree] ([Id], [ParentId], [IsLeft], [IsRight]) VALUES (2, 1, 1, NULL)INSERT [dbo].[tblTestingTree] ([Id], [ParentId], [IsLeft], [IsRight]) VALUES (3, 1, NULL, 1)INSERT [dbo].[tblTestingTree] ([Id], [ParentId], [IsLeft], [IsRight]) VALUES (4, 2, 1, NULL)INSERT [dbo].[tblTestingTree] ([Id], [ParentId], [IsLeft], [IsRight]) VALUES (5, 2, NULL, 1)INSERT [dbo].[tblTestingTree] ([Id], [ParentId], [IsLeft], [IsRight]) VALUES (6, 3, 1, NULL)INSERT [dbo].[tblTestingTree] ([Id], [ParentId], [IsLeft], [IsRight]) VALUES (7, 3, NULL, 1)INSERT [dbo].[tblTestingTree] ([Id], [ParentId], [IsLeft], [IsRight]) VALUES (8, 4, 1, NULL)INSERT [dbo].[tblTestingTree] ([Id], [ParentId], [IsLeft], [IsRight]) VALUES (9, 4, NULL, 1)INSERT [dbo].[tblTestingTree] ([Id], [ParentId], [IsLeft], [IsRight]) VALUES (10, 5, 1, NULL)
    SET IDENTITY_INSERT [dbo].[tblTestingTree] OFFVALUES (10, 5, 1, NULL)SET IDENTITY_INSERT [dbo].[tblTestingTree] On
    
    create procedure GetDateasbeginselect Id,ParentId from tblTestingTreeend
    create table tbltemp(id int,ParentId int)insert into tbltempexec GetDate
    select * from tbltemp;

如果OPENROWSET导致您出现问题,那么从2012年起还有另一种方法;使用sys.dm_exec_describe_first_result_set_for_object,如下所述:检索存储过程的列名和类型?

首先,创建这个存储过程来生成临时表的SQL:

CREATE PROCEDURE dbo.usp_GetStoredProcTableDefinition(@ProcedureName  nvarchar(128),@TableName      nvarchar(128),@SQL            nvarchar(max) OUTPUT)ASSET @SQL = 'CREATE TABLE ' + @tableName + ' ('
SELECT @SQL = @SQL + '['+name +'] '+ system_type_name +''  + ','FROM sys.dm_exec_describe_first_result_set_for_object(OBJECT_ID(@ProcedureName),NULL);
--Remove trailing commaSET @SQL = SUBSTRING(@SQL,0,LEN(@SQL))SET @SQL =  @SQL +')'

要使用该过程,请按以下方式调用它:

DECLARE     @SQL    NVARCHAR(MAX)
exec dbo.usp_GetStoredProcTableDefinition@ProcedureName='dbo.usp_YourProcedure',@TableName='##YourGlobalTempTable',@SQL = @SQL OUTPUT
INSERT INTO ##YourGlobalTempTableEXEC    [dbo].usp_YourProcedure
select * from ##YourGlobalTempTable

请注意,我使用的是全局临时表。这是因为使用EXEC运行动态SQL会创建自己的会话,因此普通临时表将超出任何后续代码的范围。如果全局临时表出现问题,您可以使用普通临时表,但任何后续SQL都需要是动态的,也就是说,也由EXEC语句执行。

我遇到了同样的问题,这是我从保罗的建议为此所做的。这里的主要部分是使用NEWID()来避免多个用户同时运行存储过程/脚本,全局临时表的痛苦。

DECLARE @sql varchar(max) = '',@tmp_global_table varchar(255) = '##global_tmp_' + CONVERT(varchar(36), NEWID())SET @sql = @sql + 'select * into [' + @tmp_global_table + '] from YOURTABLE'EXEC(@sql)
EXEC('SELECT * FROM [' + @tmp_global_table + ']')

我会做以下事情

  1. 创建(将SP转换为)UDF(表值UDF)。

  2. select * into #tmpBusLine from dbo.UDF_getBusinessLineHistory '16 Mar 2009'

最简单的解决方案:

CREATE TABLE #temp (...);
INSERT INTO #tempEXEC [sproc];

如果您不知道模式,那么您可以执行以下操作。请请注意,此方法存在严重的安全风险。

SELECT *INTO #tempFROM OPENROWSET('SQLNCLI','Server=localhost;Trusted_Connection=yes;','EXEC [db].[schema].[sproc]')

如果您知道正在传递的参数,并且您无权sp_configure,则使用这些参数编辑存储过程,并且可以将其存储在##全局表中。

为了将存储过程的第一个记录集插入临时表,您需要了解以下内容:

  1. 只能将存储过程的第一行集插入临时表
  2. 存储过程不得执行动态T-SQL语句(sp_executesql
  3. 您需要先定义临时表的结构

以上可能看起来是限制,但恕我直言,它完全有意义-如果您使用sp_executesql,您可以一次返回两列,一次返回十列,如果您有多个结果集,您不能将它们插入到多个表中-您可以在一个T-SQL语句中插入两个表中的最大值(使用OUTPUT子句,没有触发器)。

因此,问题主要是如何在执行EXEC ... INTO ...语句之前定义临时表结构。

第一个适用于OBJECT_ID,而第二个和第三个也适用于自组织查询。我更喜欢使用DMV而不是sp,因为您可以使用CROSS APPLY并同时为多个过程构建临时表定义。

SELECT p.name, r.*FROM sys.procedures AS pCROSS APPLY sys.dm_exec_describe_first_result_set_for_object(p.object_id, 0) AS r;

另外,请注意system_type_name字段,因为它非常有用。它存储列完整定义。例如:

smalldatetimenvarchar(max)uniqueidentifiernvarchar(1000)realsmalldatetimedecimal(18,2)

在大多数情况下,您可以直接使用它来创建表定义。

因此,我认为在大多数情况下(如果存储过程符合某些条件),您可以轻松构建动态语句来解决此类问题(创建临时表,在其中插入存储过程结果,对数据进行所需操作)。


请注意,上面的对象无法定义某些案件中的第一个结果集数据,例如在执行动态T-SQL语句或在存储过程中使用临时表时。

如果查询不包含参数,请使用OpenQuery,否则使用OpenRowset

基本的事情是根据存储过程创建模式并插入到该表中。

DECLARE @abc TABLE(RequisitionTypeSourceTypeID INT, RequisitionTypeID INT, RequisitionSourcingTypeID INT, AutoDistOverride INT, AllowManagerToWithdrawDistributedReq INT, ResumeRequired INT, WarnSupplierOnDNRReqSubmission  INT, MSPApprovalReqd INT, EnableMSPSupplierCounterOffer INT, RequireVendorToAcceptOffer INT, UseCertification INT, UseCompetency INT, RequireRequisitionTemplate INT, CreatedByID INT, CreatedDate DATE, ModifiedByID INT, ModifiedDate DATE, UseCandidateScheduledHours INT, WeekEndingDayOfWeekID INT, AllowAutoEnroll INT)INSERT INTO @abcEXEC [dbo].[usp_MySp] 726,3SELECT * FROM @abc

如果你足够幸运SQL2012年或更高,你可以使用dm_exec_describe_first_result_set_for_object

我刚刚编辑了gotqn提供的sql。谢谢gotqn。

这将创建一个名称与过程名称相同的全局临时表。稍后可以根据需要使用临时表。只是不要忘记在重新执行之前删除它。

    declare @procname nvarchar(255) = 'myProcedure',@sql nvarchar(max)
set @sql = 'create table ##' + @procname + ' ('beginselect      @sql = @sql + '[' + r.name + '] ' +  r.system_type_name + ','from        sys.procedures AS pcross apply sys.dm_exec_describe_first_result_set_for_object(p.object_id, 0) AS rwhere       p.name = @procname
set @sql = substring(@sql,1,len(@sql)-1) + ')'execute (@sql)execute('insert ##' + @procname + ' exec ' + @procname)end

这可以在SQLServer 2014+中完成,前提是存储过程只返回一个表。如果有人找到了对多个表执行此操作的方法,我很想知道。

DECLARE @storedProcname NVARCHAR(MAX) = ''SET @storedProcname = 'myStoredProc'
DECLARE @strSQL AS VARCHAR(MAX) = 'CREATE TABLE myTableName '
SELECT @strSQL = @strSQL+STUFF((SELECT ',' +name+' ' + system_type_nameFROM sys.dm_exec_describe_first_result_set_for_object (OBJECT_ID(@storedProcname),0)FOR XML PATH('')),1,1,'(') + ')'
EXEC (@strSQL)
INSERT INTO myTableName
EXEC ('myStoredProc @param1=1, @param2=2')
SELECT * FROM myTableName
DROP TABLE myTableName

这将从系统表中提取返回表的定义,并使用它来为您构建临时表。然后,您可以如前所述从存储过程中填充它。

还有一些变体也适用于动态SQL。

好吧,你必须创建一个临时表,但它不一定有正确的模式……我创建了一个存储过程来修改现有的临时表,以便它具有具有正确数据类型和顺序的所需列(删除所有现有列,添加新列):

GOcreate procedure #TempTableForSP(@tableId int, @procedureId int)asbegindeclare @tableName varchar(max) =  (select namefrom tempdb.sys.tableswhere object_id = @tableId);declare @tsql nvarchar(max);declare @tempId nvarchar(max) = newid();set @tsql = 'declare @drop nvarchar(max) = (select  ''alter table tempdb.dbo.' + @tableName+  ' drop column ''  + quotename(c.name) + '';''+ char(10)from tempdb.sys.columns cwhere c.object_id =  ' +cast(@tableId as varchar(max)) + 'for xml path(''''))alter table tempdb.dbo.' + @tableName + ' add ' + QUOTENAME(@tempId) + ' int;exec sp_executeSQL @drop;declare @add nvarchar(max) = (select ''alter table ' + @tableName+ ' add '' + name+ '' '' + system_type_name+ case when d.is_nullable=1 then '' null '' else '''' end+ char(10)from sys.dm_exec_describe_first_result_set_for_object('+ cast(@procedureId as varchar(max)) + ', 0) dorder by column_ordinalfor xml path(''''))
execute sp_executeSQL  @add;alter table '  + @tableName + ' drop column ' + quotename(@tempId) + '  ';execute sp_executeSQL @tsql;endGO
create table #exampleTable (pk int);
declare @tableId int = object_Id('tempdb..#exampleTable')declare @procedureId int = object_id('examplestoredProcedure')
exec #TempTableForSP @tableId, @procedureId;
insert into #exampleTableexec examplestoredProcedure

说明如果sys.dm_exec_describe_first_result_set_for_object不能确定存储过程的结果(例如,如果它使用临时表),这将不起作用。

这是一个简单的两步过程:创建一个临时表-插入到临时表中。

执行相同操作的代码:

CREATE TABLE #tempTable (Column1 int, Column2 varchar(max));INSERT INTO #tempTableEXEC [app].[Sproc_name]@param1 = 1,@param2 =2;

在四处搜索之后,我找到了一种方法,可以为任何存储过程动态创建临时表,而无需使用OPENROWSETOPENQUERY,使用存储过程结果定义的通用模式,尤其是当您不是数据库管理员时。

SQL服务器有一个内置的procsp_describe_first_result_set,可以为您提供任何过程结果集的模式。我根据此过程的结果创建了一个模式表,并手动将所有字段设置为NULLABLE。

declare @procname varchar(100) = 'PROCEDURENAME' -- your procedure namedeclare @param varchar(max) = '''2019-06-06''' -- your parametersdeclare @execstr nvarchar(max) = N'exec ' + @procnamedeclare @qry nvarchar(max)
-- Schema table to store the result from sp_describe_first_result_set.create table #d(is_hidden  bit  NULL, column_ordinal   int  NULL, name sysname NULL, is_nullable   bit  NULL, system_type_id   int  NULL, system_type_name nvarchar(256) NULL,max_length  smallint  NULL, precision   tinyint  NULL,  scale   tinyint  NULL,  collation_name  sysname NULL, user_type_id  int NULL, user_type_database    sysname NULL,user_type_schema    sysname NULL,user_type_name sysname NULL,assembly_qualified_type_name   nvarchar(4000),xml_collection_id    int NULL,xml_collection_database    sysname NULL,xml_collection_schema   sysname NULL,xml_collection_name    sysname NULL,is_xml_document    bit  NULL,is_case_sensitive bit  NULL,is_fixed_length_clr_type  bit  NULL,source_server   sysname NULL,source_database    sysname NULL,source_schema  sysname NULL,source_table   sysname NULL,source_column  sysname NULL,is_identity_column bit NULL,is_part_of_unique_key   bit NULL,is_updateable  bit NULL,is_computed_column bit NULL,is_sparse_column_set   bit NULL,ordinal_in_order_by_list   smallint NULL,order_by_list_length    smallint NULL,order_by_is_descending    smallint NULL,tds_type_id   int  NULL,tds_length    int  NULL,tds_collation_id  int NULL,tds_collation_sort_id   tinyint NULL)

-- Get result set definition of your procedureinsert into #dEXEC sp_describe_first_result_set @exestr, NULL, 0
-- Create a query to generate and populate a global temp table from above resultsselect@qry = 'Create table ##t(' +stuff((select ',' + name + ' '+ system_type_name + ' NULL'from #d d For XML Path, TYPE).value(N'.[1]', N'nvarchar(max)'), 1,1,'')+ ')
insert into ##tExec '+@procname+' ' + @param
Exec sp_executesql @qry
-- Use below global temp table to query the data as you mayselect * from ##t
-- **WARNING** Don't forget to drop the global temp table ##t.--drop table ##tdrop table #d

在Sql Server版本上开发和测试-MicrosoftSQLServer 2016(RTM)-13.0.1601.5(Build 17134:)

您可以调整您正在使用的SQL服务器版本的架构(如果需要)。

这个问题已经晚了几年,但我需要这样的东西来快速而肮脏的代码生成。我相信正如其他人所说,预先定义临时表更容易,但这种方法应该适用于简单的存储过程查询或sql语句。

这会有点复杂,但它借鉴了这里的贡献者以及DBA Stack Exchange获取存储过程结果列类型中Paul White的解决方案。再次重申,这种方法和示例不是为多用户环境中的进程设计的。在这种情况下,表定义在全局临时表中设置了很短的时间,以供代码生成模板进程参考。

我还没有完全测试过,所以可能会有一些警告,所以你可能想去保罗怀特的答案中的MSDN链接。这适用于SQL2012年及更高版本。

首先使用类似于Oracle描述的存储过程sp_describe_first_result_set

这将评估第一个结果集的第一行,因此如果您的存储过程或语句返回多个查询,它将仅描述第一个结果。

我创建了一个存储过程来分解返回单个字段以供选择以创建临时表定义的任务。

CREATE OR ALTER PROCEDURE [dbo].[sp_GetTableDefinitionFromSqlBatch_DescribeFirstResultSet](@sql NVARCHAR(4000),@table_name VARCHAR(100),@TableDefinition NVARCHAR(MAX) OUTPUT)ASBEGINSET NOCOUNT ONDECLARE @TempTableDefinition NVARCHAR(MAX)DECLARE @NewLine NVARCHAR(4) = CHAR(13)+CHAR(10)
DECLARE @ResultDefinition TABLE (  --The View Definition per MSDNis_hidden         bit NOT NULL, column_ordinal    int NOT NULL, [name]            sysname NULL, is_nullable       bit NOT NULL, system_type_id    int NOT NULL, system_type_name  nvarchar(256) NULL, max_length        smallint NOT NULL, [precision]       tinyint NOT NULL, scale             tinyint NOT NULL, collation_name    sysname NULL, user_type_id      int NULL, user_type_database    sysname NULL, user_type_schema  sysname NULL, user_type_name    sysname NULL, assembly_qualified_type_name      nvarchar(4000), xml_collection_id         int NULL, xml_collection_database   sysname NULL, xml_collection_schema     sysname NULL, xml_collection_name       sysname NULL, is_xml_document           bit NOT NULL, is_case_sensitive         bit NOT NULL, is_fixed_length_clr_type  bit NOT NULL, source_server             sysname NULL, source_database           sysname NULL, source_schema             sysname NULL, source_table              sysname NULL, source_column             sysname NULL, is_identity_column        bit NULL, is_part_of_unique_key     bit NULL, is_updateable             bit NULL, is_computed_column        bit NULL, is_sparse_column_set      bit NULL, ordinal_in_order_by_list  smallint NULL, order_by_is_descending    smallint NULL, order_by_list_length      smallint NULL, tds_type_id               int NOT NULL, tds_length                int NOT NULL, tds_collation_id          int NULL, tds_collation_sort_id     tinyint NULL)
--Insert the description into table variableINSERT @ResultDefinitionEXEC sp_describe_first_result_set @sql
--Now Build the string to create the table via union select statement;WITH STMT AS (SELECT N'CREATE TABLE ' + @table_name + N' (' AS TextValUNION ALL
SELECTCONCAT(CASE column_ordinalWHEN 1 THEN '     ' ELSE '   , ' END  --Determines if comma should precede, QUOTENAME([name]) , '   ', system_type_name  -- Column Name and SQL TYPE,CASE is_nullableWHEN 0 THEN '   NOT NULL' ELSE '   NULL' END --NULLABLE CONSTRAINT) AS TextValFROM @ResultDefinition WHERE is_hidden = 0  -- May not be neededUNION ALL
SELECT N');' + @NewLine)
--Now Combine the rows to a single StringSELECT @TempTableDefinition = COALESCE (@TempTableDefinition + @NewLine + TextVal, TextVal) FROM STMT
SELECT @TableDefinition = @TempTableDefinitionEND

难题是您需要使用全局表,但您需要使其足够独特因此,您可以经常删除和创建它,而不必担心碰撞。在示例中,我使用Guid(FE264BF5_9C32_438F_8462_8A5DC8DEE49E)作为全局变量,将连字符替换为下划线

DECLARE @sql NVARCHAR(4000) = N'SELECT @@SERVERNAME as ServerName, GETDATE() AS Today;'DECLARE @GlobalTempTable VARCHAR(100) = N'##FE264BF5_9C32_438F_8462_8A5DC8DEE49E_MyTempTable'
--@sql can be a stored procedure name like dbo.foo without parameters
DECLARE @TableDef NVARCHAR(MAX)
DROP TABLE IF EXISTS #MyTempTableDROP TABLE IF EXISTS ##FE264BF5_9C32_438F_8462_8A5DC8DEE49E_MyTempTable
EXEC [dbo].[sp_GetTableDefinitionFromSqlBatch_DescribeFirstResultSet]@sql, @GlobalTempTable, @TableDef OUTPUT
--Creates the global table ##FE264BF5_9C32_438F_8462_8A5DC8DEE49E_MyTempTableEXEC sp_executesql @TableDef
--Now Call the stored procedure, SQL Statement with Params etc.INSERT ##FE264BF5_9C32_438F_8462_8A5DC8DEE49E_MyTempTableEXEC sp_executesql @sql
--Select the results into your undefined Temp Table from the Global TableSELECT *INTO #MyTempTableFROM ##FE264BF5_9C32_438F_8462_8A5DC8DEE49E_MyTempTable
SELECT * FROM #MyTempTable
DROP TABLE IF EXISTS #MyTempTableDROP TABLE IF EXISTS ##FE264BF5_9C32_438F_8462_8A5DC8DEE49E_MyTempTable

同样,我只测试了简单的存储过程和因人而异的简单查询。

如果让动态SQL创建临时表,则此表归动态SQL连接所有,而不是从其调用存储过程的连接。

DECLARE @COMMA_SEPARATED_KEYS varchar(MAX);DROP TABLE IF EXISTS KV;CREATE TABLE KV (id_person int, mykey varchar(30), myvalue int);INSERT INTO KV VALUES(1, 'age', 16),(1, 'weight', 63),(1, 'height', 175),(2, 'age', 26),(2, 'weight', 83),(2, 'height', 185);WITH cte(mykey) AS (SELECT DISTINCT mykey FROM KV)SELECT @COMMA_SEPARATED_KEYS=STRING_AGG(mykey,',') FROM cte;SELECT @COMMA_SEPARATED_KEYS AS keys;

在此处输入图片描述

DECLARE @ExecuteExpression varchar(MAX);
DROP TABLE IF EXISTS #Pivoted;
SET @ExecuteExpression = N'SELECT *INTO #PivotedFROM(SELECTmykey,myvalue,id_personFROM KV) AS tPIVOT(MAX(t.myvalue)FOR mykey IN (COMMA_SEPARATED_KEYS)) AS pivot_table;';
SET @ExecuteExpression = REPLACE(@ExecuteExpression, 'COMMA_SEPARATED_KEYS', @COMMA_SEPARATED_KEYS);
EXEC(@ExecuteExpression);
SELECT * FROM #Pivoted;

Msg 208,16级,状态0无效的对象名称“#枢转”。这是因为#枢转属于动态SQL连接。所以最后一条指令

SELECT * FROM #Pivoted

失败。

避免遇到此问题的一种方法是确保对#枢轴的所有引用都来自动态查询本身:

DECLARE @COMMA_SEPARATED_KEYS varchar(MAX);DROP TABLE IF EXISTS KV;CREATE TABLE KV (id_person int, mykey varchar(30), myvalue int);INSERT INTO KV VALUES(1, 'age', 16),(1, 'weight', 63),(1, 'height', 175),(2, 'age', 26),(2, 'weight', 83),(2, 'height', 185);WITH cte(mykey) AS (SELECT DISTINCT mykey FROM KV)SELECT @COMMA_SEPARATED_KEYS=STRING_AGG(mykey,',') FROM cte;SELECT @COMMA_SEPARATED_KEYS AS keys;

DECLARE @ExecuteExpression varchar(MAX);
DROP TABLE IF EXISTS #Pivoted;
SET @ExecuteExpression = N'SELECT *INTO #PivotedFROM(SELECTmykey,myvalue,id_personFROM KV) AS tPIVOT(MAX(t.myvalue)FOR mykey IN (COMMA_SEPARATED_KEYS)) AS pivot_table;SELECT * FROM #Pivoted;';
SET @ExecuteExpression = REPLACE(@ExecuteExpression, 'COMMA_SEPARATED_KEYS', @COMMA_SEPARATED_KEYS);
EXEC(@ExecuteExpression);

在此处输入图片描述

这是我的参数T-SQL

--require one time execution if not configured beforesp_configure 'Show Advanced Options', 1GORECONFIGUREGO
--require one time execution if not configured beforesp_configure 'Ad Hoc Distributed Queries', 1GORECONFIGUREGO

--the queryDECLARE @param1 int = 1, @param2 int = 2DECLARE @SQLStr varchar(max) = 'SELECT * INTO #MyTempTableFROM OPENROWSET(''SQLNCLI'',''Server=ServerName;Database=DbName;Trusted_Connection=yes'',''exec StoredProcedureName '+ CAST(@param1 AS varchar(15)) +','+ CAST(@param2 AS varchar(15)) +''') AS a ;select * from #MyTempTable;drop table #MyTempTable';EXECUTE(@SQLStr);

首先,修改您的存储过程以将最终结果保存到临时表中。通过这样做,我们创建了一个与SP输出字段匹配的表。然后有一个选择语句将临时表保存为任何表名。然后执行步骤2中解释的SP

步骤1:修改存储过程以将最终结果保存到临时表中

[your stored procedure]
into #table_temp //this will insert the data to a temp table
from  #table_temp
select * into SP_Output_Table_1 from #table_temp //this will save data to a actual table

第2步:执行下面的SP,将记录插入到表中

Insert SP_Output_Table_1EXE  You_SP_Nane @Parameter1 = 52, @parameter2 =1
  1. 首先,选择一个空的结果集,然后创建它。
  2. 将exec SP运行到TABLE中
SELECT TOP 0 * INTO [temp_table] FROM [Table];
EXEC [StoredProcedure] INTO [temp_table];