检索存储过程的列名和类型?

46

可能重复:
检索存储过程结果集的列定义

我使用以下SQL语句获取表或视图的列名和类型:

DECLARE @viewname varchar (250);

select a.name as colname,b.name as typename 
from syscolumns a, systypes b -- GAH!
where a.id = object_id(@viewname) 
and a.xtype=b.xtype 
and b.name <> 'sysname'

我该如何对存储过程的输出列执行类似的操作?

2个回答

106

[我刚刚意识到我之前回答过这个问题]

对于存储过程来说,做这件事情比视图或表要复杂得多。其中一个问题是,存储过程可以有多个不同的代码路径,这取决于输入参数,甚至包括你无法控制的东西,比如服务器状态、时间等等。例如,你期望看到这个存储过程的输出是什么?如果有多个结果集,无论条件如何呢?

CREATE PROCEDURE dbo.foo
  @bar INT
AS
BEGIN
  SET NOCOUNT ON;

  IF @bar = 1
    SELECT a, b, c FROM dbo.blat;
  ELSE
    SELECT d, e, f, g, h FROM dbo.splunge;
END
GO

如果您的存储过程没有代码路径,并且您确信始终会看到相同的结果集(并且可以预先确定如果存储过程具有非可选参数应提供什么值),让我们以一个简单的例子来说明:

CREATE PROCEDURE dbo.bar
AS
BEGIN
  SET NOCOUNT ON;

  SELECT a = 'a', b = 1, c = GETDATE();
END
GO

SQL Server 2012+

在SQL Server 2012中引入了一些新的函数,使元数据发现更加容易。对于上述过程,我们可以执行以下操作:

SELECT [column] = name, system_type_name
FROM sys.dm_exec_describe_first_result_set_for_object
(
  OBJECT_ID('dbo.bar'), 
  NULL
);

除其他外,这实际上为我们提供了精度和规模,并解决了别名类型问题。对于以上过程,这产生了以下结果:

column   system_type_name
------   ----------------
a        varchar(1)
b        int
c        datetime

在视觉上没有太大区别,但当您开始涉及各种不同的数据类型、不同的精度和比例时,您会感谢这个函数为您做的额外工作。

缺点:到目前为止(通过 SQL Server 2022),这仅适用于第一个结果集(正如函数名称所示)。

FMTONLY

在旧版本中,一种方法是像这样做:

SET FMTONLY ON;
GO
EXEC dbo.bar;

这将给你一个空的结果集,你的客户端应用程序可以查看该结果集的属性以确定列名和数据类型。

现在,关于 SET FMTONLY ON; 存在很多问题,我不会在这里详细解释,但至少需要注意的是,这个命令已经被弃用了 - 有充分的理由。当你完成后,一定要小心地执行 SET FMTONLY OFF;,否则你会想知道为什么成功创建了存储过程,但无法执行它。不,我提醒你并不是因为我刚遇到了这个问题。诚实地说。:-)

OPENQUERY

通过创建一个环回链接服务器,然后使用像 OPENQUERY 这样的工具来执行存储过程,但返回可组合的结果集(请将其视为非常宽泛的定义),你可以检查结果集。首先创建一个环回服务器(假设本地实例名为 FOO):

USE master;
GO
EXEC sp_addlinkedserver @server = N'.\FOO', @srvproduct=N'SQL Server'
GO
EXEC sp_serveroption @server=N'.\FOO', @optname=N'data access', 
  @optvalue=N'true';
现在,我们可以将上述过程输入到以下查询中:
SELECT * INTO #t 
FROM OPENQUERY([.\FOO], 'EXEC dbname.dbo.bar;')
WHERE 1 = 0;

SELECT c.name, t.name
FROM tempdb.sys.columns AS c
INNER JOIN sys.types AS t
ON c.system_type_id = t.system_type_id
WHERE c.[object_id] = OBJECT_ID('tempdb..#t');
这会忽略别名类型(以前称为用户定义数据类型),并且对于定义为 sysname 的列可能会显示两行。但从上面的内容中,它产生了以下结果:
name   name
----   --------
b      int
c      datetime
a      varchar

很明显这里还有更多的工作要做 - varchar 没有显示长度,你还需要获取其他类型的精度/比例,例如 datetime2timedecimal。但这是一个开始。


2
谢谢,这是一篇写得很好的答案。我试图使用您的最后一个解决方案(“SQL Server 2012”),但有几个因素阻止它正常工作。如果您显示实际执行计划,它将无法工作。如果您将其包装在自己的函数中,它似乎也无法工作。 SQL Prompt说签名是RETURN SELECT * FROM OPENROWSET(TABLE DMF_SP_DESCRIBE_FIRST_RESULT_SET_OBJECT, @object_id, @browse_information_mode),但似乎不会执行。我唯一能让它工作的方法是在没有实际执行计划的查询窗口中直接运行它。 - Adam Plocher
3
我最终将其放入Sproc中,看起来它可以工作,但是阻止它工作的另一个因素是如果你的结果来自于#TEMP表。 - Adam Plocher
4
你的SQL Server 2012答案简洁、易懂,至少适用于简单的存储过程。 - Mark Meuer
1
2012年的解决方案如果存储过程使用临时表会无法工作(返回NULL),但对于CTE / 表变量可以正常工作。 - Sajjan Sarkar
1
@SajjanSarkar 是的,没错。如果你查看函数的输出,你会发现其中一列实际上告诉你 #temp 表不受支持。 - Aaron Bertrand
显示剩余4条评论

2
你是否想要返回所有存储过程及其参数?下面这段代码可以实现此功能。
select * from information_schema.parameters
如果您需要获取存储过程返回的列,请查看这里:

获取存储过程返回的列名/类型


谢谢您。这是一个很棒的查询。我从未见过这样的查询,它提供了如此丰富的信息!@sgeddes - Mike
2
除此之外,它只列出参数而不是列。从information_schema.columns选择*(列出了令人惊叹的列信息!)@sgeddes - Mike

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