Wählen Sie Spalten aus der Ergebnismenge der gespeicherten Prozedur aus

447

Ich habe eine gespeicherte Prozedur, die 80 Spalten und 300 Zeilen zurückgibt. Ich möchte eine Auswahl schreiben, die 2 dieser Spalten erhält. Etwas wie

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

Wenn ich die obige Syntax verwendet habe, erhalte ich den Fehler:

"Spaltenname ungültig".

Ich weiß, dass die einfachste Lösung darin besteht, die gespeicherte Prozedur zu ändern, aber ich habe sie nicht geschrieben und kann sie nicht ändern.

Gibt es eine Möglichkeit zu tun, was ich will?

  • Ich könnte eine temporäre Tabelle erstellen, um die Ergebnisse einzufügen, aber da es 80 Spalten gibt, müsste ich eine temporäre Tabelle mit 80 Spalten erstellen, um 2 Spalten zu erhalten. Ich wollte vermeiden, alle zurückgegebenen Spalten aufzuspüren.

  • Ich habe versucht, WITH SprocResults AS ....wie von Mark vorgeschlagen zu verwenden, aber ich habe 2 Fehler erhalten

    Falsche Syntax in der Nähe des Schlüsselworts 'EXEC'.
    Falsche Syntax in der Nähe ')'.

  • Ich habe versucht, eine Tabellenvariable zu deklarieren, und habe den folgenden Fehler erhalten

    Einfügefehler: Der Spaltenname oder die Anzahl der angegebenen Werte stimmen nicht mit der Tabellendefinition überein

  • Wenn ich es versuche,
    SELECT * FROM EXEC MyStoredProc 'param1', 'param2'
    erhalte ich den Fehler:

    Falsche Syntax in der Nähe des Schlüsselworts 'exec'.

Rossini
quelle
Funktioniert diese Abfrage aus Neugier: SELECT * FROM EXEC MyStoredProc 'param1', 'param2' Wenn ja, welche Spaltennamen werden in der Ergebnismenge angezeigt, und können Sie diese Spaltennamen in Ihrer Auswahlliste verwenden?
Dave Costa
5
Ich habe nie eine Antwort darauf gefunden.
Rossini
32
Nun, Sie haben nie eine sehr wichtige Frage beantwortet! Nach welcher SQL-Plattform fragen Sie? MySQL, Microsoft SQL Server, Oracle usw. Für mich sieht es so aus, als wäre es SQL Server, aber Sie müssen es den Leuten mitteilen, sonst können sie Ihre Frage nicht zuverlässig beantworten.
JMTyler
6
Nun, es muss MS-SQL sein. EXECist kein MySQL-Schlüsselwort (das MySQL-Äquivalent sind vorbereitete Anweisungen ). Obwohl ich die Antwort für MySQL wissen möchte, zielen die folgenden Antworten auf T-SQL ab. Retagging.
Bobobobo
1
Ich habe nie eine Antwort darauf gefunden
Rossini

Antworten:

186

Können Sie die Abfrage aufteilen? Fügen Sie die gespeicherten Proc-Ergebnisse in eine Tabellenvariable oder eine temporäre Tabelle ein. Wählen Sie dann die 2 Spalten aus der Tabellenvariablen aus.

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

SELECT col1, col2 FROM @tablevar
Gulzar Nazim
quelle
27
Es funktioniert auch nicht, wenn Sie die Tabellendefinition nicht kennen
Ian Boyd
wusste nichts über diesen Typ. Sind sie genauso implementiert wie temporäre Tabellen? Oder ist es nur in Erinnerung?
d -_- b
Das war interessant: sqlnerd.blogspot.com/2005/09/…
d -_- b
Dies funktioniert einwandfrei, wenn die Anzahl der in der temporären Tabelle angegebenen Spalten mit der in der Ausgabe der gespeicherten Prozedur übereinstimmt. chagbert.
Chagbert
83

Hier ist ein Link zu einem ziemlich guten Dokument, in dem die verschiedenen Möglichkeiten zur Lösung Ihres Problems erläutert werden (obwohl viele davon nicht verwendet werden können, da Sie die vorhandene gespeicherte Prozedur nicht ändern können.)

