10

私のシナリオ

私は、サーバー全体のさまざまなデータベースのさまざまなストアド プロシージャからの多くの詳細を含むデータベースに取り組んでいます。今収集しようとしている情報は、「SP は何を出力するか」です。

検索すると、答えは OPENROWSET にあることがわかりました。私の最初のテストは成功し、すべてが素晴らしく見えました。ただし、ライブ SP でテストしたところ、1 つの大きな問題に遭遇しました。一時 (#) テーブルではうまく機能しません。

例えば:

このSPを取るとしたら:

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

次のコードを使用して、一時 (##) テーブルに出力を簡単に挿入し、tempdb の sysobjects をクエリして、列とそのデータ型のリストを生成できます。

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

すごい!ただし、SP が代わりにこれだった場合:

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

以前と同じOPENROWSETコードを実行すると、次のエラーが表示されます。

オブジェクト "SET FMTONLY OFF exec DatabaseName.dbo.zzTempSP @A=1,@B=2" を処理できません。リンク サーバー "(null)" の OLE DB プロバイダー "SQLNCLI10" は、オブジェクトに列がないか、現在のユーザーがそのオブジェクトに対するアクセス許可を持っていないことを示しています。

OPENROWSET コードを (動的なものを削除して) 切り詰めると、次のようになります。

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

次の (より便利な) エラーが表示されます。

オブジェクト名 '#T' が無効です。

ここで壁にぶち当たりました。私の検索では解決策がないように見えますが、まだあきらめることはできませんでした.

そして、私は導かれます..

あなたへの私の質問

このエラーを回避する方法を知っている人はいますか? または、代替ソリューションがある可能性がありますか?

このプロセスは頻繁に実行されるわけではないので、ソリューションの効率についてあまり心配する必要はありません。

任意の入力をいただければ幸いです。

ありがとう、ゾック

PS: フォーマットについて申し訳ありません。言語タグがよくわかりませんでした。

4

3 に答える 3

19

私はこの質問を SQL Server Central にも投稿しましたが、回答によっては OPENROWSET 内で回答を探す (そして見つけた) ことに戻りました。そのうちの 1 人が、この記事の OPENQUERY に関するセクションを紹介してくれました。一時テーブルの問題を回避するには、次のように OPENQUERY/OPENROWSET ステートメントの実行行に SET FMTONLY OFF を追加するだけです。

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

ただし、プロシージャに SET NOCOUNT ON が指定されていない場合でも、エラーが発生します。頭の片隅に SET NOCOUNT ON に関するばかげた誤解があり、「OPENROWSET の execute ステートメントに SET NOCOUNT ON を追加することはできませんか??」と考えることができませんでした。誰かが他のスレッドで私にその質問したとき、それはあまりにも理にかなっています =) だから、ここに私がずっと探していた解決策があります:

SELECT  *
FROM    OPENROWSET( 'SQLNCLI',
                    'Server=SERVERNAME;Trusted_Connection=yes;',
                    'SET FMTONLY OFF; SET NOCOUNT ON; exec DatabaseName.dbo.zzTempSP @A=1,@B=2'
                  )
于 2011-10-25T16:01:46.497 に答える
2

わかりました.. あきらめて、旧友の xpcmdshell に戻りました。フルネームを含むページを読み込めないことが多いため、この応答とそのコード全体で、アンダースコア (_) が xpcmdshell に暗示されます。

まず、私が試した中でうまくいかなかったことが 3 つだけあります (他のすべてを思い出すことはできません)。

  • NOCOUNTをオンに設定
    • 一時テーブルのない任意の SP で機能しますが、2500 以上のほとんどがそれらを利用するために調べているため、これは実現不可能です。
  • ノーオペレーション
    • No Op を動的に作成する手順を作成しましたが、実装時に、SQL がネスト ループに陥るのを回避する方法を見つけることができませんでした。
  • bcp クエリアウト
    • 出力にヘッダーが含まれていない

それで、頭をぶつけてグーグルで調べた後、xpcmdshellに戻りました。次のスクリプト (プロシージャに変換します) は、SP exec ステートメントとそれを実行するデータベースを取得し、xpcmdshell sqlquery コマンドをファイルにフォーマットし、ファイルを実行して、その出力を一時テーブルに挿入します。次に、これらの結果の列ヘッダーを別の一時テーブルに抽出します。

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

このコードを使用する場合は、xpcmdshell を xp(_)cmdshell に検索置換することを忘れないでください。

これが誰かを助けることを願っています! ご質問、ご意見、ご提案がありましたら、遠慮なく投稿してください。

于 2011-10-21T21:48:01.777 に答える
1

一時テーブル変数 #T を使用しています。一時テーブル @T を使用する必要があります。私の理解では、一時テーブル変数は分散トランザクション環境では使用できません。また、リンク サーバーの TempDB にアクセスできない可能性があります。

于 2015-01-13T09:02:08.573 に答える