2011-10-19 12 views
9

Moja Scenariusz

Pracuję nad bazą danych, która będzie zawierać wiele szczegółów z różnych procedur przechowywanych w różnych bazach na terenie całego serwera. Informacje, które próbuję teraz zebrać, brzmią: "Co to jest wynik SP?"Korzystanie OPENROWSET dynamicznie pobierać wyników SP przy SP zawiera # tabele tymczasowe

Podczas wyszukiwania stwierdziłem, że odpowiedź leży w OPENROWSET. Moje pierwsze testy zakończyły się sukcesem i wszystko wyglądało świetnie. Jednak po przetestowaniu go za pomocą live SPs natknąłem się na jeden poważny problem: nie działa dobrze z tabelami temp (#).

Na przykład:

Gdybym miał podjąć tę SP:

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

mogę łatwo wstawić wyjście do temp (##) tabeli z następującego kodu, a następnie kwerendy sysobjects tempdb i utworzyć listę kolumn i ich typy danych:

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 

Świetnie! Jednakże, jeśli SP był to zamiast:

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 

Kiedy wykonać ten sam kod OPENROWSET jak przed otrzymuję następujący błąd:

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.

Kiedy przyciąć dół kod OPENROWSET (usuwając dynamiczny rzeczy) to:

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

otrzymuję następujący (znacznie bardziej użyteczne) błąd:

Invalid object name '#T'.

To tam uderzyłem w ścianę. W moich poszukiwaniach wydaje mi się, że nie ma rozwiązania, ale nie mogłem się zmusić, żeby się jeszcze poddać.

A więc mam doprowadził do ..

Moje pytanie do was

Czy ktoś świadomy jakikolwiek sposób obejścia tego błędu? A może istnieje alternatywne rozwiązanie?

Proces ten nie będzie działał często, więc nie muszę się martwić zbytnio o wydajność rozwiązania.

Wszelkie dane wejściowe będą bardzo mile widziane.

Dzięki, Zok

PS: Przepraszam za formatowania. Nie bardzo zrozumiałem tagi językowe.

+0

Myślę, że mogłem znaleźć ołów, który wymaga użycia SET NOCOUNT ON. Dodając go do mojego manekina SP zadziałało, ale nie dla tego, którego faktycznie będę używał (który faktycznie już miał tę linię).Będę nadal bawiła się z tym i relacjonowała, co znajdę. –

+0

W tym samym przewodzie, o którym wspomniałem powyżej, musieli wprowadzić No Op do SP. Zastanawiałem się nad stworzeniem procedury pośredniej, która parsowałaby SP, z którego próbujemy zbierać szczegóły (poprzez syscomments), wyciągając definicję tabeli tymczasowej, aby dynamicznie tworzyć No Op, ale widzę wiele problemów, które byłyby trudne do pracy na około. Soooooo, wciąż jestem w tej samej łodzi. –

+0

Niesamowite wysiłki podjęte przez Ciebie .... dzięki –

Odpowiedz

16

Miałem to pytanie opublikowane na SQL Server Central, a niektóre odpowiedzi zwróciły mnie z powrotem do szukania odpowiedzi w OPENROWSET (i znajdowania tego). Jedna z osób skierowała mnie do sekcji this article w sprawie OPENQUERY. Stwierdza, że ​​w celu obejścia problemu z tabel tymczasowych po prostu dodać SET FMTONLY OFF do linii wykonanie swojej OPENQUERY rachunku/OPENROWSET tak:

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

Jednakże, jeżeli procedura nie SET NOCOUNT ON, ale wciąż wywołuje błąd. Miałem głupie nieporozumienie dotyczące SET NOCOUNT ON z tyłu głowy, które powstrzymało mnie od myślenia: "Hej, nie mogę po prostu dodać SET NOCOUNT ON do instrukcji execute OPENROWSET ??" Raz ktoś zapytał na to pytanie dla mnie na inny wątek to zrobiło wszystko zbyt wiele sensu =) Więc tutaj jest rozwiązanie Szukałem na całej długości:

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

To działa/DUŻO/wolniej niż rozwiązanie opublikowane przez Xpcmdshell. Jeśli potrzebujesz czegoś takiego, ale nie musisz znać typów danych kolumn, sugerowałbym zamiast tego użycie rozwiązania xpcmdshell. –

+0

Chcę ostrzec użytkowników przed używaniem 'SET FMTONLY OFF' w krytycznych przypadkach: pamiętaj, ustawienie 'SET FMTONLY OFF' powoduje DWUSTRONNE wykonywanie bieżących instrukcji! Jeśli SP wstawi trochę danych, możesz wpaść w kłopoty! Z opcją FMTONLY OFF SQL Server zapyta oświadczenie po raz pierwszy, aby uzyskać metadane. – xacinay

2

Okay ... Poddałem się i wróciłem do mojego starego przyjaciela xpcmdshell. W całej tej odpowiedzi i jej kodu podkreślenie (_) będzie implikowane dla xpcmdshell, ponieważ często nie mogę załadować stron zawierających pełną nazwę.

pierwsze, tutaj są tylko trzy rzeczy próbowałem, że nie działa (nie mogę sobie przypomnieć wszystkie innymi):

  • SET NOCOUNT NA
    • działa dla wszystkich SP bez tabel tymczasowych, ale jak większość z 2500 + - będę przeglądać ich wykorzystanie to nie jest możliwe.
  • No Op
    • stworzyłem procedurę do dynamicznego tworzenia żaden op, jednak po realizacji nie byłem w stanie znaleźć drogę SQL utknięcie w zagnieżdżania pętli.
  • bcp queryout
    • wyjściowy nie zawiera nagłówków

I tak, po wielu walnąć głową i Googling, jakie spadły z powrotem do xpcmdshell. Poniższy skrypt (który zamieniam w procedurę) pobiera instrukcję SP exec i bazę danych, aby uruchomić ją, formatuje polecenie sqlquery xpcmdshell do pliku, wykonuje plik i wstawia jego wynik do tabeli tymczasowej, następnie wyodrębnia nagłówki kolumn tych wyników w innej tabeli tymczasowej.

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 

Jeśli zamierzasz użyć tego kodu, nie zapomnij zrobić odnaleźć wymienić na xpcmdshell do xp (_) cmdshell

nadzieję, że to pomaga kogoś! Nie wahaj się zadać żadnych pytań, komentarzy lub sugestii.

+0

Poprzez więcej testów znalazłem kilka błędów z tym kodem. Jeśli ktoś chce z niego skorzystać, daj mi znać i mogę opublikować zaktualizowaną wersję. –

1

Używasz Zmienna tabeli temp #T. Musisz użyć tabeli tymczasowej @ T. Zgodnie z moim rozumieniem zmiennej tabeli Temp nie można używać w środowisku transakcji rozproszonych, a także, że może nie mieć dostępu do TempDB w połączonym serwerze.

Powiązane problemy