So teilen Sie Daten zwischen gespeicherten Prozeduren

Gulzars Antwort wird funktionieren (es ist im obigen Link dokumentiert), aber das Schreiben wird mühsam sein (Sie müssen alle 80 Spaltennamen in Ihrer @ tablevar (col1, ...) - Anweisung angeben. Und in Zukunft Wenn dem Schema eine Spalte hinzugefügt oder die Ausgabe geändert wird, muss sie in Ihrem Code aktualisiert werden, da sonst ein Fehler auftritt.

Lance McNearney
quelle
1
Ich denke, der OPENQUERY-Vorschlag in diesem Link ist viel näher an dem, wonach das OP sucht.
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

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

Peter Nazarov
quelle
@ LawfulHacker Heiliger Rauch. Was machen Sie mit SQL Server 2000 im Jahr 2014?
John Zabroski
1
Große Unternehmen mit Legacy-Systemen: D
LawfulHacker
39

Das funktioniert bei mir: (dh ich brauche nur 2 Spalten der 30+, die von zurückgegeben wurden sp_help_job)

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

Bevor dies funktionieren würde, musste ich Folgendes ausführen:

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

.... um die sys.serversTabelle zu aktualisieren . (dh die Verwendung einer Selbstreferenz in OPENQUERY scheint standardmäßig deaktiviert zu sein.)

Für meine einfache Anforderung stieß ich auf keines der Probleme, die im Abschnitt OPENQUERY von Lances ausgezeichnetem Link beschrieben wurden.

Rossini, wenn Sie diese Eingabeparameter dynamisch einstellen müssen, wird die Verwendung von OPENQUERY etwas umständlicher:

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

Ich bin mir nicht sicher, welche Unterschiede (falls vorhanden) zwischen der Verwendung sp_serveroptionzum sys.serversdirekten Aktualisieren der vorhandenen Selbstreferenz und der Verwendung sp_addlinkedserver(wie in Lances Link beschrieben) zum Erstellen eines Duplikats / Alias ​​bestehen.

Anmerkung 1: Ich bevorzuge OPENQUERY gegenüber OPENROWSET, da OPENQUERY die Definition der Verbindungszeichenfolge innerhalb des Prozesses nicht erfordert.

Anmerkung 2: Nachdem ich das alles gesagt habe: Normalerweise würde ich nur INSERT ... EXEC verwenden :) Ja, es ist 10 Minuten mehr Zeit für die Eingabe, aber wenn ich helfen kann, ziehe ich es vor, nicht herumzuspielen mit:
(a) Anführungszeichen innerhalb von Anführungszeichen innerhalb Anführungszeichen und
(b) Sys-Tabellen und / oder hinterhältige selbstreferenzierende Verbindungsserver-Setups (dh für diese muss ich meinen Fall unseren allmächtigen Datenbankadministratoren vorlegen :)

In diesem Fall konnte ich jedoch kein INSERT ... EXEC-Konstrukt verwenden, da sp_help_jobbereits eines verwendet wird. ("Eine INSERT EXEC-Anweisung kann nicht verschachtelt werden.")

Merenzo
quelle
3
Ich hatte 13 einfache Anführungszeichen hintereinander in dynamischem SQL, das dynamisches SQL generiert hat, das dynamisches SQL generiert hat ...
ErikE
Ich muss überprüfen, ob der Job beendet ist. "Eine INSERT EXEC-Anweisung kann nicht verschachtelt werden". Ich hasse dich Microsoft.
Alexkovelsky
11

Um dies zu erreichen, erstellen Sie zunächst ein " #test_tableGefällt mir" :

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

Führen Sie nun die Prozedur aus und geben Sie den Wert ein in #test_table:

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

Jetzt holen Sie den Wert von #test_table:

select col1,col2....,col80 from #test_table
Navneet
quelle
2
Gibt es einen Vorteil beim Erstellen einer temporären Tabelle anstelle einer Tabellenvariablen?
Dave Kelly
1
Die beste Lösung, die ich bei Stackoverflow gefunden habe! :)
Umar
4
Was ist, wenn ich nur eine Spalte aus einer anderen gespeicherten Prozedur benötige?
Keval Patel
11

