Sélectionnez les colonnes de l'ensemble de résultats de la procédure stockée


448

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'ai utilisé la syntaxe ci-dessus, j'obtiens l'erreur:

"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.

Existe-t-il un moyen de faire ce que je veux?

  • Je pourrais faire une table temporaire pour y mettre les résultats, mais parce qu'il y a 80 colonnes, je devrais donc faire une table temporaire de 80 colonnes juste 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 eu 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 la 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'
    j'obtiens l'erreur:

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


Par curiosité, cette requête fonctionne-t-elle: SELECT * FROM EXEC MyStoredProc 'param1', 'param2' Si oui, quels noms de colonnes affiche-t-elle dans le jeu de résultats, et pouvez-vous utiliser ces noms de colonnes dans votre liste de sélection?
Dave Costa,

5
Je n'ai jamais trouvé de réponse à cela.
Rossini

32
Eh bien, vous n'avez jamais répondu à une question très importante! De quelle plateforme SQL parlez-vous? MySQL, Microsoft SQL Server, Oracle, etc. Il me semble que c'est SQL Server, mais vous devez le dire aux gens ou ils ne peuvent pas répondre de manière fiable à votre question.
JMTyler

6
Eh bien, ce doit être MS-SQL. EXECn'est pas un mot clé MySQL (l'équivalent MySQL est des instructions préparées ). Bien que j'aimerais connaître la réponse pour MySQL, les réponses ci-dessous ciblent T-SQL. Redéfinir.
bobobobo

1
Je n'ai jamais trouvé de réponse à cela
Rossini

Réponses:


186

Pouvez-vous diviser la requête? Insérez les résultats de proc 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

27
Cela ne fonctionne pas non plus lorsque vous ne connaissez pas la définition de la table
Ian Boyd

ne connaissait pas ce type. Sont-ils implémentés de la même manière que les tables temporaires? Ou est-ce strictement en mémoire?
d -_- b

C'était intéressant: sqlnerd.blogspot.com/2005/09/…
d -_- b

Cela fonctionne bien si le nombre de colonnes fournies dans la table temporaire est le même que celles de la sortie de la procédure stockée. chagbert.
Chagbert

83

