Seleziona le colonne dal set di risultati della procedura memorizzata


448

Ho una stored procedure che restituisce 80 colonne e 300 righe. Voglio scrivere una selezione che ottiene 2 di quelle colonne. Qualcosa di simile a

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

Quando ho usato la sintassi sopra ho visualizzato l'errore:

"Nome colonna non valido".

So che la soluzione più semplice sarebbe quella di cambiare la procedura memorizzata, ma non l'ho scritta e non posso cambiarla.

C'è un modo per fare quello che voglio?

  • Potrei creare una tabella temporanea per inserire i risultati, ma poiché ci sono 80 colonne quindi avrei bisogno di creare una tabella temporanea a 80 colonne solo per ottenere 2 colonne. Volevo evitare di rintracciare tutte le colonne restituite.

  • Ho provato a usare WITH SprocResults AS ....come suggerito da Mark, ma ho ottenuto 2 errori

    Sintassi errata vicino alla parola chiave "EXEC".
    Sintassi errata vicino a ")".

  • Ho provato a dichiarare una variabile di tabella e ho ottenuto il seguente errore

    Errore di inserimento: il nome della colonna o il numero di valori forniti non corrisponde alla definizione della tabella

  • Se provo
    SELECT * FROM EXEC MyStoredProc 'param1', 'param2'
    ottengo l'errore:

    Sintassi errata vicino alla parola chiave "exec".


Per curiosità, questa query funziona: SELEZIONA * DA EXEC MyStoredProc 'param1', 'param2' In tal caso, quali nomi di colonna vengono visualizzati nel set di risultati e puoi utilizzare quei nomi di colonna nell'elenco di selezione?
Dave Costa,

5
Non ho mai trovato una risposta per questo.
Rossini,

32
Beh, non hai mai risposto a una domanda molto importante! Di quale piattaforma SQL stai chiedendo? MySQL, Microsoft SQL Server, Oracle, ecc. Mi sembra che sia SQL Server, ma devi dirlo alle persone o non possono rispondere in modo affidabile alla tua domanda.
JMTyler,