Wenn Sie Ihre gespeicherte Prozedur ändern können, können Sie die erforderlichen Spaltendefinitionen einfach als Parameter festlegen und eine automatisch erstellte temporäre Tabelle verwenden:

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

In diesem Fall müssen Sie eine temporäre Tabelle nicht manuell erstellen - sie wird automatisch erstellt. Hoffe das hilft.

dyatchenko
quelle
Seien Sie vorsichtig mit
##
1
Eine kurze Beschreibung der Unterschiede zwischen # und ##
eKelvin
Ist dies anfällig für SQL Injection?
Bushrod
11

Es kann hilfreich sein zu wissen, warum dies so schwierig ist. Eine gespeicherte Prozedur kann nur Text zurückgeben ('Text' drucken) oder mehrere Tabellen zurückgeben oder überhaupt keine Tabellen zurückgeben.

So etwas SELECT * FROM (exec sp_tables) Table1 wird nicht funktionieren

newbie007
quelle
12
In diesem Fall kann SQL Server einen Fehler auslösen. zB wenn ich eine Unterabfrage schreibe, die mehr als einen Wert zurückgibt. Ja, es kann passieren, aber in Wirklichkeit nicht. Und selbst wenn ja: Es ist nicht schwierig, einen Fehler auszulösen.
Ian Boyd
8

(Angenommen, SQL Server)

Die einzige Möglichkeit, mit den Ergebnissen einer in T-SQL gespeicherten Prozedur zu arbeiten, ist die Verwendung der INSERT INTO ... EXECSyntax. Das gibt Ihnen die Möglichkeit, in eine temporäre Tabelle oder eine Tabellenvariable einzufügen und von dort aus die benötigten Daten auszuwählen.

Brannon
quelle
1
Das erfordert die Kenntnis der Tabellendefinition. Nicht nützlich.
Triynko
8

Ein schneller Hack wäre, einen neuen Parameter hinzuzufügen '@Column_Name'und die aufrufende Funktion den abzurufenden Spaltennamen definieren zu lassen. Im Rückgabeteil Ihres sproc hätten Sie if / else-Anweisungen und geben nur die angegebene Spalte zurück, oder wenn leer - geben Sie alle zurück.

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
quelle
7

Wenn Sie dies zur manuellen Validierung der Daten tun, können Sie dies mit LINQPad tun.

Erstellen Sie in LinqPad eine Verbindung zur Datenbank und erstellen Sie dann C # -Anweisungen ähnlich den folgenden:

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

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

ShawnFeatherly
quelle
7

Für SQL Server finde ich, dass dies gut funktioniert:

Erstellen Sie eine temporäre Tabelle (oder eine permanente Tabelle, die eigentlich keine Rolle spielt) und fügen Sie eine Anweisung in die gespeicherte Prozedur ein. Die Ergebnismenge des SP sollte mit den Spalten in Ihrer Tabelle übereinstimmen, da sonst eine Fehlermeldung angezeigt wird.

Hier ist ein Beispiel:

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;

Das ist es!

LTMOD
quelle
Dazu muss eine Kopie der Tabellendefinition erstellt werden. Gibt es eine Möglichkeit, dies zu vermeiden?
Hardik
7

Wie in der Frage erwähnt, ist es schwierig, die temporäre Tabelle mit 80 Spalten zu definieren, bevor die gespeicherte Prozedur ausgeführt wird.

Umgekehrt besteht die Tabelle darin, die Tabelle basierend auf der Ergebnismenge der gespeicherten Prozedur zu füllen.

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

Wenn Sie eine Fehlermeldung erhalten, müssen Sie verteilte Ad-hoc-Abfragen aktivieren, indem Sie die folgende Abfrage ausführen.

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

Um sp_configuremit beiden Parametern eine Konfigurationsoption zu ändern oder die RECONFIGUREAnweisung auszuführen , muss Ihnen die Berechtigung auf ALTER SETTINGSServerebene erteilt werden

