2011-10-19 10 views
9

mio scenario

Sto lavorando su un database che conterrà molti dettagli da varie stored procedure in diverse banche dati in tutto l'intero server. Le informazioni che sto tentando di raccogliere ora sono: "Che cosa produce l'SP?"Utilizzo di OPENROWSET per recuperare dinamicamente i risultati SP quando SP contiene # tabelle temporanee

Nella ricerca ho trovato che la risposta si trova in OPENROWSET. I miei test iniziali hanno avuto successo e tutto sembrava fantastico. Tuttavia, dopo averlo testato con SP live, mi sono imbattuto in uno dei problemi principali: non funziona bene con le tabelle temp (#).

Ad esempio:

Se dovessi prendere questo SP:

CREATE PROCEDURE dbo.zzTempSP(@A INT, @B INT) AS 
SELECT @A AS A, @B AS B 

I può facilmente inserire l'output in una temperatura (##) tabella con il seguente codice, quindi interrogare sysobjects di tempdb e produrre un elenco delle colonne e dei loro tipi di dati:

IF OBJECT_ID('tempdb.dbo.##TempOutput','U') IS NOT NULL DROP TABLE ##TempOutput 

DECLARE @sql VARCHAR(MAX) 
SELECT @sql = 'SELECT * 
       INTO ##TempOutput 
       FROM OPENROWSET(''SQLNCLI'', ''Server=' + 
     CONVERT(VARCHAR(100), SERVERPROPERTY('MachineName')) + 
          ';Trusted_Connection=yes;'', ''SET FMTONLY OFF exec ' + 
           DB_NAME() + 
           '.dbo.zzTempSP @A=1, @B=2'')' 
EXEC(@sql) 

SELECT * 
FROM ##TempOutput 

Grande! Tuttavia, se il SP è stato questo, invece:

CREATE PROCEDURE dbo.zzTempSP (@A INT, @B INT) AS CREATE TABLE dbo.#T (A INT, B INT) 

INSERT INTO dbo.#T 
SELECT @A AS A, @B AS B 

SELECT * 
FROM dbo.#T 

Quando eseguo lo stesso codice OPENROWSET come prima ricevo il seguente errore:

Cannot process the object "SET FMTONLY OFF exec DatabaseName.dbo.zzTempSP @A=1,@B=2". The OLE DB provider "SQLNCLI10" for linked server "(null)" indicates that either the object has no columns or the current user does not have permissions on that object.

Quando ho rintuzzare il codice OPENROWSET (rimuovendo la dinamica roba) a questo:

SELECT * 
FROM OPENROWSET('SQLNCLI','Server=ServerName;Trusted_Connection=yes;', 
          'exec DatabaseName.dbo.zzTempSP @A=1,@B=2' 
      ) 

ricevo il seguente (molto più utile) di errore:

Invalid object name '#T'.

Qual è il punto in cui colpisco il muro. Nella mia ricerca sembra che non ci sia una soluzione, ma non potevo ancora arrendermi.

E così sto portato a ..

La mia domanda a voi

C'è qualcuno a conoscenza di possibili modo per aggirare questo errore? O c'è forse una soluzione alternativa?

Questo processo non verrà eseguito frequentemente, quindi non mi devo preoccupare troppo dell'efficienza della soluzione.

Qualsiasi input sarebbe molto apprezzato.

Grazie, Zok

PS: Mi dispiace per la formattazione. Non ho capito bene i tag della lingua.

+0

Penso di aver trovato un lead che implica l'utilizzo di SET NOCOUNT ON. Quando l'ho aggiunto al mio dummy SP ha funzionato, ma non per quello che effettivamente userò (che in realtà aveva già quella linea).Continuerò a giocarci e riferirò su ciò che trovo. –

+0

Nello stesso modo in cui ho menzionato sopra, hanno dovuto introdurre un No Op alla SP. Ho preso in considerazione la creazione di una procedura intermedia che analizzerebbe la SP che stiamo cercando di raccogliere i dettagli (attraverso i syscomments) tirando fuori la definizione della tabella temporanea per creare dinamicamente un No Op, ma vedo molti problemi che sarebbero difficili da lavorare in giro. Soooooo, sono ancora nella stessa barca. –

+0

Un grande sforzo da parte tua .... grazie –

risposta

16

Ho avuto questa domanda anche su SQL Server Central e alcune risposte mi hanno riportato a cercare una risposta all'interno di OPENROWSET (e trovandola). Una delle persone mi ha trasformato nella sezione this article su OPENQUERY. Esso afferma che, al fine di risolvere il problema con le tabelle del temp è sufficiente aggiungere SET FMTONLY OFF alla linea di esecuzione della sua dichiarazione/OPENROWSET OPENQUERY in questo modo:

SELECT * 
FROM OPENROWSET('SQLNCLI', 
        'Server=SERVERNAME;Trusted_Connection=yes;', 
        'SET FMTONLY OFF; exec DatabaseName.dbo.zzTempSP @A=1,@B=2' 
       ) 

Tuttavia, se la procedura non lo fa avere SET NOCOUNT ON specificato genera ancora un errore. Ho avuto uno sciocco equivoco su SET NOCOUNT ON nella parte posteriore della mia testa che mi ha impedito di pensare, "Ehi, non posso semplicemente aggiungere SET NOCOUNT ON all'istruzione execute di OPENROWSET ??" Una volta qualcuno ha chiesto a questa domanda per me l'altro thread ha reso tutto molto senso =) Quindi, ecco la soluzione che ho cercato per tutto il tempo:

SELECT * 
FROM OPENROWSET('SQLNCLI', 
        'Server=SERVERNAME;Trusted_Connection=yes;', 
        'SET FMTONLY OFF; SET NOCOUNT ON; exec DatabaseName.dbo.zzTempSP @A=1,@B=2' 
       ) 
+0

Viene eseguito/MOLTO/più lento della soluzione xpcmdshell che ho postato. Se è necessario fare qualcosa di simile ma non è necessario conoscere i tipi di dati delle colonne, suggerirei invece di utilizzare la soluzione xpcmdshell. –

+0

Voglio avvisare gli utenti di usare 'SET FMTONLY OFF' in qualsiasi caso critico: ricorda, l'impostazione 'SET FMTONLY OFF' causa l'esecuzione DOPPIA di dichiarazioni in corso! Se il tuo SP inserisce dei dati, potresti metterti nei guai! Con l'opzione FMTONLY OFF, SQL Server invierà l'istruzione query per la prima volta per ottenere i dati metadati. – xacinay

2

Okay .. Mi sono arreso e sono tornato dal mio vecchio amico xpcmdshell. Attraverso questa risposta e il suo codice il carattere di sottolineatura (_) sarà implicito per xpcmdshell poiché spesso non riesco a caricare pagine contenenti il ​​nome completo.

In primo luogo, qui ci sono solo tre delle cose che ho provato che non ha funzionato (non riesco a ricordare tutti gli altri):

  • SET NOCOUNT ON
    • funziona per ogni SP senza tabelle temporanee, ma come la maggior parte dei 2500 + - cercherò di farne uso non è fattibile.
  • No Op
    • Ho creato una procedura per creare dinamicamente un No Op, tuttavia Nell'attuare non sono riuscito a trovare un modo per aggirare SQL rimanere bloccati in un ciclo di nidificazione.
  • BCP queryout
    • uscita non include le intestazioni

E così, dopo tanto colpire di testa e usare Google, sono caduto di nuovo a xpcmdshell. Lo script seguente (che trasformerò in una procedura) prende una istruzione exec SP e il database per eseguirlo, formatta un comando sqlquery xpcmdshell in un file, esegue il file e inserisce l'output di esso in una tabella temporanea, quindi estrae le intestazioni di colonna di tali risultati in un'altra tabella temporanea.

SET NOCOUNT ON 

DECLARE @TempCmdPath VARCHAR(MAX), 
     @ProcedureExec VARCHAR(MAX), 
     @DatabaseName VARCHAR(255) 

SELECT @TempCmdPath = 'C:\Temp\' --Make sure path ends with a '\' (or add logic to append if missing) 

SELECT @ProcedureExec = 'exec dbo.crp_rpt_GetCustomerDetails @ShowContacts=0,@CustomerName=''cust123%''' --Make sure to double up the single quotes (') 
SELECT @ProcedureExec = REPLACE(@ProcedureExec, '''', '''''') --Double the single quotes again (') for use in xpcmdshell sqlquery command 

SELECT @DatabaseName = 'CorpDB' 


IF OBJECT_ID('tempdb.dbo.#CmdOut','U') IS NOT NULL 
     DROP TABLE dbo.#CmdOut 

CREATE TABLE dbo.#CmdOut 
    (
     id INT IDENTITY(1,1), --Used in ROW_NUMBER() function to update rid 
     rid INT, --Actual number for use in WHILE loop 
     LineOut VARCHAR(MAX) 
    ) 


DECLARE @cmdshell VARCHAR(MAX) 

/* Create a file with the commands to run */ 
SELECT @cmdshell = 'exec master.dbo.xpcmdshell ''sqlcmd ' 
        + REPLACE('-q "PRINT '':error ' + @TempCmdPath + 'TempSqlCmdOut.txt'' ' --Set errors to be directed to a text file 
            + 'PRINT ''' + @ProcedureExec + '''" ' --Add additional PRINT statements to include more statements to run 
           + '-o "' + @TempCmdPath + 'TempSqlCmd.txt" ' --Specify where the file should output to 
           , '''', '''''') --Double up the single quotes (') /again/ for this statement 
        + '''' --Close the statement 

PRINT @cmdshell 
INSERT INTO dbo.#CmdOut (LineOut) 
     EXEC (@cmdshell) 


/* Execute the commands stored in the file we just created */ 
SELECT @cmdshell = 'exec master.dbo.xpcmdshell ''sqlcmd ' 
        + '-d ' + @DatabaseName + ' ' 
        + '-r 1 ' --Set any additional messsages to be treated as errors. This, combined with the ":error <path>\TempSqlCmdOut.txt" line above, will ensure that print statements are not returned in the output 
        + '-i "' + @TempCmdPath + 'TempSqlCmd.txt" ' 
        + '-s "," ' --Column Separator 
        + '''' --Close the statement 

PRINT @cmdshell 
INSERT INTO dbo.#CmdOut (LineOut) 
     EXEC (@cmdshell) 


/* Clean up. Delete the two temp files */ 
SELECT @cmdshell = 'exec master.dbo.xpcmdshell ''del "' + @TempCmdPath + 'TempSqlCmd.txt"''' 
PRINT @cmdshell 
INSERT INTO dbo.#CmdOut (LineOut) 
     EXEC (@cmdshell) 

SELECT @cmdshell = 'exec master.dbo.xpcmdshell ''del "' + @TempCmdPath + 'TempSqlCmdOut.txt"''' 
PRINT @cmdshell 
INSERT INTO dbo.#CmdOut (LineOut) 
     EXEC (@cmdshell) 



/* Clean up NULL rows then update the rid column's value */ 
DELETE dbo.#CmdOut 
WHERE LineOut IS NULL 

UPDATE co 
SET  rid = n.rid 
FROM dbo.#CmdOut co 
     INNER JOIN ( SELECT id, 
           ROW_NUMBER() OVER (ORDER BY id) AS [rid] 
         FROM dbo.#CmdOut 
        ) AS n ON co.id = n.id 


--SELECT * FROM dbo.#CmdOut 

--------------------------------------------------------------- 
--------------------------------------------------------------- 

IF OBJECT_ID('tempdb.dbo.#SPResultHeaders','U') IS NOT NULL 
     DROP TABLE dbo.#SPResultHeaders 

CREATE TABLE dbo.#SPResultHeaders 
    (
     id INT IDENTITY(1,1), 
     HeaderName VARCHAR(500) 
    ) 


DECLARE @LineCount INT, 
     @LineIndex INT, 
     @Delimiter VARCHAR(10), 
     @PrevDelimitCharIndex INT, 
     @NextDelimitCharIndex INT, 
     @LineText VARCHAR(MAX), 
     @EndOfLineText VARCHAR(MAX), 
     @FoundDivider BIT 

SELECT @Delimiter = ',', 
     @FoundDivider = 0 

SELECT @LineCount = COUNT(*), 
     @LineIndex = 1 
FROM dbo.#CmdOut 

/* Until we move through all of the output lines OR we run into the line between the headers and their data (divider).. */ 
WHILE (@LineIndex <= @LineCount 
     AND @FoundDivider = 0 
    ) 
    BEGIN 
     /* Reset DelimitCharIndex: */ 
     SELECT @PrevDelimitCharIndex = 0, 
       @NextDelimitCharIndex = 1 

     /* Until the Delimiter is not found.. */ 
     WHILE (@NextDelimitCharIndex <> 0 
       AND @FoundDivider = 0 
      ) 
      BEGIN 
       /* Search for the Delimiter starting after the last one's position */ 
       SELECT @NextDelimitCharIndex = CHARINDEX(@Delimiter, LineOut, @PrevDelimitCharIndex) 
       FROM dbo.#CmdOut 
       WHERE rid = @LineIndex 

       /* If another Delimiter is found on this line.. */ 
       IF (@NextDelimitCharIndex <> 0 OR @EndOfLineText IS NOT NULL) 
        BEGIN 
         /* Make sure we're don't have left overs from a previous line */ 
         IF (@EndOfLineText IS NOT NULL) 
          BEGIN 
           /* If we do, set the current string to the previous + the current */ 
           SELECT @LineText = @EndOfLineText + SUBSTRING(LineOut, @PrevDelimitCharIndex, (@NextDelimitCharIndex - @PrevDelimitCharIndex)) 
           FROM dbo.#CmdOut 
           WHERE rid = @LineIndex 

           /* Then clear out the left overs */ 
           SELECT @EndOfLineText = NULL 
          END 
         ELSE 
          BEGIN 
           /* Get the text between the previous delimiter and the next */ 
           SELECT @LineText = SUBSTRING(LineOut, @PrevDelimitCharIndex, (@NextDelimitCharIndex - @PrevDelimitCharIndex)) 
           FROM dbo.#CmdOut 
           WHERE rid = @LineIndex 
          END 

         /* After the column headers in the output it will have a divider consisting of hyphens (-) (split by whatever we specified for the -s argument of the sqlcmd) 
          Check to see if our text is purely hyphens. IF NOT, insert the text into our result table and increment Header Count by 1. IF SO, set the FoundDivider flag to 1. 
         */ 
         IF (LTRIM(RTRIM(REPLACE(@LineText, '-', ''))) <> '') 
          BEGIN 
           IF (CHARINDEX('-', @LineText) <> 0) 
            BEGIN 
             /* If there are more than three hyphens in a row, assume it's the divider and set @FoundDivider to 1 to exit while */ 
             IF (SUBSTRING(@LineText, CHARINDEX('-', @LineText), 3) = '---') 
               SELECT @FoundDivider = 1 
             ELSE 
              INSERT INTO dbo.#SPResultHeaders (HeaderName) 
                SELECT LTRIM(RTRIM(@LineText)) 
            END 
           ELSE 
            BEGIN 
             INSERT INTO dbo.#SPResultHeaders (HeaderName) 
               SELECT LTRIM(RTRIM(@LineText)) 
            END 
          END 
         ELSE 
          BEGIN 
           /* If there are more than three hyphens in a row, assume it's the divider and set @FoundDivider to 1 to exit while */ 
           IF (SUBSTRING(@LineText, CHARINDEX('-', @LineText), 3) = '---') 
             SELECT @FoundDivider = 1 
          END 
        END 
       /* If another Delimiter is NOT found on this line.. */ 
       ELSE 
        BEGIN 
         /* Move remainder of this line's text to @EndOfLineText ("left overs") for use in next itteration */ 
         SELECT @LineText = NULL, 
           @EndOfLineText = SUBSTRING(LineOut, @PrevDelimitCharIndex, (LEN(LineOut) + 1)) 
         FROM dbo.#CmdOut 
         WHERE rid = @LineIndex 
        END 

       /* Update previous Delimiter's position */ 
       SELECT @PrevDelimitCharIndex = @NextDelimitCharIndex + 1 
      END --WHILE (@NextDelimitCharIndex <> 0) 

     SELECT @LineIndex = @LineIndex + 1 
    END --WHILE (@LineIndex <= @LineCount) 


SELECT * 
FROM dbo.#SPResultHeaders 

Se si prevede di utilizzare questo codice, non dimenticate di fare una ricerca per sostituire xpcmdshell a XP (_) cmdshell

Spero che questo aiuti qualcuno! Non esitate a postare domande, commenti o suggerimenti che potreste avere.

+0

Attraverso altri test ho trovato un paio di bug con questo codice. Se qualcuno vuole usarlo, fammi sapere e posso pubblicare una versione aggiornata. –

1

Si utilizza una variabile della tabella Temp #T. Devi usare una tabella temporanea @T. Per quanto ne so, la variabile della tabella Temp non può essere utilizzata nell'ambiente di transazioni distribuite e inoltre, potrebbe non essere possibile accedere al TempDB nel server collegato.

Problemi correlati