web-dev-qa-db-fra.com

Sélectionner les colonnes du jeu de résultats de la procédure stockée

J'ai une procédure stockée qui renvoie 80 colonnes et 300 lignes. Je veux écrire une sélection qui obtient 2 de ces colonnes. Quelque chose comme 

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

Lorsque j'utilise la syntaxe ci-dessus, le message d'erreur suivant s'affiche: 

"Nom de colonne invalide".

Je sais que la solution la plus simple serait de changer la procédure stockée, mais je ne l'ai pas écrite et je ne peux pas la changer. 

Est-il possible de faire ce que je veux?

  • Je pourrais créer une table temporaire pour afficher les résultats, mais comme il y a 80 colonnes, il me faudrait donc créer une table temporaire de 80 colonnes uniquement pour obtenir 2 colonnes. Je voulais éviter de retrouver toutes les colonnes retournées.

  • J'ai essayé d'utiliser WITH SprocResults AS .... comme suggéré par Mark, mais j'ai 2 erreurs 

    Syntaxe incorrecte près du mot clé 'EXEC'.
    Syntaxe incorrecte près de ')'.

  • J'ai essayé de déclarer une variable de table et j'ai eu l'erreur suivante 

    Erreur d'insertion: le nom de colonne ou le nombre de valeurs fournies ne correspond pas à la définition de la table

  • Si j'essaye 
    SELECT * FROM EXEC MyStoredProc 'param1', 'param2'
    .__ Je reçois l'erreur:

    Syntaxe incorrecte près du mot clé 'exec'.

404
Rossini

Pouvez-vous diviser la requête? Insérez les résultats de procédure stockés dans une variable de table ou une table temporaire. Ensuite, sélectionnez les 2 colonnes de la variable de table.

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

SELECT col1, col2 FROM @tablevar
168
Gulzar Nazim

Voici un lien vers un très bon document expliquant les différentes façons de résoudre votre problème (bien qu'un grand nombre d'entre elles ne puissent pas être utilisées car vous ne pouvez pas modifier la procédure stockée existante).

Comment partager des données entre procédures stockées

La réponse de Gulzar fonctionnera (il est documenté dans le lien ci-dessus), mais l'écriture sera fastidieuse (vous devrez spécifier les 80 noms de colonne dans votre instruction @tablevar (col1, ...). Et à l'avenir. si une colonne est ajoutée au schéma ou si la sortie est modifiée, il faudra la mettre à jour dans votre code ou l'erreur se produira.

81
Lance McNearney
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

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

75
Peter Nazarov

Cela fonctionne pour moi: (c’est-à-dire que je n'ai besoin que de 2 colonnes sur plus de 30 renvoyées par sp_help_job

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

Avant que cela fonctionne, j'avais besoin d'exécuter ceci:

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

.... pour mettre à jour la table sys.servers. (c’est-à-dire que l’utilisation d’une référence à soi dans OPENQUERY semble être désactivée par défaut.)

Pour répondre à mes besoins simples, je n’ai rencontré aucun des problèmes décrits dans la section OPENQUERY de l’excellent lien de Lance. 

Rossini, si vous avez besoin de définir dynamiquement ces paramètres d'entrée, l'utilisation de OPENQUERY devient un peu plus compliquée:

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);

Je ne suis pas sûr des différences (le cas échéant) entre l'utilisation de sp_serveroption pour mettre à jour directement l'auto-référence sys.servers existante, par opposition à l'utilisation de sp_addlinkedserver (comme décrit dans le lien de Lance) pour créer un doublon/alias.

Note 1: Je préfère OPENQUERY à OPENROWSET, étant donné qu’OPENQUERY ne nécessite pas la définition de chaîne de connexion dans le proc. 

Note 2: Après avoir dit tout cela: normalement, j'utiliserais simplement INSERT ... EXEC :) Oui, il faut taper 10 minutes de plus, mais si je peux l'aider, je préfère ne pas bouger avec:
(a) citations entre guillemets entre guillemets, et
(b) des tables sys et/ou des configurations sournoises de serveurs liés se référant à eux-mêmes (c’est-à-dire, je dois plaider ma cause devant nos tout-à-puissants DBA :) 

Cependant, dans ce cas, je ne pouvais pas utiliser une construction INSERT ... EXEC, car sp_help_job en utilisait déjà une. ("Une instruction INSERT EXEC ne peut pas être imbriquée.")

37
Merenzo

Il serait peut-être utile de savoir pourquoi c'est si difficile. Une procédure stockée peut uniquement renvoyer du texte (print 'text'), plusieurs tableaux ou ne rien renvoyer.

