我正在使用SQL Server 2008中的存储过程,并且我已经了解到我必须INSERT INTO
已经预定义的临时表以便处理数据。这很好,除了如何定义我的临时表,如果我不是那个编写存储过程而不是列出其定义并阅读代码的那个?
例如,我的临时表对于'EXEC sp_stored_procedure'是什么样的?这是一个简单的存储过程,我可能猜测数据类型,但似乎必须有一种方法来只读取执行过程返回的列的类型和长度。
答案 0 :(得分:52)
因此,假设您在tempdb中有一个存储过程:
USE tempdb;
GO
CREATE PROCEDURE dbo.my_procedure
AS
BEGIN
SET NOCOUNT ON;
SELECT foo = 1, bar = 'tooth';
END
GO
有一种非常复杂的方法可以确定存储过程将输出的元数据。有几个注意事项,包括该过程只能输出单个结果集,如果无法精确确定数据类型,则会对数据类型进行最佳猜测。它需要使用OPENQUERY
和环回链接服务器,并将'DATA ACCESS'
属性设置为true。您可以检查sys.servers以查看您是否已有一台有效的服务器,但我们只需创建一个名为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实例来验证等效查询。)
结果:
name type max_length precision scale
--------- ------- ---------- --------- -----
foo int 4 10 0
bar varchar 5 0 0
在Denali,这将变得更加轻松。同样,第一个结果集仍然存在限制,但您不必设置链接服务器并跳过所有这些环节。你可以说:
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,我建议你可以更轻松地卷起袖子并自己找出数据类型。这不仅仅是因为完成上述步骤很繁琐,而且还因为您更有可能做出比引擎更正确(或至少更准确)的猜测,因为数据类型猜测引擎所做的将基于运行时输出,没有任何可能值的域的外部知识。这个因素在Denali中也是如此,所以不要觉得新的元数据发现功能是最终的结果,它们只会使上面的内容变得不那么乏味。
哦,对于OPENQUERY
的其他潜在问题,请参阅Erland Sommarskog的文章:
答案 1 :(得分:7)
一种不太复杂的方法(在某些情况下可能就足够了):编辑原始SP,在最终SELECT之后和FROM子句之前添加INSERT INTO tmpTable以将SP结果保存在tmpTable中。
运行修改后的SP,最好使用有意义的参数以获取实际数据。恢复该过程的原始代码。
现在您可以从SQL server management studio获取tmpTable脚本或查询sys.columns以获取字段描述。
答案 2 :(得分:6)
这是我写的一些代码。这个想法(正如其他人所说)是获取SP代码,修改它并执行它。但是,我的代码不会更改原始SP。
第一步,获取SP的定义,剥离“创建”部分并在声明参数后删除“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, '')
(注意根据唯一标识符创建新表名) 现在找到代码中的最后一个'From'字,假设这是执行返回结果集的select的代码。
Select @SQLReverse = REVERSE(@SQL)
Select @StartPos = CHARINDEX('MORF', UPPER(@SQLReverse), 1)
更改代码以将结果集选择到表中(基于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)
希望这有帮助!
答案 3 :(得分:5)
在SQL 2012中看起来有一个新的SP来帮助解决这个问题。
exec sp_describe_first_result_set N'PROC_NAME'
答案 4 :(得分:0)
为了获得可查询的结果集sys.dm_exec_describe_first_result_set
(SQL Server 2012),可以使用:
SELECT column_ordinal, name, system_type_name
FROM sys.dm_exec_describe_first_result_set(N'EXEC stored_procedure_name', NULL, 0);
尽管例如SP不能使用临时表,但这种灵魂没有什么限制。
答案 5 :(得分:-2)
如果你在一个权利有限的环境中工作,那里环回链接服务器之类的东西似乎是黑魔法并且肯定是没有办法!",但你对模式有一些权利,只有几个处理存储过程有一个非常简单的解决方案。
您可以使用非常有用的 SELECT INTO 语法,该语法将创建一个包含查询结果集的新表。
我们假设您的程序包含以下选择查询:
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...
当您执行它时,它将创建一个新表MyOutputTable,其中包含查询返回的结果。
您只需右键单击其名称即可获得表格定义。
这就是全部!
SELECT INTO只需要能够创建新表并且也可以使用临时表(SELECT ... INTO #MyTempTable),但是检索定义可能更难。
但是,当然如果您需要检索数千个SP的输出定义,那么它不是最快的方式:)