6
Bene, deve essere MS-SQL. EXECnon è una parola chiave MySQL (l'equivalente MySQL è istruzioni preparate ). Anche se mi piacerebbe sapere la risposta per MySQL, le risposte sotto target T-SQL. Ricodifica.
bobobobo,

1
Non ho mai trovato una risposta per questo
Rossini,

Risposte:


186

Puoi dividere la query? Inserire i risultati proc memorizzati in una variabile di tabella o in una tabella temporanea. Quindi, selezionare le 2 colonne dalla variabile della tabella.

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

SELECT col1, col2 FROM @tablevar

27
Inoltre, non funziona quando non si conosce la definizione della tabella
Ian Boyd,

non sapevo di quel tipo. Sono implementate come le tabelle temporanee? O è rigorosamente in memoria?
d -_- b

Questo è stato interessante: sqlnerd.blogspot.com/2005/09/…
d -_-b

Funziona bene se il numero di colonne fornite nella tabella temporanea è uguale a quello nell'output della procedura memorizzata. chagbert.
Chagbert,

83

Ecco un link a un documento abbastanza buono che spiega tutti i diversi modi per risolvere il problema (anche se molti di essi non possono essere utilizzati poiché non è possibile modificare la procedura memorizzata esistente).

Come condividere i dati tra le procedure memorizzate

La risposta di Gulzar funzionerà (è documentata nel link sopra) ma sarà una seccatura per scrivere (dovrai specificare tutti gli 80 nomi di colonna nella tua dichiarazione @tablevar (col1, ...). E in futuro se una colonna viene aggiunta allo schema o l'output viene modificato, dovrà essere aggiornato nel codice o si verificherà un errore.


1
Penso che il suggerimento OPENQUERY in quel link sia molto più vicino a ciò che l'OP sta cercando.
Corin,


39

Questo funziona per me: (cioè ho solo bisogno di 2 colonne dei 30+ restituiti da sp_help_job)

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

Prima che funzionasse, dovevo eseguire questo:

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

.... per aggiornare la sys.serverstabella. (ovvero l'uso di un autoreferenziazione all'interno di OPENQUERY sembra essere disabilitato per impostazione predefinita.)

Per la mia semplice esigenza, non ho riscontrato nessuno dei problemi descritti nella sezione OPENQUERY dell'eccellente collegamento di Lance.

Rossini, se devi impostare dinamicamente quei parametri di input, l'uso di OPENQUERY diventa un po 'più complicato:

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

Non sono sicuro delle differenze (se presenti) tra l'utilizzo sp_serveroptionper aggiornare direttamente l' sys.serversautoreferenziamento esistente e l'uso sp_addlinkedserver(come descritto nel collegamento di Lance) per creare un duplicato / alias.

Nota 1: Preferisco OPENQUERY rispetto a OPENROWSET, dato che OPENQUERY non richiede la definizione della stringa di connessione all'interno del proc.

Nota 2: Detto questo: normalmente userei INSERT ... EXEC :) Sì, sono 10 minuti in più di digitazione, ma se posso aiutarlo, preferisco non muovermi con:
(a) virgolette tra virgolette all'interno citazioni e
(b) tabelle sys e / o subdole configurazioni di server collegati autoreferenziali (ovvero per queste, ho bisogno di presentare il mio caso ai nostri DBA onnipotenti :)

Tuttavia, in questo caso, non ho potuto usare un costrutto INSERT ... EXEC, come sp_help_jobgià ne sto usando. ("Non è possibile nidificare un'istruzione INSERT EXEC.")


3
Ho avuto 13 virgolette consecutive di seguito in dynamic-sql-that-generated-dynamic-sql-that-generated-dynamic-sql ...
ErikE

Devo controllare se il lavoro è finito. "Non è possibile nidificare un'istruzione INSERT EXEC". Ti odio Microsoft.
alexkovelsky,

11

Per raggiungere questo obiettivo, prima devi creare un #test_tablelike di seguito:

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

Ora esegui la procedura e inserisci il valore in #test_table:

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

Ora prendi il valore da #test_table:

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

2
C'è un vantaggio nella creazione di una tabella temporanea anziché di una variabile di tabella?
Dave Kelly,

1
la migliore soluzione che ho trovato su StackOverflow! :)
Umar,

4
Cosa succede se ho bisogno solo di una colonna da un'altra Stored procedure?
Keval Patel,

11

Se è possibile modificare la procedura memorizzata, è possibile inserire facilmente le definizioni delle colonne richieste come parametro e utilizzare una tabella temporanea creata automaticamente:

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 questo caso non è necessario creare manualmente una tabella temporanea: viene creata automaticamente. Spero che sia di aiuto.


Fai

1
È possibile trovare una breve descrizione delle differenze tra # e ## tavoli in stackoverflow.com/a/34081438/352820
eKelvin

È incline a SQL Injection?
Bushrod

11

Potrebbe essere utile sapere perché questo è così difficile. Una procedura memorizzata può restituire solo testo (stampa "testo"), oppure può restituire più tabelle o non restituire alcuna tabella.

Quindi qualcosa del genere SELECT * FROM (exec sp_tables) Table1 non funzionerà


12
SQL Server è libero di generare un errore se ciò accade. ad es. se scrivo una sottoquery che restituisce più di un valore. Sì, può succedere, ma in realtà non lo è. E anche se lo facesse: non è difficile sollevare un errore.
Ian Boyd,

8

(Supponendo SQL Server)

L'unico modo per lavorare con i risultati di una procedura memorizzata in T-SQL è utilizzare la INSERT INTO ... EXECsintassi. Ciò ti dà la possibilità di inserire in una tabella temporanea o in una variabile di tabella e da lì selezionare i dati che ti servono.


1
Ciò richiede la conoscenza della definizione della tabella. Inutile.
Triynko,

8

Un trucco rapido sarebbe aggiungere un nuovo parametro '@Column_Name'e fare in modo che la funzione chiamante definisca il nome della colonna da recuperare. Nella parte di ritorno del tuo sproc, avresti le istruzioni if ​​/ else e restituisci solo la colonna specificata, o se vuoto - restituisci tutto.

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

Se lo stai facendo per la convalida manuale dei dati, puoi farlo con LINQPad.

