web-dev-qa-db-ja.com

ストアドプロシージャの結果セットの列定義を取得する

私はSQL Server 2008でストアドプロシージャを使用していますが、INSERT INTOデータを処理するために事前定義された一時テーブル。私が一時テーブルを定義する方法を理解する方法を除いて、それは大丈夫です、私がその定義をリストしてコードを読む以外のストアドプロシージャを書いたものではない場合は?

たとえば、「EXEC sp_stored_procedure」の一時テーブルはどのようになりますか?これは単純なストアドプロシージャであり、おそらくデータ型を推測できますが、プロシージャの実行から返される列の型と長さを読み取る方法があるに違いないようです。

34
cjbarth

したがって、tempdbにストアドプロシージャがあるとします。

USE tempdb;
GO

CREATE PROCEDURE dbo.my_procedure
AS
BEGIN
    SET NOCOUNT ON;

    SELECT foo = 1, bar = 'tooth';
END
GO

ストアドプロシージャが出力するメタデータを決定するには、かなり複雑な方法があります。プロシージャには単一の結果セットのみを出力でき、正確に判断できない場合はデータ型について最良の推測が行われるなど、いくつかの注意事項があります。 OPENQUERYを使用し、'DATA ACCESS'プロパティをtrueに設定したループバックリンクサーバーを使用する必要があります。 sys.serversをチェックして、有効なサーバーが既にあるかどうかを確認できますが、手動でloopbackと呼ばれるサーバーを作成してみましょう。

EXEC master..sp_addlinkedserver 
    @server = 'loopback',  
    @srvproduct = '',
    @provider = 'SQLNCLI',
    @datasrc = @@SERVERNAME;

EXEC master..sp_serveroption 
    @server = 'loopback', 
    @optname = 'DATA ACCESS',
    @optvalue = 'TRUE';

これをリンクサーバーとしてクエリできるようになったので、通常のSELECTとしてクエリの結果(ストアドプロシージャコールを含む)を使用できます。そのため、これを行うことができます(データベースプレフィックスが重要です、そうでない場合はエラー11529および2812が表示されます):

SELECT * FROM OPENQUERY(loopback, 'EXEC tempdb.dbo.my_procedure;');

SELECT *を実行できる場合は、SELECT * INTOも実行できます。

SELECT * INTO #tmp FROM OPENQUERY(loopback, 'EXEC tempdb.dbo.my_procedure;');

そして、その#tmpテーブルが存在すると、次のように言ってメタデータを決定できます(SQL Server 2005以降を想定)。

SELECT c.name, [type] = t.name, c.max_length, c.[precision], c.scale
  FROM sys.columns AS c
  INNER JOIN sys.types AS t
  ON c.system_type_id = t.system_type_id
  AND c.user_type_id = t.user_type_id
  WHERE c.[object_id] = OBJECT_ID('tempdb..#tmp');

(SQL Server 2000を使用している場合は、syscolumnsで同様のことができますが、同等のクエリを検証するのに便利な2000インスタンスはありません。)

結果:

name      type    max_length precision scale
--------- ------- ---------- --------- -----
foo       int              4        10     0
bar       varchar          5         0     0

デナリでは、これははるかに簡単になります。繰り返しますが、最初の結果セットにはまだ制限がありますが、リンクサーバーをセットアップしてそれらすべてのフープをジャンプする必要はありません。あなたはただ言うことができます:

DECLARE @sql NVARCHAR(MAX) = N'EXEC tempdb.dbo.my_procedure;';

SELECT name, system_type_name
    FROM sys.dm_exec_describe_first_result_set(@sql, NULL, 1);

結果:

name      system_type_name
--------- ----------------
foo       int             
bar       varchar(5)      

デナリまでは、袖をまくり、自分でデータ型を把握する方が簡単だと思います。上記の手順を実行するのが退屈だからというだけでなく、エンジンが行うデータ型の推定は実行時間に基づいているため、エンジンよりもはるかに正確な(または少なくともより正確な)推定を行う可能性が高いためです可能な値のドメインに関する外部知識なしで出力。この要素はデナリでも同様ですので、新しいメタデータ検出機能がすべてであるという印象を受けないようにしてください。