Donc, quelque chose comme SELECT * FROM (exec sp_tables) Table1 ne fonctionnera pas

10
newbie007

Si vous pouvez modifier votre procédure stockée, vous pouvez facilement mettre les définitions de colonnes requises en tant que paramètre et utiliser une table temporaire créée automatiquement:

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

Dans ce cas, vous n'avez pas besoin de créer manuellement une table temporaire: elle est créée automatiquement. J'espère que cela t'aides.

9
dyatchenko

Pour ce faire, créez d'abord un #test_table comme ci-dessous:

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

Maintenant, exécutez la procédure et mettez la valeur dans #test_table:

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

Maintenant, vous récupérez la valeur de #test_table:

select col1,col2....,col80 from #test_table
9
Navneet

(En supposant que SQL Server)

La seule façon de travailler avec les résultats d'une procédure stockée dans T-SQL consiste à utiliser la syntaxe INSERT INTO ... EXEC. Cela vous donne la possibilité d’insérer dans une table temporaire ou une variable de table et de sélectionner les données dont vous avez besoin.

8
Brannon

Si vous effectuez cette opération pour la validation manuelle des données, vous pouvez le faire avec LINQPad.

Créez une connexion à la base de données dans LinqPad, puis créez des instructions C # similaires au suivant:

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();

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

7
ShawnFeatherly

Un hack rapide consisterait à ajouter un nouveau paramètre '@Column_Name' et à laisser la fonction appelante définir le nom de la colonne à récupérer. Dans la partie retour de votre sproc, vous auriez des instructions if/else et ne renverriez que la colonne spécifiée, ou si vide, vous renverriez tout.

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
7
Samir Basic

Pour SQL Server, je trouve que cela fonctionne bien:

Créez une table temporaire (ou une table permanente, cela n'a pas vraiment d'importance) et faites une instruction insert dans la procédure stockée. L'ensemble de résultats de SP doit correspondre aux colonnes de votre table, sinon vous obtiendrez une erreur.

Voici un exemple:

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;

C'est tout!

6
LTMOD

essaye ça

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
4
SelvirK

Comme cela a été mentionné dans la question, il est difficile de définir la table temporaire de 80 colonnes avant d'exécuter la procédure stockée.

Par conséquent, l'inverse consiste à renseigner la table en fonction du jeu de résultats de la procédure stockée.

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

Si vous obtenez une erreur, vous devez activer les requêtes distribuées ad hoc en exécutant la requête suivante.

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

Pour exécuter sp_configure avec les deux paramètres afin de modifier une option de configuration ou d'exécuter l'instruction RECONFIGURE, vous devez disposer de l'autorisation ALTER SETTINGS au niveau du serveur.

Maintenant, vous pouvez sélectionner vos colonnes spécifiques à partir de la table générée

SELECT col1, col2
FROM #temp
4
sqluser

Je sais qu’exécuter à partir de sp et insérer dans une table ou une variable de table serait une option, mais je ne pense pas que ce soit votre exigence. Selon votre exigence, cette requête ci-dessous devrait fonctionner:

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 vous avez une connexion sécurisée, utilisez la requête ci-dessous:

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

si vous obtenez une erreur pour exécuter l'instruction ci-dessus, exécutez simplement cette instruction ci-dessous:

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

J'espère que cela aidera quelqu'un qui aura été confronté à ce genre de problème similaire. Si quelqu'un souhaite utiliser une table temporaire ou une variable de table qui devrait ressembler à ceci ci-dessous mais dans ce scénario, vous devez savoir combien de colonnes votre sp renvoie, vous devez créer autant de colonnes dans une table temporaire ou une variable de table:

--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
1
Humayoun_Kabir

Pour ceux qui possèdent SQL 2012 ou une version ultérieure, j'ai pu le faire avec des procédures stockées qui ne sont pas dynamiques et ont les mêmes colonnes sorties à chaque fois.

L'idée générale est de construire la requête dynamique pour créer, insérer, sélectionner et supprimer la table temporaire, puis l'exécuter une fois qu'elle a été générée. Je génère dynamiquement la table temporaire en récupérant d'abord en récupérant les noms et les types de colonnes à partir de la procédure stockée .

Remarque: il existe de bien meilleures solutions, plus universelles, qui fonctionnent avec moins de lignes de code si vous souhaitez/pouvez mettre à jour le SP ou modifier la configuration et utiliser OPENROWSET. Utilisez ce qui suit si vous n'avez pas d'autre moyen.

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)
0
Emil

Meilleure façon de faire si vous n’avez besoin que de cela une fois:

Exporter vers Excel dans l'assistant d'importation et d'exportation, puis importer cet Excel dans un tableau.

0
Martijn Tromp