Creare una connessione al database in LinqPad, quindi creare istruzioni C # simili alle seguenti:

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

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


7

Per SQL Server, trovo che funzioni bene:

Creare una tabella temporanea (o tabella permanente, non importa davvero) ed eseguire un inserimento nell'istruzione contro la procedura memorizzata. Il set di risultati dell'SP dovrebbe corrispondere alle colonne della tabella, altrimenti verrà visualizzato un errore.

Ecco un esempio:

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;

Questo è tutto!


Per questo, è necessario creare una copia della definizione della tabella. C'è un modo per evitarlo?
Hardik,

7

Come è stato menzionato nella domanda, è difficile definire la tabella temporanea a 80 colonne prima di eseguire la procedura memorizzata.

Quindi il contrario è quello di popolare la tabella in base al set di risultati della procedura memorizzata.

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

Se si verificano errori, è necessario abilitare le query distribuite ad hoc eseguendo la query seguente.

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

Per eseguire sp_configurecon entrambi i parametri per modificare un'opzione di configurazione o per eseguire l' RECONFIGUREistruzione, è necessario disporre ALTER SETTINGSdell'autorizzazione a livello di server

Ora puoi selezionare le tue colonne specifiche dalla tabella generata

SELECT col1, col2
FROM #temp

4

prova questo

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 - non l'ha fatto. Lo ha codificato nell'invocazione della procedura memorizzata invece dove può essere ottenuta molto più facilmente senza accesso al database tramite lo sniffing di rete.
Martin Milan,

Abbastanza facile farlo uscire anche da Github.
nomen

3

So che eseguire da sp e inserire in una tabella temporanea o una variabile di tabella sarebbe un'opzione, ma non penso che sia il tuo requisito. Secondo il vostro requisito questa istruzione query sotto dovrebbe funzionare:

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

se si dispone di una connessione attendibile, utilizzare la seguente istruzione query:

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

se ricevi errori per eseguire l'istruzione precedente, esegui questa istruzione di seguito:

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

Spero che questo possa aiutare qualcuno che avrà affrontato questo tipo di problema simile. Se qualcuno dovesse provare con una tabella temporanea o una variabile di tabella che dovrebbe essere come questa di seguito ma in questo scenario dovresti sapere quante colonne sta restituendo il tuo sp, allora dovresti creare tante colonne nella tabella temporanea o nella variabile tabella:

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

Per chiunque abbia SQL 2012 o versioni successive, sono stato in grado di farlo con procedure memorizzate che non sono dinamiche e hanno lo stesso output di colonne ogni volta.

L'idea generale è di creare la query dinamica per creare, inserire, selezionare e rilasciare la tabella temporanea ed eseguirla dopo che è stata generata. Genero dinamicamente la tabella temporanea recuperando prima i nomi e i tipi di colonna dalla procedura memorizzata .

Nota: esistono soluzioni molto migliori e più universali che funzioneranno con meno righe di codice se si desidera / è in grado di aggiornare SP o modificare la configurazione e l'utilizzo OPENROWSET. Usa il seguito se non hai altro modo.

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

Il modo più semplice da fare se è necessario farlo solo una volta:

Esporta in Excel nella procedura guidata Importa ed esporta, quindi importa questo Excel in una tabella.


4
Il punto centrale della creazione di un proc memorizzato è la riusabilità. La tua risposta lo contraddice totalmente.
deutschZuid

6
Per contrastare deutschZuid, nel post originale, non menziona se vuole riutilizzarlo o se sta solo cercando di esaminare i risultati di un proc memorizzato. Martin ha ragione, questo è probabilmente il modo più semplice se deve farlo solo una volta.
Vescovo

0

Crea una vista dinamica e ottieni risultati da essa .......

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

Tagliare e incollare l'SP originale ed eliminare tutte le colonne tranne le 2 desiderate. O. Riporterei indietro il risultato, lo mapperei su un oggetto business adeguato, quindi LINQ tra le due colonne.


1
Le persone non lo fanno. Ciò violerà il principio DRY. Quando le cose cambiano, non se, ora dovrai rintracciare e inserire la modifica in tutte le posizioni.
jriver27,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.