2011-10-19 62 views
9

Kịch bản của tôi

Tôi đang làm việc trên một cơ sở dữ liệu trong đó sẽ chứa nhiều chi tiết từ các Stored Procedure khác nhau trong cơ sở dữ liệu khác nhau trên toàn bộ máy chủ. Thông tin tôi đang cố gắng thu thập bây giờ là, "SP đầu ra là gì?"Sử dụng OPENROWSET để tự động lấy kết quả SP khi SP chứa # bảng tạm

Trong tìm kiếm, tôi đã tìm thấy câu trả lời nằm trong OPENROWSET. Thử nghiệm ban đầu của tôi đã thành công và mọi thứ đều tuyệt vời. Tuy nhiên, khi thử nghiệm nó với các SP trực tiếp, tôi gặp phải một vấn đề lớn: Nó không chơi tốt với các bảng tạm thời (#).

Ví dụ:

Nếu tôi được đưa SP này:

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

tôi có thể dễ dàng chèn kết quả vào một temp (##) bảng với đoạn mã sau, sau đó truy vấn sysobjects tempdb và tạo danh sách các cột và loại dữ liệu của chúng:

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 

Tuyệt vời! Tuy nhiên, nếu nhà cung cấp vẫn này thay vì:

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 

Khi tôi thực hiện cùng OPENROWSET mã như trước khi tôi nhận được lỗi sau:

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.

Khi tôi cắt xuống mã OPENROWSET (bằng cách loại bỏ các động thứ) như sau:

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

tôi nhận được (nhiều hữu ích hơn) lỗi sau:

Invalid object name '#T'.

Đó là nơi tôi chạm vào tường. Trong tìm kiếm của tôi có vẻ như không có giải pháp, nhưng tôi không thể tự mình từ bỏ nó.

Và vì vậy tôi dẫn đến ..

Câu hỏi của tôi để bạn

Có ai biết về bất kỳ cách nào có thể phá vỡ được lỗi này? Hoặc là có thể có một giải pháp thay thế?

Quá trình này sẽ không chạy thường xuyên vì vậy tôi không cần phải lo lắng quá nhiều về hiệu quả của giải pháp.

Bất kỳ đầu vào nào cũng sẽ được đánh giá cao.

Xin cảm ơn, Zok

PS: Xin lỗi về định dạng. Tôi đã không hoàn toàn tìm ra các thẻ ngôn ngữ.

+0

Tôi nghĩ rằng tôi có thể đã tìm thấy khách hàng tiềm năng liên quan đến việc sử dụng SET NOCOUNT ON. Khi thêm nó vào SP giả của tôi nó đã làm việc, nhưng không phải cho một mà tôi thực sự sẽ được sử dụng (mà thực sự đã có dòng đó).Tôi sẽ tiếp tục đùa giỡn với nó và báo cáo về những gì tôi tìm thấy. –

+0

Trong cùng một khách hàng tiềm năng tôi đã đề cập ở trên, họ đã giới thiệu một No Op cho SP. Tôi đã xem xét việc tạo một thủ tục trung gian phân tích SP mà chúng tôi đang cố gắng thu thập chi tiết từ (thông qua syscomments), rút ​​ra định nghĩa về bảng tạm thời để tự động tạo ra No Op, nhưng tôi thấy rất nhiều vấn đề khó làm việc xung quanh. Soooooo, tôi vẫn ở cùng một chiếc thuyền. –

+0

Nỗ lực tuyệt vời do bạn gây ra .... cảm ơn –

Trả lời

16

Tôi cũng có câu hỏi này được đăng trên Trung tâm máy chủ SQL và một số câu trả lời đã chuyển tôi trở lại để tìm câu trả lời trong OPENROWSET (và tìm kiếm nó). Một trong những người đã chuyển tôi sang phần của this article trên OPENQUERY. Nó khẳng định rằng để làm việc xung quanh vấn đề với các bảng tạm thời bạn chỉ cần thêm SET FMTONLY OFF vào dòng thực của tuyên bố/OPENROWSET OPENQUERY bạn như vậy:

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

Tuy nhiên, nếu các thủ tục không có SET NOCOUNT ON được chỉ định, nó vẫn gây ra lỗi. Tôi đã có một sự hiểu lầm ngớ ngẩn về SET NOCOUNT ON ở phía sau đầu của tôi khiến tôi không nghĩ, "Này, tôi không thể thêm SET NOCOUNT ON vào câu lệnh thực thi của OPENROWSET?" Khi ai đó hỏi rằng câu hỏi cho tôi trên thread khác nó làm cho tất cả các cảm giác quá nhiều =) Vì vậy, đây là giải pháp tôi đã tìm kiếm tất cả cùng:

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

Điều này chạy/MUCH/chậm hơn so với giải pháp xpcmdshell tôi đã đăng. Nếu bạn cần phải làm một cái gì đó như thế này nhưng không cần phải biết các kiểu dữ liệu của các cột tôi sẽ đề nghị sử dụng giải pháp xpcmdshell thay thế. –

+0

Tôi muốn cảnh báo người dùng sử dụng chức năng 'SET FMTONLY OFF' trong bất kỳ trường hợp quan trọng nào: hãy nhớ, cài đặt 'SET FMTONLY OFF' khiến DOUBLE thực hiện các câu lệnh đang diễn ra! Nếu SP của bạn chèn một số dữ liệu, bạn có thể gặp rắc rối! Với tùy chọn FMTONLY OFF, SQL Server sẽ truy vấn lần đầu tiên để nhận dữ liệu metdata. – xacinay

2

OK .. Tôi đã từ bỏ và quay trở lại người bạn cũ của tôi xpcmdshell. Trong suốt phản hồi này và mã của nó, dấu gạch dưới (_) sẽ được ngụ ý cho xpcmdshell vì tôi thường không thể tải các trang có chứa tên đầy đủ.

Thứ nhất, đây chỉ là ba trong những điều tôi đã cố gắng mà không làm việc (Tôi không thể nhớ lại tất cả những người khác):

  • SET NOCOUNT ON
    • trình cho bất kỳ SP mà không có bảng tạm thời, nhưng như hầu hết các 2500 + - Tôi sẽ được xem xét thông qua sử dụng chúng, điều này là không khả thi.
  • Không Op
    • Tôi tạo ra một quy trình để tự động tạo ra một Không Op, tuy nhiên khi thực hiện tôi đã không thể tìm thấy một con đường xung quanh SQL bị mắc kẹt trong một vòng lặp làm tổ.
  • BCP queryout
    • Output không bao gồm tiêu đề

Và như vậy, sau nhiều bashing đầu và Googling, tôi đã rơi trở lại xpcmdshell. Kịch bản sau đây (mà tôi sẽ chuyển thành một thủ tục) có một câu lệnh SP exec và cơ sở dữ liệu để chạy nó dưới, định dạng lệnh xpcmdshell sqlquery thành một tệp, thực thi tệp và chèn đầu ra của nó vào bảng tạm thời, sau đó chiết xuất các tiêu đề cột của các kết quả đó vào một bảng tạm thời khác.

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 

Nếu bạn có kế hoạch sử dụng mã này, đừng quên để làm một tìm thay thế cho xpcmdshell để xp (_) cmdshell

Hy vọng điều này sẽ giúp người! Vui lòng không ngần ngại đăng bất kỳ câu hỏi, nhận xét hoặc đề xuất nào mà bạn có thể có.

+0

Thông qua thử nghiệm khác, tôi đã tìm thấy một vài lỗi với mã này. Nếu bất cứ ai muốn sử dụng nó, hãy cho tôi biết và tôi có thể đăng một phiên bản cập nhật. –

1

Bạn đang sử dụng biến bảng Temp #T. Bạn phải sử dụng một bảng tạm thời @T. Theo hiểu biết của tôi, biến bảng Temp không thể được sử dụng trong môi trường giao dịch phân tán và cũng có thể, rằng bạn có thể không có quyền truy cập vào TempDB trong máy chủ được liên kết.