Seleccionar columnas del conjunto de resultados del procedimiento almacenado

448

Tengo un procedimiento almacenado que devuelve 80 columnas y 300 filas. Quiero escribir una selección que obtenga 2 de esas columnas. Algo como

SELECT col1, col2 FROM EXEC MyStoredProc 'param1', 'param2'

Cuando utilicé la sintaxis anterior obtengo el error:

"Nombre de columna inválido".

Sé que la solución más fácil sería cambiar el procedimiento almacenado, pero no lo escribí y no puedo cambiarlo.

¿Hay alguna forma de hacer lo que quiero?

  • Podría hacer una tabla temporal para poner los resultados, pero debido a que hay 80 columnas, necesitaría hacer una tabla temporal de 80 columnas solo para obtener 2 columnas. Quería evitar rastrear todas las columnas que se devuelven.

  • Intenté usar WITH SprocResults AS ....como lo sugirió Mark, pero obtuve 2 errores

    Sintaxis incorrecta cerca de la palabra clave 'EXEC'.
    Sintaxis incorrecta cerca ')'.

  • Intenté declarar una variable de tabla y recibí el siguiente error

    Error de inserción: el nombre de la columna o el número de valores proporcionados no coincide con la definición de la tabla

  • Si lo intento
    SELECT * FROM EXEC MyStoredProc 'param1', 'param2'
    me sale el error:

    Sintaxis incorrecta cerca de la palabra clave 'exec'.

Rossini
fuente
Por curiosidad, ¿funciona esta consulta: SELECT * FROM EXEC MyStoredProc 'param1', 'param2' Si es así, ¿qué nombres de columna muestra en el conjunto de resultados, y puede usar esos nombres de columna en su lista de selección?
Dave Costa
55
Nunca encontré una respuesta para esto.
Rossini el
32
¡Bueno, nunca respondiste una pregunta muy importante! ¿Sobre qué plataforma SQL estás preguntando? MySQL, Microsoft SQL Server, Oracle, etc. Me parece que es SQL Server, pero debe decirle a las personas o no pueden responder su pregunta de manera confiable.
JMTyler
66
Bueno, debe ser MS-SQL. EXECno es una palabra clave MySQL (el equivalente de MySQL es declaraciones preparadas ). Aunque me gustaría saber la respuesta para MySQL, las respuestas a continuación apuntan a T-SQL. Reetiquetado
bobobobo
1
Nunca encontré una respuesta para esto
Rossini

Respuestas:

186

¿Puedes dividir la consulta? Inserte los resultados de proceso almacenados en una variable de tabla o una tabla temporal. Luego, seleccione las 2 columnas de la variable de tabla.

