ユーザー定義のテーブルタイプを含むsp_executesqlが正しく動作しない


11

問題sp_executesqlへのパラメーターとしてユーザー定義のテーブルタイプに既知の問題がありますか?回答-いいえ、私はばかです。

スクリプトを設定する

このスクリプトは、テーブル、プロシージャ、ユーザー定義のテーブルタイプをそれぞれ1つ作成します(制限付きのSQL Server 2008以降のみ)。

  • ヒープの目的は、はい、データがプロシージャに入ったという監査を提供することです。制約はなく、データの挿入を妨げるものはありません。

  • このプロシージャは、ユーザー定義のテーブルタイプをパラメータとして受け取ります。プロシージャが行うことはすべて、テーブルに挿入することです。

  • ユーザー定義のテーブルタイプも同様に単純で、単一の列です

私は以下に対して実行しました、11.0.1750.32 (X64) そして、10.0.4064.0 (X64)はい、私はボックスにパッチを当てることができることを知っています、それを制御しません。

-- this table record that something happened
CREATE TABLE dbo.UDTT_holder
(
    ServerName varchar(200)
,   insert_time datetime default(current_timestamp)
)
GO

-- user defined table type transport mechanism
CREATE TYPE dbo.UDTT
AS TABLE
(
    ServerName varchar(200)
)
GO

-- stored procedure to reproduce issue
CREATE PROCEDURE dbo.Repro
(
    @MetricData dbo.UDTT READONLY
)
AS
BEGIN
    SET NOCOUNT ON

    INSERT INTO dbo.UDTT_holder 
    (ServerName)
    SELECT MD.* FROM @MetricData MD
END
GO

問題の再現

このスクリプトは問題を示しており、実行に5秒かかるはずです。ユーザー定義のテーブルタイプのインスタンスを2つ作成し、それらをに渡す2つの異なる方法を試しsp_executesqlます。最初の呼び出しのパラメーターマッピングは、SQLプロファイラーからキャプチャしたものを模倣しています。次に、sp_executesqlラッパーなしでプロシージャを呼び出します。

SET NOCOUNT ON
DECLARE 
    @p3 dbo.UDTT
,   @MetricData dbo.UDTT
INSERT INTO @p3 VALUES(N'SQLB\SQLB')
INSERT INTO @MetricData VALUES(N'SQLC\SQLC')

-- nothing up my sleeve
SELECT * FROM dbo.UDTT_holder

SELECT CONVERT(varchar(24), current_timestamp, 121) + ' Firing sp_executesql' AS commentary
-- This does nothing
EXECUTE sp_executesql N'dbo.Repro',N'@MetricData dbo.UDTT READONLY',@MetricData=@p3
-- makes no matter if we're mapping variables
EXECUTE sp_executesql N'dbo.Repro',N'@MetricData dbo.UDTT READONLY',@MetricData

-- Five second delay
waitfor delay '00:00:05'
SELECT CONVERT(varchar(24), current_timestamp, 121) + ' Firing proc' AS commentary
-- this does
EXECUTE dbo.Repro @p3

-- Should only see the latter timestamp
SELECT * FROM dbo.UDTT_holder

GO

結果:以下は私の結果です。テーブルは最初は空です。現在の時刻を出力し、を2回呼び出しますsp_executesql。5秒待って現在の時刻を出力し、続いてストアドプロシージャ自体を呼び出して、最後に監査テーブルをダンプします。

タイムスタンプからわかるように、Bレコードのレコードは、ストレートストアドプロシージャの呼び出しに対応しています。また、SQLCレコードはありません。

ServerName                                       insert_time
------------------------------------------------------------------------

commentary
-------------------------------------------------
2012-02-02 13:09:05.973 Firing sp_executesql

commentary
-------------------------------------------------
2012-02-02 13:09:10.983 Firing proc

ServerName                                       insert_time
------------------------------------------------------------------------
SQLB\SQLB                                        2012-02-02 13:09:10.983

分解スクリプト

このスクリプトは、正しい順序でオブジェクトを削除します(プロシージャの参照が削除されるまで、型を削除することはできません)

-- cleanup
DROP TABLE dbo.UDTT_holder
DROP PROCEDURE dbo.Repro
DROP TYPE dbo.UDTT
GO