Voici un lien vers un très bon document expliquant toutes les différentes façons de résoudre votre problème (bien que beaucoup 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 des procédures stockées

La réponse de Gulzar fonctionnera (elle est documentée dans le lien ci-dessus) mais ça va être compliqué à écrire (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, elle devra être mise à jour dans votre code ou elle entraînera une erreur.


1
Je pense que la suggestion OPENQUERY dans ce lien est beaucoup plus proche de ce que le PO recherche.
Corin


39

Cela fonctionne pour moi: (c'est-à-dire que je n'ai besoin que de 2 colonnes des 30+ retourné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 ne fonctionne, je devais exécuter ceci:

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

.... pour mettre à jour le sys.serverstableau. (Par exemple, l'utilisation d'une auto-référence dans OPENQUERY semble être désactivée par défaut.)

Pour ma simple exigence, 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, alors 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 sp_serveroptionpour mettre à jour sys.serversdirectement l'auto-référence existante et l'utilisation sp_addlinkedserver(comme décrit dans le lien de Lance) pour créer un doublon / alias.

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

Remarque 2: Cela dit: normalement, j'utiliserais simplement INSERT ... EXEC :) Oui, c'est 10 minutes de frappe supplémentaire, mais si je peux l'aider, je préfère ne pas bouger avec:
(a) les guillemets entre guillemets dans citations, et
(b) tables sys, et / ou configurations sournoises auto-référencées de serveur lié (c'est-à-dire pour celles-ci, je dois plaider ma cause auprès de nos DBA tout-puissants :)

Cependant, dans ce cas, je ne pouvais pas utiliser une construction INSERT ... EXEC, comme sp_help_jobc'est déjà le cas. ("Une instruction INSERT EXEC ne peut pas être imbriquée.")


3
J'ai déjà eu 13 guillemets de suite dans dynamic-sql-that-generated-dynamic-sql-that-generated-dynamic-sql ...
ErikE

Je dois vérifier si le travail est terminé. Msgstr "Une instruction INSERT EXEC ne peut pas être imbriquée". Je te déteste Microsoft.
alexkovelsky du

11

Pour ce faire, vous devez d'abord créer un #test_tablecomme ci-dessous:

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

Maintenant, exécutez la procédure et mettez de 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

2
Y a-t-il un avantage à créer une table temporaire au lieu d'une variable de table?
Dave Kelly

1
meilleure solution que j'ai trouvée sur stackoverflow! :)
Umar

4
Que faire si je n'ai besoin que d'une colonne d'une autre procédure stockée?
Keval Patel

11

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 une table temporaire manuellement - elle est créée automatiquement. J'espère que cela t'aides.


Soyez prudent en utilisant les tables ## car elles sont partagées entre les sessions
eKelvin

1
Vous pouvez trouver une brève description des différences entre les tables # et ## dans stackoverflow.com/a/34081438/352820
eKelvin

Est-ce sujet à l'injection SQL?
Bushrod

11

Il pourrait être utile de savoir pourquoi cela est si difficile. Une procédure stockée peut uniquement renvoyer du texte («texte» imprimé), ou renvoyer plusieurs tableaux, ou ne renvoyer aucun tableau du tout.

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


12
SQL Server est libre de générer une erreur si cela se produit. par exemple, si j'écris une sous-requête qui renvoie plus d'une valeur. Oui, cela peut arriver, mais en réalité, cela ne se produit pas. Et même si c'était le cas: il n'est pas difficile de soulever une erreur.
Ian Boyd

8

(En supposant que SQL Server)

La seule façon de travailler avec les résultats d'une procédure stockée dans T-SQL est d'utiliser la INSERT INTO ... EXECsyntaxe. 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.


1
Cela nécessite de connaître la définition de la table. Pas utile.
Triynko

8

Un hack rapide serait d'ajouter un nouveau paramètre '@Column_Name'et de faire définir par la fonction appelante le nom de la colonne à récupérer. Dans la partie de retour de votre sproc, vous auriez des instructions if / else et ne renverriez que la colonne spécifiée, ou si vide - renvoyez 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

Si vous faites cela pour une 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 aux suivantes:

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

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 effectuez une insertion dans l'instruction par rapport à la procédure stockée. Le jeu de résultats du 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 ça!


Pour cela, il faut créer une copie de la définition de table. Existe-t-il un moyen de l'éviter?
Hardik

7

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.

Donc, l'inverse consiste à remplir la table en fonction de l'ensemble 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_configureavec les deux paramètres afin de modifier une option de configuration ou d'exécuter l' RECONFIGUREinstruction, vous devez disposer de l' ALTER SETTINGSautorisation au niveau du serveur

Vous pouvez maintenant sélectionner vos colonnes spécifiques dans le tableau généré

SELECT col1, col2
FROM #temp

4

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

1
Lol - il ne l'a pas fait. Il l'a codé dans l'invocation de la procédure stockée à la place où elle peut être obtenue beaucoup plus facilement sans accès à la base de données par reniflement de réseau.
Martin Milan

Assez facile à retirer de Github aussi.
nomen

3

Je sais que l'exécution depuis sp et l'insertion dans une table temporaire ou une variable de table serait une option, mais je ne pense pas que ce soit votre exigence. Selon vos besoins, cette déclaration de 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 de confiance, utilisez cette instruction de 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 lors de l'exécution de 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 fait face à ce genre de problème similaire. Si quelqu'un veut essayer avec 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, alors vous devez créer autant de colonnes dans la table temporaire ou la 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

Pour tous ceux qui ont SQL 2012 ou version ultérieure, j'ai pu accomplir cela avec des procédures stockées qui ne sont pas dynamiques et ont les mêmes colonnes de sortie à chaque fois.

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

Remarque: il existe des solutions bien meilleures et plus universelles qui fonctionneront avec moins de lignes de code si vous souhaitez / pouvez mettre à jour le SP ou modifier la configuration et l'utilisation 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

Le moyen le plus simple de le faire si vous n'en avez besoin qu'une fois:

Exportez vers Excel dans l'assistant d'importation et d'exportation, puis importez cet Excel dans une table.


4
L'intérêt de créer un proc stocké est la réutilisabilité. Votre réponse contredit totalement cela.
deutschZuid

6
Pour contrer deutschZuid, dans le message d'origine, il ne mentionne pas s'il veut réutiliser cela ou s'il essaie simplement de parcourir les résultats d'un proc stocké. Martin a raison, c'est probablement le moyen le plus simple s'il n'a besoin de le faire qu'une seule fois.
Bishop

0

Créez une vue dynamique et obtenez-en des résultats .......

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

-1

Je voudrais couper et coller le SP d'origine et supprimer toutes les colonnes sauf les 2 que vous voulez. Ou. Je ramènerais l'ensemble de résultats, le mapperais à un objet métier approprié, puis LINQ sur les deux colonnes.


1
Les gens ne font pas ça. Cela violera le principe DRY. Lorsque les choses changent, pas si, vous devrez maintenant suivre et saisir vos modifications dans tous les endroits.
jriver27
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.