ああ、その他のOPENQUERYの潜在的な落とし穴については、Erland Sommarskogの記事をご覧ください

http://www.sommarskog.se/share_data.html#OPENQUERY

52
Aaron Bertrand

より洗練されていない方法(場合によっては十分かもしれません):元のSPを編集し、最後のSELECTの後、FROM句の前にINSERT INTO tmpTableを追加してSP結果をtmpTableに保存します。

実際のデータを取得するために、できれば意味のあるパラメーターを使用して、変更したSPを実行します。プロシージャの元のコードを復元します。

これで、SQL Server Management StudioからtmpTableのスクリプトを取得するか、sys.columnsをクエリしてフィールドの説明を取得できます。

7
Luca

SQL 2012には、これを支援する新しいSPがあるようです。

exec sp_describe_first_result_set N'PROC_NAME'

https://docs.Microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-describe-first-result-set-transact-sql

6
T Brown

これが私が書いたコードです。アイデアは(他の誰かが述べたように)SPコードを取得し、修正して実行することです。しかし、私のコードは元のSPを変更しません。

最初のステップは、SPの定義を取得し、「作成」部分を取り除き、パラメーター宣言がある場合は「AS」を取り除きます。

Declare @SPName varchar(250)
Set nocount on

Declare @SQL Varchar(max), @SQLReverse Varchar(MAX), @StartPos int, @LastParameterName varchar(250) = '', @TableName varchar(36) = 'A' + REPLACE(CONVERT(varchar(36), NewID()), '-', '')

Select * INTO #Temp from INFORMATION_SCHEMA.PARAMETERS where SPECIFIC_NAME = 'ADMIN_Sync_CompareDataForSync'

if @@ROWCOUNT > 0
    BEGIN
        Select @SQL = REPLACE(ROUTINE_DEFINITION, 'CREATE PROCEDURE [' + ROUTINE_SCHEMA + '].[' + ROUTINE_NAME + ']', 'Declare') 
        from INFORMATION_SCHEMA.ROUTINES 
        where ROUTINE_NAME = @SPName

        Select @LastParameterName = PARAMETER_NAME + ' ' + DATA_TYPE + 
            CASE WHEN CHARACTER_MAXIMUM_LENGTH is not null THEN '(' + 
                CASE WHEN CHARACTER_MAXIMUM_LENGTH = -1 THEN 'MAX' ELSE CONVERT(varchar,CHARACTER_MAXIMUM_LENGTH) END + ')' ELSE '' END 
        from #Temp 
        WHERE ORDINAL_POSITION = 
            (Select MAX(ORDINAL_POSITION) 
            From #Temp)

        Select @StartPos = CHARINDEX(@LastParameterName, REPLACE(@SQL, '  ', ' '), 1) + LEN(@LastParameterName)
    END
else
    Select @SQL = REPLACE(ROUTINE_DEFINITION, 'CREATE PROCEDURE [' + ROUTINE_SCHEMA + '].[' + ROUTINE_NAME + ']', '') from INFORMATION_SCHEMA.ROUTINES where ROUTINE_NAME = @SPName

DROP TABLE #Temp

Select @StartPos = CHARINDEX('AS', UPPER(@SQL), @StartPos)

Select @SQL = STUFF(@SQL, @StartPos, 2, '')

(一意の識別子に基づいた新しいテーブル名の作成に注意してください)これで、結果セットを返す選択を行うコードであると仮定して、コード内の最後の「From」ワードを見つけます。

Select @SQLReverse = REVERSE(@SQL)

Select @StartPos = CHARINDEX('MORF', UPPER(@SQLReverse), 1)

コードを変更して、結果セットをテーブル(uniqueidentifierに基づくテーブル)に選択します

Select @StartPos = LEN(@SQL) - @StartPos - 2

Select @SQL = STUFF(@SQL, @StartPos, 5, ' INTO ' + @TableName + ' FROM ')

EXEC (@SQL)

結果セットはテーブルになりました。テーブルが空かどうかは関係ありません!

テーブルの構造を取得できます

Select * from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @TableName

これで魔法をかけることができます

その一意のテーブルを削除することを忘れないでください

Select @SQL = 'drop table ' + @TableName

Exec (@SQL)

お役に立てれば!

6
Johan Smith