2011-10-19 2 views
9

Mon scénario

Je travaille sur une base de données qui contiendra de nombreux détails de diverses procédures stockées dans différentes bases de données sur l'ensemble du serveur. L'information que je tente de rassembler maintenant est: «Que sort le SP?En utilisant OPENROWSET pour récupérer dynamiquement les résultats de la SP lorsque SP contient des tables temporaires n

En cherchant, j'ai trouvé que la réponse se trouve dans OPENROWSET. Mes tests initiaux ont été réussis et tout semblait bien. Cependant, après l'avoir testé avec des SPs actifs, j'ai rencontré un problème majeur: il ne fonctionne pas bien avec les tables temp (#).

Par exemple:

Si je devais prendre ce SP:

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

je peux facilement insérer la sortie dans une température (##) table avec le code suivant, puis interroger les sysobjects de tempdb et produire une liste des colonnes et de leurs types de données:

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 

Great! Cependant, si le SP était plutôt:

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 

Lorsque j'exécute le même code OPENROWSET comme avant que je reçois l'erreur suivante:

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.

Quand je couperons le code OPENROWSET (en supprimant la dynamique choses) à ceci:

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

Je reçois l'erreur suivante (beaucoup plus utile):

Invalid object name '#T'.

C'est où je frappe le mur. Dans mes recherches, il semble qu'il n'y ait pas de solution, mais je ne pouvais pas me résoudre à abandonner tout de suite.

Et donc je suis conduit à ..

Ma question

Quelqu'un est-il au courant de toute façon possible de contourner cette erreur? Ou y a-t-il éventuellement une solution alternative?

Ce processus ne sera pas exécuté fréquemment, donc je n'ai pas besoin de trop m'inquiéter de l'efficacité de la solution.

Toute contribution serait grandement appréciée.

Merci, Zok

PS: Désolé pour la mise en forme. Je n'ai pas bien compris les étiquettes de langue.

+0

Je pense que j'ai peut-être trouvé une piste qui implique l'utilisation de SET NOCOUNT ON. Lorsque je l'ai ajouté à mon SP factice, cela a fonctionné, mais pas pour celui que j'utiliserai réellement (qui avait déjà cette ligne).Je vais continuer à jouer avec et faire un rapport sur ce que je trouve. –

+0

Dans la même avance que j'ai mentionné ci-dessus, ils ont dû introduire un No Op à la SP. J'ai envisagé de créer une procédure intermédiaire qui analyserait le SP dont nous essayons de rassembler les détails (par le biais de syscomments) en extrayant la définition de la table temporaire pour créer dynamiquement un No Op, mais je vois beaucoup de problèmes qui seraient difficiles à travailler autour. Soooooo, je suis toujours dans le même bateau. –

+0

Super effort mis en place par vous .... merci –

Répondre

16

J'ai également posé cette question sur SQL Server Central et certaines réponses m'ont ramené à la recherche d'une réponse dans OPENROWSET (et à la trouver). Une des personnes m'a tourné vers la section this article sur OPENQUERY. Il précise que, pour contourner le problème avec des tables temporaires vous ajoutez simplement SET FMTONLY OFF à la ligne exécuter de votre instruction OPENQUERY/OPENROWSET comme ceci:

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

Cependant, si la procédure ne avoir SET NOCOUNT ON spécifié, il soulève toujours une erreur. J'ai eu un malentendu stupide à propos de SET NOCOUNT ON à l'arrière de ma tête qui m'a empêché de penser, "Hey, je ne peux pas simplement ajouter SET NOCOUNT ON à la déclaration d'exécution de OPENROWSET ??" Une fois que quelqu'un a posé cette question pour moi sur l'autre fil, il fait trop beaucoup de sens =) Donc, voici la solution que j'ai cherché tout le long:

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

Cela fonctionne/BEAUCOUP/plus lent que la solution xpcmdshell que j'ai posté. Si vous avez besoin de faire quelque chose comme ça, mais que vous n'avez pas besoin de connaître les types de données des colonnes, je vous suggère d'utiliser la solution xpcmdshell à la place. –

+0

Je souhaite avertir les utilisateurs d'utiliser 'SET FMTONLY OFF' dans tous les cas critiques: rappelez-vous, le réglage 'SET FMTONLY OFF' entraîne DOUBLE exécution des instructions en cours! Si votre SP insère des données, vous risquez de rencontrer des problèmes! Avec l'option FMTONLY OFF, SQL Server interrogera la première fois l'instruction pour obtenir les métadonnées. – xacinay

2

Okay .. J'ai abandonné et suis retourné à mon vieil ami xpcmdshell. Tout au long de cette réponse et son code le trait de soulignement (_) sera implicite pour xpcmdshell car je ne peux souvent pas charger les pages contenant le nom complet.

Tout d'abord, voici trois des choses que j'ai essayé cela ne fonctionne pas (je ne me souviens pas tous les autres):

  • SET NOCOUNT SUR
    • Works pour tout SP sans tables temporaires, mais comme la plupart des 2500 + - je vais chercher à les utiliser ce n'est pas faisable.
  • Non Op
    • J'ai créé une procédure pour créer dynamiquement un non Op, mais sur la mise en œuvre, je ne pouvais pas trouver un moyen de contourner SQL rester coincé dans une boucle d'imbrication.
  • bcp queryout
    • sortie ne comprend pas les en-têtes

Et, après dénigrement beaucoup de tête et recherche sur Google, je suis retombé à xpcmdshell. Le script suivant (que je vais transformer en procédure) prend une instruction exec SP et la base de données pour l'exécuter, formate une commande xpcmdshell sqlquery dans un fichier, exécute le fichier et insère la sortie de celui-ci dans une table temporaire, extrait ensuite les en-têtes de colonne de ces résultats dans une autre table temporaire.

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 

Si vous prévoyez d'utiliser ce code, ne pas oublier de faire une trouvaille pour remplacer xpcmdshell à xp (_) cmdshell

Hope this helps quelqu'un! N'hésitez pas à poster toute question, commentaire ou suggestion que vous pourriez avoir.

+0

Grâce à plus de tests, j'ai trouvé quelques bugs avec ce code. Si quelqu'un veut l'utiliser, faites le moi savoir et je peux poster une version mise à jour. –

1

Vous utilisez une variable de table Temp #T. Vous devez utiliser une table temporaire @T. D'après ce que je comprends, la variable de table Temp ne peut pas être utilisée dans un environnement de transaction distribuée et aussi, que vous pourriez ne pas avoir accès à la TempDB dans le serveur lié.