[Me acabo de dar cuenta de que he respondido esta pregunta antes]
Hacer esto para un procedimiento almacenado es mucho más complicado que para una vista o una tabla. Uno de los problemas es que un procedimiento almacenado puede tener múltiples rutas de código diferentes según los parámetros de entrada e incluso cosas que no puede controlar como el estado del servidor, la hora del día, etc. Entonces, por ejemplo, lo que esperaría ver como salida para este procedimiento almacenado? ¿Qué pasa si hay varios conjuntos de resultados independientemente de los condicionales?
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
Si su procedimiento almacenado no tiene rutas de código y está seguro de que siempre verá el mismo conjunto de resultados (y puede determinar de antemano qué valores se deben proporcionar si un procedimiento almacenado tiene parámetros no opcionales), tomemos un ejemplo simple:
CREATE PROCEDURE dbo.bar
AS
BEGIN
SET NOCOUNT ON;
SELECT a = 'a', b = 1, c = GETDATE();
END
GO
SOLO FM
Una forma es hacer algo como esto:
SET FMTONLY ON;
GO
EXEC dbo.bar;
Esto le dará un conjunto de resultados vacío y su aplicación cliente puede echar un vistazo a las propiedades de ese conjunto de resultados para determinar los nombres de las columnas y los tipos de datos.
Ahora, hay muchos problemas con SET FMTONLY ON;
que no entraré aquí, pero al menos debe tenerse en cuenta que este comando está obsoleto, por una buena razón. También tenga cuidado de SET FMTONLY OFF;
cuando haya terminado, o se preguntará por qué crea un procedimiento almacenado con éxito pero luego no puede ejecutarlo. Y no, no te lo advierto porque me acaba de pasar. Honesto. :-)
CONSULTA ABIERTA
Al crear un servidor vinculado de bucle invertido, puede usar herramientas como OPENQUERY
para ejecutar un procedimiento almacenado pero devolver un conjunto de resultados componible (bueno, acéptelo como una definición extremadamente flexible) que puede inspeccionar. Primero cree un servidor de loopback (esto asume una instancia local llamada 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';
Ahora podemos tomar el procedimiento anterior e incluirlo en una consulta como esta:
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');
Esto ignora los tipos de alias (anteriormente conocidos como tipos de datos definidos por el usuario) y también puede mostrar dos filas para columnas definidas como, por ejemplo, sysname
. Pero de lo anterior produce:
name name
---- --------
b int
c datetime
a varchar
Obviamente hay más trabajo por hacer aquí - varchar
no muestra la longitud, y tendrá que obtener precisión/escala para otros tipos como datetime2
, time
y decimal
. Pero eso es un comienzo.
Servidor SQL 2012
Hay algunas funciones nuevas en SQL Server 2012 que facilitan mucho la detección de metadatos. Para el procedimiento anterior podemos hacer lo siguiente:
SELECT name, system_type_name
FROM sys.dm_exec_describe_first_result_set_for_object
(
OBJECT_ID('dbo.bar'),
NULL
);
Entre otras cosas, esto realmente proporciona precisión y escala y resuelve los tipos de alias para nosotros. Para el procedimiento anterior, esto produce:
name system_type_name
---- ----------------
a varchar(1)
b int
c datetime
No hay mucha diferencia visualmente, pero cuando comience a ingresar a todos los diferentes tipos de datos con varias precisiones y escalas, apreciará el trabajo adicional que esta función hace por usted.
La desventaja:en SQL Server 2012, al menos estas funciones solo funcionan para el primero conjunto de resultados (como implica el nombre de la función).