Jetzt können Sie Ihre spezifischen Spalten aus der generierten Tabelle auswählen

SELECT col1, col2
FROM #temp
sqluser
quelle
4

Versuche dies

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
quelle
1
Lol - hat er nicht. Er codierte es stattdessen in den Aufruf der gespeicherten Prozedur, wo es ohne Netzwerkzugriff viel einfacher abgerufen werden kann, ohne auf die Datenbank zuzugreifen.
Martin Milan
Ziemlich einfach, es auch aus Github herauszuholen.
Nomen
3

Ich weiß, dass die Ausführung von sp und das Einfügen in eine temporäre Tabelle oder Tabellenvariable eine Option wäre, aber ich denke nicht, dass dies Ihre Anforderung ist. Gemäß Ihrer Anforderung sollte die folgende Abfrageanweisung funktionieren:

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

Wenn Sie eine vertrauenswürdige Verbindung haben, verwenden Sie die folgende Abfrageanweisung:

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

Wenn beim Ausführen der obigen Anweisung eine Fehlermeldung angezeigt wird, führen Sie einfach die folgende Anweisung aus:

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

Ich hoffe, dies wird jemandem helfen, der mit einem ähnlichen Problem konfrontiert ist. Wenn jemand versuchen würde, eine temporäre Tabelle oder Tabellenvariable zu verwenden, die wie folgt aussehen sollte, aber in diesem Szenario sollten Sie wissen, wie viele Spalten Ihr SP zurückgibt, sollten Sie so viele Spalten in einer temporären Tabelle oder Tabellenvariablen erstellen:

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

Für alle, die über SQL 2012 oder höher verfügen, konnte ich dies mit gespeicherten Prozeduren erreichen, die nicht dynamisch sind und jedes Mal die gleichen Spalten ausgeben.

Die allgemeine Idee ist, dass ich die dynamische Abfrage erstelle, um die temporäre Tabelle zu erstellen, in sie einzufügen, aus ihr auszuwählen, sie zu löschen und sie auszuführen, nachdem alles generiert wurde. Ich generiere die temporäre Tabelle dynamisch, indem ich zuerst Spaltennamen und -typen aus der gespeicherten Prozedur abrufe .

Hinweis: Es gibt viel bessere, universellere Lösungen, die mit weniger Codezeilen funktionieren, wenn Sie bereit / in der Lage sind, den SP zu aktualisieren oder die Konfiguration und Verwendung zu ändern OPENROWSET. Verwenden Sie das Folgende, wenn Sie keinen anderen Weg haben.

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
quelle
0

Der einfachste Weg, wenn Sie dies nur einmal benötigen:

Exportieren Sie im Import- und Export-Assistenten nach Excel und importieren Sie dieses Excel in eine Tabelle.

Martijn Tromp
quelle
4
Der springende Punkt beim Erstellen eines gespeicherten Prozesses ist die Wiederverwendbarkeit. Ihre Antwort widerspricht dem völlig.
deutschZuid
6
Um deutschZuid entgegenzuwirken, erwähnt er im ursprünglichen Beitrag nicht, ob er dies wiederverwenden möchte oder ob er nur versucht, die Ergebnisse eines gespeicherten Prozesses zu durchsuchen. Martin hat recht, dies ist wahrscheinlich der einfachste Weg, wenn er es nur einmal tun muss.
Bischof
0

Erstellen Sie eine dynamische Ansicht und erhalten Sie ein Ergebnis daraus .......

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

Ich würde den ursprünglichen SP ausschneiden und einfügen und alle Spalten außer den gewünschten 2 löschen. Oder. Ich würde die Ergebnismenge zurückbringen, sie einem richtigen Geschäftsobjekt zuordnen und dann die beiden Spalten LINQ.

Andrew
quelle
1
Leute machen das nicht. Dies verstößt gegen das DRY-Prinzip. Wenn sich die Dinge ändern, nicht wenn, müssen Sie jetzt Ihre Änderungen an allen Standorten aufspüren und eingeben.
jriver27