检索存储过程结果集的列定义

我正在使用SQL Server 2008中的存储过程,并且我已经了解到,为了处理数据,我必须INSERT INTO已预定义的临时表。 这很好,除了我怎么知道如何定义我的临时表,如果我不是编写存储过程,而不是列出它的定义和通过代码读取?

例如,对于`EXEC sp_stored_procedure',我的临时表是什么样的? 这是一个简单的存储过程,我可能会猜测数据types,但似乎必须有一种方法来读取执行过程返回的列的types和长度。

假设你在tempdb中有一个存储过程:

 USE tempdb; GO CREATE PROCEDURE dbo.my_procedure AS BEGIN SET NOCOUNT ON; SELECT foo = 1, bar = 'tooth'; END GO 

有一个相当复杂的方式可以确定存储过程将输出的元数据。 有几个注意事项,包括程序只能输出一个结果集,如果不能精确确定,最好猜测数据types。 它需要使用'DATA ACCESS'属性设置为true的OPENQUERY和环回链接服务器。 你可以检查sys.servers,看看你是否已经有了一个有效的服务器,但是我们只需手动创build一个叫做loopback

 EXEC master..sp_addlinkedserver @server = 'loopback', @srvproduct = '', @provider = 'SQLNCLI', @datasrc = @@SERVERNAME; EXEC master..sp_serveroption @server = 'loopback', @optname = 'DATA ACCESS', @optvalue = 'TRUE'; 

现在您可以将其作为链接服务器进行查询,您可以将任何查询(包括存储过程调用)的结果用作常规SELECT 。 所以你可以这样做(注意数据库前缀重要的,否则你会得到错误11529和2812):

 SELECT * FROM OPENQUERY(loopback, 'EXEC tempdb.dbo.my_procedure;'); 

如果我们可以执行一个SELECT * ,我们也可以执行一个SELECT * INTO

 SELECT * INTO #tmp FROM OPENQUERY(loopback, 'EXEC tempdb.dbo.my_procedure;'); 

一旦#tmp表存在,我们可以通过说(假定SQL Server 2005或更高版本)来确定元数据:

 SELECT c.name, [type] = t.name, c.max_length, c.[precision], c.scale FROM sys.columns AS c INNER JOIN sys.types AS t ON c.system_type_id = t.system_type_id AND c.user_type_id = t.user_type_id WHERE c.[object_id] = OBJECT_ID('tempdb..#tmp'); 

(如果你使用的是SQL Server 2000,你可以用syscolumns做类似的事情,但是我没有一个2000的实例来validation等价的查询。)

结果:

 name type max_length precision scale --------- ------- ---------- --------- ----- foo int 4 10 0 bar varchar 5 0 0 

在德纳里,这将会轻松得多。 同样,第一个结果集仍然有限制,但是您不必设置链接服务器并跳过所有这些环节。 你可以说:

 DECLARE @sql NVARCHAR(MAX) = N'EXEC tempdb.dbo.my_procedure;'; SELECT name, system_type_name FROM sys.dm_exec_describe_first_result_set(@sql, NULL, 1); 

结果:

 name system_type_name --------- ---------------- foo int bar varchar(5) 

在Denali之前,我build议只需卷起袖子并自行计算数据types就会更容易。 不仅仅是因为经历上述步骤非常繁琐,而且因为你比发动机更有可能做出正确的(或者至less是更准确的)猜测,因为数据types猜测发动机的制造将基于运行时输出,而没有任何可能的价值领域的外部知识。 这个因素在Denali也会保持不变,所以不要让人觉得新的元数据发现function是最终的一切,只是让上面的内容变得单调乏味。

哦,还有其他一些潜在的问题,请参阅Erland Sommarskog的文章:

http://www.sommarskog.se/share_data.html#OPENQUERY

一个不太复杂的方法(在某些情况下可能已经足够了):在最终的SELECT之后和FROM子句之前添加INSERT INTO tmpTable将SP结果保存在tmpTable中,以编辑原始SP。

运行修改的SP,最好使用有意义的参数来获取实际的数据。 恢复程序的原始代码。

现在您可以从SQL Server Management Studio获取tmpTable的脚本或查询sys.columns来获取字段说明。

这是我写的一些代码。 这个想法(正如其他人所说的)是获得SP代码,修改并执行它。 但是,我的代码不会更改原始的SP。

第一步,获取SP的定义,剥离“创build”部分,并在参数声明后删除“AS”(如果存在)。

 Declare @SPName varchar(250) Set nocount on Declare @SQL Varchar(max), @SQLReverse Varchar(MAX), @StartPos int, @LastParameterName varchar(250) = '', @TableName varchar(36) = 'A' + REPLACE(CONVERT(varchar(36), NewID()), '-', '') Select * INTO #Temp from INFORMATION_SCHEMA.PARAMETERS where SPECIFIC_NAME = 'ADMIN_Sync_CompareDataForSync' if @@ROWCOUNT > 0 BEGIN Select @SQL = REPLACE(ROUTINE_DEFINITION, 'CREATE PROCEDURE [' + ROUTINE_SCHEMA + '].[' + ROUTINE_NAME + ']', 'Declare') from INFORMATION_SCHEMA.ROUTINES where ROUTINE_NAME = @SPName Select @LastParameterName = PARAMETER_NAME + ' ' + DATA_TYPE + CASE WHEN CHARACTER_MAXIMUM_LENGTH is not null THEN '(' + CASE WHEN CHARACTER_MAXIMUM_LENGTH = -1 THEN 'MAX' ELSE CONVERT(varchar,CHARACTER_MAXIMUM_LENGTH) END + ')' ELSE '' END from #Temp WHERE ORDINAL_POSITION = (Select MAX(ORDINAL_POSITION) From #Temp) Select @StartPos = CHARINDEX(@LastParameterName, REPLACE(@SQL, ' ', ' '), 1) + LEN(@LastParameterName) END else Select @SQL = REPLACE(ROUTINE_DEFINITION, 'CREATE PROCEDURE [' + ROUTINE_SCHEMA + '].[' + ROUTINE_NAME + ']', '') from INFORMATION_SCHEMA.ROUTINES where ROUTINE_NAME = @SPName DROP TABLE #Temp Select @StartPos = CHARINDEX('AS', UPPER(@SQL), @StartPos) Select @SQL = STUFF(@SQL, @StartPos, 2, '') 

(注意基于唯一标识符创build一个新表名)现在,在代码中find最后一个“From”字,假设这是执行返回结果集的select的代码。

 Select @SQLReverse = REVERSE(@SQL) Select @StartPos = CHARINDEX('MORF', UPPER(@SQLReverse), 1) 

更改代码以将结果集select到表中(基于uniqueidentifier的表)

 Select @StartPos = LEN(@SQL) - @StartPos - 2 Select @SQL = STUFF(@SQL, @StartPos, 5, ' INTO ' + @TableName + ' FROM ') EXEC (@SQL) 

结果集现在放在一个表中,如果表是空的,这并不重要!

让我们得到表的结构

 Select * from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @TableName 

你现在可以用这个来做你的魔法了

不要忘记放弃这个独特的表

 Select @SQL = 'drop table ' + @TableName Exec (@SQL) 

希望这可以帮助!

它看起来像在SQL 2012中有一个新的SP来帮助这个。

 exec sp_describe_first_result_set N'PROC_NAME' 

https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-describe-first-result-set-transact-sql

如果你在一个有限权限的环境中工作,像loopback链接服务器这样的东西看起来是黑魔法的,并且绝对是“不可能的!”,但是你对schema有一些权限,只有几个存储过程可以处理,这是非常简单的解。

您可以使用非常有用的SELECT INTO语法,它将创build一个包含查询结果集的新表。

假设您的过程包含以下Select查询:

 SELECT x, y, z FROM MyTable t INNER JOIN Table2 t2 ON t.id = t2.id... 

取而代之的是:

 SELECT x, y, z INTO MyOutputTable FROM MyTable t INNER JOIN Table2 t2 ON t.id = t2.id... 

当你执行它的时候,它将创build一个新的MyOutputTable表,查询返回结果。

你只需要右键点击它的名字来获得表格定义。

就这样 !

SELECT INTO只需要创build新表的能力,也可以使用临时表(SELECT … INTO #MyTempTable),但是可能难以检索定义。

但是,当然,如果你需要检索一个数千SP的输出定义,这不是最快的方法:)