なぜこれが重要なのか

コードはばかげているように見えますが、sp_executeの使用とprocへの「まっすぐな」呼び出しをあまり制御できません。コールチェーンの上位では、ADO.NETライブラリを使用して、以前と同じようにTVPを渡します。パラメーターのタイプはSystem.Data.SqlDbType.Structuredに正しく設定され、CommandTypeはSystem.Data.CommandType.StoredProcedureに設定されます。

なぜ私は初心者です(編集)

RobとMartin Smithは私が見ていなかったものを見ました-sp_executesqlに渡されるステートメントは@MetricDataパラメーターを使用しませんでした。渡されていない/マップされたパラメーターは使用されません。

私は作業中のC#のテーブル値パラメーターコードをPowerShellに変換していましたが、コンパイルされたため、問題のあるコードになりませんでした。それ以外だった。この質問を書いているときに、を設定していないことに気付いCommandTypeStoredProcedureので、その行を追加してコードを再実行しましたが、プロファイラーのトレースは変更されなかったので、問題ではないと思いました。

面白い話- 更新されたファイルを保存しなくも、実行しても違いはありません。私は今朝入って、それを(保存後に)再実行し、ADO.NETはそれを適切に翻訳して翻訳しましEXEC dbo.Repro @MetricData=@p3た。

回答:


10

sp_executesqlアドホックT-SQLを実行するためのものです。だからあなたは試すべきです:

EXECUTE sp_executesql N'exec dbo.Repro @MetricData',N'@MetricData dbo.UDTT READONLY',@MetricData=@p3

1
+1現在、OP内のスクリプトはステートメントのみを含みdbo.Repro、渡されたパラメーターをまったく使用しません。
マーティンスミス

はい、Robのコメントを見ました。そこでEXEC部分は必要ありませんでしたが、呼び出しでパラメーターが使用されなかった理由は、パラメーターがスクリプトで参照されていなかったことが理にかなっています。私の愚かさを反映するようにチケットを更新する
billinkc

@billinkc-ああ、私はより詳細な説明を含む回答を追加した後、あなたのコメントを見ました。その答えを少し削除します。
マーティン・スミス

正しい。ado.netからこれを行っているとは言わなかった。sp_executesqlは、.StoredProcedureではなく、.CommandTextと同等です。
Rob Farley

3

私はこの質問を削除したくなりましたが、他の誰かが同様の状況に遭遇するかもしれないと考えました。

根本的な原因は$updateCommand、CommandTypeを設定していなかったことです。デフォルト値はTextなので、ストアドプロシージャが正しく呼び出されていました。私のパラメーターは作成されて渡されていましたが、他の回答に記載されているように、パラメーターが使用可能であるという理由だけでそれらが使用されているわけではありません。

誰かが私の間違いに興味を持った場合のコード

    $updateConnection = New-Object System.Data.SqlClient.SqlConnection("Data Source=localhost\localsqla;Initial Catalog=baseline;Integrated Security=SSPI;")
    $updateConnection.Open()

    $updateCommand = New-Object System.Data.SqlClient.SqlCommand($updateQuery)
    $updateCommand.Connection = $updateConnection

    # This is what I was missing
    $updateCommand.CommandType = [System.Data.CommandType]::StoredProcedure
    #$updateCommand.CommandType = [System.Data.CommandType]::Text
    #$updateCommand.CommandType = [System.Data.CommandType]::TableDirect

    $DataTransferFormat = $updateCommand.Parameters.AddWithValue("@MetricData", $dataTable)
    $DataTransferFormat.SqlDbType = [System.Data.SqlDbType]::Structured
    $DataTransferFormat.TypeName = $udtt
    $results = $updateCommand.ExecuteNonQuery()

CommandTypeにTextの値を指定して実行するには、値$updateQueryを "EXEC dbo.Repro @MetricData" に変更する@rob_farleyのソリューションが必要です。その時点で、sp_executesqlは値を正しくマップし、寿命は良好です。

ドキュメントで示されているように、CommandTypeofでの実行TableDirectはSqlClientデータプロバイダーではサポートされていません。

の使用は、ラッパーなしのストアドプロシージャの直接呼び出しCommandTypeStoredProcedure変換され、sp_executesqlうまく機能します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.