Declare @tablevar table(col1 col1Type,..
insert into @tablevar(col1,..) exec MyStoredProc 'param1', 'param2'

SELECT col1, col2 FROM @tablevar
Gulzar Nazim
fuente
27
Tampoco funciona cuando no conoce la definición de la tabla
Ian Boyd
No sabía sobre ese tipo. ¿Se implementan igual que las tablas temporales? ¿O es estrictamente en la memoria?
d -_- b
Esto fue interesante: sqlnerd.blogspot.com/2005/09/…
d -_- b
Esto funciona bien si el número de columnas suministradas en la tabla temporal es el mismo que el de la salida del procedimiento almacenado. Chagbert.
Chagbert
83

Aquí hay un enlace a un documento bastante bueno que explica todas las diferentes formas de resolver su problema (aunque muchas de ellas no se pueden usar ya que no puede modificar el procedimiento almacenado existente).

Cómo compartir datos entre procedimientos almacenados

La respuesta de Gulzar funcionará (está documentada en el enlace de arriba) pero será una molestia escribirla (necesitará especificar los 80 nombres de columna en su declaración @tablevar (col1, ...). Y en el futuro Si se agrega una columna al esquema o se cambia la salida, deberá actualizarse en su código o se producirá un error.

Lance McNearney
fuente
1
Creo que la sugerencia de OPENQUERY en ese enlace está mucho más cerca de lo que está buscando el OP.
Corin
80
CREATE TABLE #Result
(
  ID int,  Name varchar(500), Revenue money
)
INSERT #Result EXEC RevenueByAdvertiser '1/1/10', '2/1/10'
SELECT * FROM #Result ORDER BY Name
DROP TABLE #Result

Fuente:
http://stevesmithblog.com/blog/select-from-a-stored-procedure/

Peter Nazarov
fuente
@LawfulHacker Holy fuma. ¿Qué haces en SQL Server 2000 en el año 2014?
John Zabroski
1
Grandes corporaciones con sistemas heredados: D
LawfulHacker
39

Esto funciona para mí: (es decir, solo necesito 2 columnas de las más de 30 devueltas por sp_help_job)

SELECT name, current_execution_status 
FROM OPENQUERY (MYSERVER, 
  'EXEC msdb.dbo.sp_help_job @job_name = ''My Job'', @job_aspect = ''JOB''');  

Antes de que esto funcionara, necesitaba ejecutar esto:

sp_serveroption 'MYSERVER', 'DATA ACCESS', TRUE;

.... para actualizar la sys.serverstabla. (es decir, el uso de una autorreferencia dentro de OPENQUERY parece estar deshabilitado de forma predeterminada).

Por mi simple requisito, no encontré ninguno de los problemas descritos en la sección OPENQUERY del excelente enlace de Lance.

Rossini, si necesita establecer dinámicamente esos parámetros de entrada, el uso de OPENQUERY se vuelve un poco más complicado:

DECLARE @innerSql varchar(1000);
DECLARE @outerSql varchar(1000);

-- Set up the original stored proc definition.
SET @innerSql = 
'EXEC msdb.dbo.sp_help_job @job_name = '''+@param1+''', @job_aspect = N'''+@param2+'''' ;

-- Handle quotes.
SET @innerSql = REPLACE(@innerSql, '''', '''''');

-- Set up the OPENQUERY definition.
SET @outerSql = 
'SELECT name, current_execution_status 
FROM OPENQUERY (MYSERVER, ''' + @innerSql + ''');';

-- Execute.
EXEC (@outerSql);

No estoy seguro de las diferencias (si las hay) entre usar sp_serveroptionpara actualizar la sys.serversautorreferencia existente directamente, versus usar sp_addlinkedserver(como se describe en el enlace de Lance) para crear un duplicado / alias.

Nota 1: Prefiero OPENQUERY sobre OPENROWSET, dado que OPENQUERY no requiere la definición de la cadena de conexión dentro del proceso.

Nota 2: Habiendo dicho todo esto: normalmente solo usaría INSERT ... EXEC :) Sí, son 10 minutos más de tipeo, pero si puedo evitarlo, prefiero no moverme con:
(a) comillas dentro de comillas dentro de citas, y
(b) tablas del sistema y / o configuraciones de servidor vinculado autorreferenciadas y escurridizas (es decir, para esto, necesito defender mi caso ante nuestros poderosos DBA :)

Sin embargo, en este caso, no pude usar una construcción INSERT ... EXEC, como sp_help_jobya estoy usando una. ("No se puede anidar una instrucción INSERT EXEC").

Merenzo
fuente
3
He tenido 13 comillas simples seguidas antes en dynamic-sql-that-generate-dynamic-sql-that-generate-dynamic-sql ...
ErikE
Necesito verificar si el trabajo está terminado. "No se puede anidar una instrucción INSERT EXEC". Te odio Microsoft.
alexkovelsky
11

Para lograr esto, primero debe crear un me #test_tablegusta a continuación:

create table #test_table(
    col1 int,
    col2 int,
   .
   .
   .
    col80 int
)

Ahora ejecute el procedimiento y ponga valor en #test_table:

insert into #test_table
EXEC MyStoredProc 'param1', 'param2'

Ahora obtiene el valor de #test_table:

select col1,col2....,col80 from #test_table
Navneet
fuente
2
¿Hay alguna ventaja en crear una tabla temporal en lugar de una variable de tabla?
Dave Kelly
1
¡La mejor solución que encontré en stackoverflow! :)
Umar
44
¿Qué sucede si solo necesito una columna de otro Procedimiento almacenado?
Keval Patel
11

Si puede modificar su procedimiento almacenado, puede poner fácilmente las definiciones de columnas necesarias como parámetro y utilizar una tabla temporal creada automáticamente:

CREATE PROCEDURE sp_GetDiffDataExample
      @columnsStatement NVARCHAR(MAX) -- required columns statement (e.g. "field1, field2")
AS
BEGIN
    DECLARE @query NVARCHAR(MAX)
    SET @query = N'SELECT ' + @columnsStatement + N' INTO ##TempTable FROM dbo.TestTable'
    EXEC sp_executeSql @query
    SELECT * FROM ##TempTable
    DROP TABLE ##TempTable
END

En este caso, no necesita crear una tabla temporal manualmente, se crea automáticamente. Espero que esto ayude.

dyatchenko
fuente
Tenga cuidado al usar ## tablas, ya que se comparten entre sesiones
eKelvin el
1
Puede encontrar una breve descripción de las diferencias entre las tablas # y ## en stackoverflow.com/a/34081438/352820
eKelvin el
¿Es propenso a la inyección SQL?
Bushrod
11

Puede ser útil saber por qué esto es tan difícil. Un procedimiento almacenado solo puede devolver texto (imprimir 'texto'), o puede devolver varias tablas, o puede no devolver ninguna tabla.

Entonces algo como SELECT * FROM (exec sp_tables) Table1 no funcionará

newbie007
fuente
12
SQL Server es libre de generar un error si eso sucede. por ejemplo, si escribo una subconsulta que devuelve más de un valor. Sí, puede suceder, pero en realidad no. E incluso si lo hiciera: no es difícil generar un error.
Ian Boyd
8

(Suponiendo SQL Server)

La única forma de trabajar con los resultados de un procedimiento almacenado en T-SQL es usar la INSERT INTO ... EXECsintaxis. Eso le da la opción de insertar en una tabla temporal o una variable de tabla y desde allí seleccionar los datos que necesita.

Brannon
fuente
1
Eso requiere conocer la definición de la tabla. Inútil.
Triynko
8

Un truco rápido sería agregar un nuevo parámetro '@Column_Name'y hacer que la función de llamada defina el nombre de la columna que se va a recuperar. En la parte de devolución de su sproc, tendría sentencias if / else y devolvería solo la columna especificada, o si está vacío, devuelva todo.

CREATE PROCEDURE [dbo].[MySproc]
        @Column_Name AS VARCHAR(50)
AS
BEGIN
    IF (@Column_Name = 'ColumnName1')
        BEGIN
            SELECT @ColumnItem1 as 'ColumnName1'
        END
    ELSE
        BEGIN
            SELECT @ColumnItem1 as 'ColumnName1', @ColumnItem2 as 'ColumnName2', @ColumnItem3 as 'ColumnName3'
        END
END
Samir Basic
fuente
7

Si está haciendo esto para la validación manual de los datos, puede hacerlo con LINQPad.

Cree una conexión a la base de datos en LinqPad y luego cree declaraciones C # similares a las siguientes:

DataTable table = MyStoredProc (param1, param2).Tables[0];
(from row in table.AsEnumerable()
 select new
 {
  Col1 = row.Field<string>("col1"),
  Col2 = row.Field<string>("col2"),
 }).Dump();

Referencia http://www.global-webnet.net/blogengine/post/2008/09/10/LINQPAD-Using-Stored-Procedures-Accessing-a-DataSet.aspx

ShawnPluma
fuente
7

Para SQL Server, encuentro que esto funciona bien:

Cree una tabla temporal (o una tabla permanente, en realidad no importa) y realice una inserción en la declaración con el procedimiento almacenado. El conjunto de resultados del SP debe coincidir con las columnas de su tabla, de lo contrario obtendrá un error.

Aquí hay un ejemplo:

DECLARE @temp TABLE (firstname NVARCHAR(30), lastname nvarchar(50));

INSERT INTO @temp EXEC dbo.GetPersonName @param1,@param2;
-- assumption is that dbo.GetPersonName returns a table with firstname / lastname columns

SELECT * FROM @temp;

¡Eso es!

LTMOD
fuente
Para esto, necesita crear una copia de la definición de la tabla. Hay alguna manera de evitarlo?
Hardik
7

Como se mencionó en la pregunta, es difícil definir la tabla temporal de 80 columnas antes de ejecutar el procedimiento almacenado.

Por lo tanto, al revés, debe completar la tabla en función del conjunto de resultados del procedimiento almacenado.

SELECT * INTO #temp FROM OPENROWSET('SQLNCLI', 'Server=localhost;Trusted_Connection=yes;'
                                   ,'EXEC MyStoredProc')

Si recibe algún error, debe habilitar consultas distribuidas ad hoc ejecutando la siguiente consulta.

sp_configure 'Show Advanced Options', 1
GO
RECONFIGURE
GO
sp_configure 'Ad Hoc Distributed Queries', 1
GO
RECONFIGURE
GO

Para ejecutar sp_configurecon ambos parámetros para cambiar una opción de configuración o para ejecutar la RECONFIGUREinstrucción, se le debe otorgar el ALTER SETTINGSpermiso de nivel de servidor

Ahora puede seleccionar sus columnas específicas de la tabla generada

SELECT col1, col2
FROM #temp
sqluser
fuente
4

prueba esto

use mydatabase
create procedure sp_onetwothree as
select 1 as '1', 2 as '2', 3 as '3'
go
SELECT a.[1], a.[2]
FROM OPENROWSET('SQLOLEDB','myserver';'sa';'mysapass',
    'exec mydatabase.dbo.sp_onetwothree') AS a
GO
SelvirK
fuente
1
Lol, no lo hizo. En cambio, lo codificó en la invocación del procedimiento almacenado, donde puede obtenerse mucho más fácilmente sin acceso a la base de datos mediante el rastreo de la red.
Martin Milan
También es bastante fácil sacarlo de Github.
nomen
3

Sé que ejecutar desde sp e insertar en la tabla temporal o variable de tabla sería una opción, pero no creo que sea su requisito. Según su requisito, esta declaración de consulta a continuación debería funcionar:

Declare @sql nvarchar(max)
Set @sql='SELECT   col1, col2 FROM OPENROWSET(''SQLNCLI'', ''Server=(local);uid=test;pwd=test'',
     ''EXEC MyStoredProc ''''param1'''', ''''param2'''''')'
 Exec(@sql)

si tiene conexión de confianza, use esta declaración de consulta a continuación:

Declare @sql nvarchar(max)
Set @sql='SELECT   col1, col2 FROM OPENROWSET(''SQLNCLI'', ''Server=(local);Trusted_Connection=yes;'',
     ''EXEC MyStoredProc ''''param1'''', ''''param2'''''')'
 Exec(@sql)

si obtiene un error al ejecutar la declaración anterior, simplemente ejecute esta declaración a continuación:

sp_configure 'Show Advanced Options', 1
GO
RECONFIGURE
GO
sp_configure 'Ad Hoc Distributed Queries', 1
GO
RECONFIGURE
GO

Espero que esto ayude a alguien que haya enfrentado este tipo de problema similar. Si alguien intentara con una tabla temporal o variable de tabla que debería ser así a continuación, pero en este escenario debe saber cuántas columnas está devolviendo su sp, entonces debe crear esa cantidad de columnas en la tabla temporal o variable de tabla:

--for table variable 
Declare @t table(col1 col1Type, col2 col2Type)
insert into @t exec MyStoredProc 'param1', 'param2'
SELECT col1, col2 FROM @t

--for temp table
create table #t(col1 col1Type, col2 col2Type)
insert into #t exec MyStoredProc 'param1', 'param2'
SELECT col1, col2 FROM #t
Humayoun_Kabir
fuente
1

Para cualquiera que tenga SQL 2012 o posterior, pude lograr esto con procedimientos almacenados que no son dinámicos y tienen la misma salida de columnas cada vez.

La idea general es que construyo la consulta dinámica para crear, insertar, seleccionar y soltar la tabla temporal, y ejecutar esto después de que todo se haya generado. Genero dinámicamente la tabla temporal recuperando primero los nombres y tipos de columna del procedimiento almacenado .

Nota: existen soluciones mucho mejores y más universales que funcionarán con menos líneas de código si desea / puede actualizar el SP o cambiar la configuración y el uso OPENROWSET. Use el siguiente si no tiene otra manera.

DECLARE @spName VARCHAR(MAX) = 'MyStoredProc'
DECLARE @tempTableName VARCHAR(MAX) = '#tempTable'

-- might need to update this if your param value is a string and you need to escape quotes
DECLARE @insertCommand VARCHAR(MAX) = 'INSERT INTO ' + @tempTableName + ' EXEC MyStoredProc @param=value'

DECLARE @createTableCommand VARCHAR(MAX)

-- update this to select the columns you want
DECLARE @selectCommand VARCHAR(MAX) = 'SELECT col1, col2 FROM ' + @tempTableName

DECLARE @dropCommand VARCHAR(MAX) = 'DROP TABLE ' + @tempTableName

-- Generate command to create temp table
SELECT @createTableCommand = 'CREATE TABLE ' + @tempTableName + ' (' +
    STUFF
    (
        (
            SELECT ', ' + CONCAT('[', name, ']', ' ', system_type_name)
            FROM sys.dm_exec_describe_first_result_set_for_object
            (
              OBJECT_ID(@spName), 
              NULL
            )
            FOR XML PATH('')
        )
        ,1
        ,1
        ,''
    ) + ')'

EXEC( @createTableCommand + ' '+ @insertCommand + ' ' + @selectCommand + ' ' + @dropCommand)
Emil
fuente
0

La forma más fácil de hacerlo si solo necesita hacer esto una vez:

Exportar a Excel en el asistente Importar y Exportar y luego importar este Excel a una tabla.

Martijn Tromp
fuente
44
El objetivo de crear un proceso almacenado es la reutilización. Su respuesta lo contradice totalmente.
deutschZuid
66
Para contrarrestar deutschZuid, en la publicación original, no menciona si quiere o no reutilizar esto o si solo está tratando de ver los resultados de un proceso almacenado. Martin tiene razón, esta es probablemente la forma más fácil si solo necesita hacerlo una vez.
Obispo
0

Cree una vista dinámica y obtenga resultados de ella .......

CREATE PROCEDURE dbo.usp_userwise_columns_value
(
    @userid BIGINT
)
AS 
BEGIN
        DECLARE @maincmd NVARCHAR(max);
        DECLARE @columnlist NVARCHAR(max);
        DECLARE @columnname VARCHAR(150);
        DECLARE @nickname VARCHAR(50);

        SET @maincmd = '';
        SET @columnname = '';
        SET @columnlist = '';
        SET @nickname = '';

        DECLARE CUR_COLUMNLIST CURSOR FAST_FORWARD
        FOR
            SELECT columnname , nickname
            FROM dbo.v_userwise_columns 
            WHERE userid = @userid

        OPEN CUR_COLUMNLIST
        IF @@ERROR <> 0
            BEGIN
                ROLLBACK
                RETURN
            END   

        FETCH NEXT FROM CUR_COLUMNLIST
        INTO @columnname, @nickname

        WHILE @@FETCH_STATUS = 0
            BEGIN
                SET @columnlist = @columnlist + @columnname + ','

                FETCH NEXT FROM CUR_COLUMNLIST
                INTO @columnname, @nickname
            END
        CLOSE CUR_COLUMNLIST
        DEALLOCATE CUR_COLUMNLIST  

        IF NOT EXISTS (SELECT * FROM sys.views WHERE name = 'v_userwise_columns_value')
            BEGIN
                SET @maincmd = 'CREATE VIEW dbo.v_userwise_columns_value AS SELECT sjoid, CONVERT(BIGINT, ' + CONVERT(VARCHAR(10), @userid) + ') as userid , ' 
                            + CHAR(39) + @nickname + CHAR(39) + ' as nickname, ' 
                            + @columnlist + ' compcode FROM dbo.SJOTran '
            END
        ELSE
            BEGIN
                SET @maincmd = 'ALTER VIEW dbo.v_userwise_columns_value AS SELECT sjoid, CONVERT(BIGINT, ' + CONVERT(VARCHAR(10), @userid) + ') as userid , ' 
                            + CHAR(39) + @nickname + CHAR(39) + ' as nickname, ' 
                            + @columnlist + ' compcode FROM dbo.SJOTran '
            END

        --PRINT @maincmd
        EXECUTE sp_executesql @maincmd
END

-----------------------------------------------
SELECT * FROM dbo.v_userwise_columns_value
Nilesh Umaretiya
fuente
-1

Cortaría y pegaría el SP original y eliminaría todas las columnas excepto las 2 que desee. O. Traería el resultado establecido, lo mapearía a un objeto comercial apropiado, luego LINQ fuera de las dos columnas.

Andrés
fuente
1
La gente no hace esto. Esto violará el Principio DRY. Cuando las cosas cambien, no si, ahora tendrá que rastrear e ingresar su cambio en todas las ubicaciones.
jriver27