WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

パラメータ化クエリと sp_executesql 変換

by WebSurfer 11. December 2019 16:05

SqlParameter クラスを利用してクエリをパラメータ化すると sp_executesql に変換されて SQL Server で実行されるのですが、変換によって予期せぬ問題が出ることがあるという話を書きます。

sp_executesql に変換

元の話は MSDN Forum のスレッド「パラメータを利用したLikeで欲しい結果が得られない 」です。

具体的にどういうことかは以下の通りです。

SQL Server のデータベースに以下の画像の通り tbl_Item テーブルがあり、Item_CD 列で LIKE 句による検索をかけるとします。型の nvarchar(6) を覚えておいてください。

tbl_Item テーブル

この tbl_Item テーブルをベースに、Visual Studio のデーターソース構成ウィザードで型付 DataSet + TableAdapter を作成します。Item_CD 列で検索できる TableAdapter のメソッドを追加します。その SELECT クエリは以下の画像の通りです。

TableAdapter のメソッド

上の画像の通り型付 DataSet + TableAdapter が作成できたら「データのプレビュー」画面を開きバラメータ ICD に %A00001% を代入して[プレビュー]ボタンをクリックします。3 レコードヒットすると期待しますが、結果は一件もヒットしません。

データのプレビュー

ICD に代入した %A00001% を %00001% に代えた場合は 3 件のレコードがヒットします。上に紹介した MSDN Forum のスレッドの最初の質問に書いてあった通りです。

データのプレビュー

何故 %A00001% では一件もヒットしないかですが、上に紹介した MSDN Forum のスレッドで質問者さんがアップしてくれたプロファイラの画像を見ると分かるので、借用して下に貼っておきます。

プロファイラの画像

理由は Item_CD 列の型 nvarchar(6) の 6 で制限を受けるからです。具体的には以下の 2 つです。

(1) SqlParameter の Size

上の画像の通り SELECT クエリをベースに Visual Studio のデータソース構成ウィザードで FillByICD, GetDataByICD メソッドを生成していますが、その際 @ICD には SqlParameter が以下のように初期化されパラメータとして設定されます。

this._commandCollection[1].Parameters.Add(
  new SqlParameter("@ICD", SqlDbType.NVarChar, 6,
  ParameterDirection.Input, 0, 0, "Item_CD", 
  DataRowVersion.Current, false, null, "", "", ""));

SqlParameter コンストラクタの第 3 引数がデータベースの Item_CD 列の型 nvarchar(6) の 6 となり、結果 SQL Server に送信するデータの最大量が 6 文字に制限されます。

上のプロファイラの画像で @CID=N'%A0000' と 6 文字になっているところに注目してください。この時は C# のコードでは "%A00001%" という文字列を代入したとのことですが、SQL Server では先頭から 6 文字だけしか受け取れていません。

(2) sp_executesql の N'@ICD nvarchar(6)'

SqlParameter を使った ADO.NET のパラメータ化クエリは、上のプロファイラ画像のとおり sp_executesql に変換されます。その中の N'@ICD nvarchar(6)' の 6 でも制限を受けます。

一番上の画像を見てください。SSMS で sp_executesql を実行したもので、そこでは @CID=N'%A00001%' としていますが、先頭から 6 文字しか SQL Server には渡せないようで、一件もヒットしていません。(@ICD には %A0000 が代入されると思われる)

ちなみに、N'@ICD nvarchar(6)', の 6 を 7 に変えると同じく @CID=N'%A00001%' で 3 件ヒットします。(@ICD には %A00001 が代入されると思われる)

何が N'@ICD nvarchar(6)', の 6 を決めているのかという疑問は残っていますが、この 6 が制限の一つであるのは結果から間違いなさそうです。

回避策は WHERE 句を WHERE Item_CD LIKE N'%' + @ICD + N'%' のように変更して、@ICD には A00001 と入力するのがよさそうです。

と言うか、普通は最初から WHERE Item_CD LIKE N'%' + @ICD + N'%' とすると思います。だから、今回の問題のような話は初耳だったのかもしれません。

LIKE 句を使った時の問題は、自分が知る限りですが先の記事「固定長パラメータの LIKE 比較」に書いた話がありました。

加えて、LIKE 句を使うと今回のような問題も起こり得るということを知ることができて、MSDN Forum のおかげで一つ利口になったような気がします。

Tags: , ,

ADO.NET

固定長パラメータの LIKE 比較

by WebSurfer 3. September 2010 22:35

Transact-SQL の LIKE 句を使用して文字列の比較を行うときは、パターン文字列と比較対象文字列の中の空白の取り扱いに注意が必要です。

詳しくは MSDN ライブラリの LIKE (Transact-SQL) を見てもらうとして、ここでは ADO.NET や Visual Studio のウィザードで作る TableAdapter を利用する場合の注意事項を、例をあげて書いておきます。

例として、SQL Server DB に TestTable という名前のテーブルがあり、以下のフィールドを持っているとします。

FieldA  nchar(5)
FieldB  nchar(20)

TeatTable は以下の内容になっています。(下の画像では TestTable2 になっていますが、気にしないでください。(笑))

TeatTable の内容

この Table を、以下の SELECT クエリを用いて、FieldA の 前 2 文字を指定して検索を行うとします。

SELECT FieldA, FieldB
FROM   TestTable
WHERE  (FieldA LIKE @FieldA + N'%')

まず、Visual Studio のウィザードを利用して、上記の SELECT クエリをベースに型付 DataSet + TableAdapter を作ります。デザイン画面で見ると、以下のようになります。

型付 DataSet + TableAdapter

TableAdapter 作成された GetData メソッドを使って、その引数を "01" としてデータを抽出してみます。

結果は "01" しか抽出されません。FieldA = "01" で、"01", "01111", "012", "01234", "01457" がマッチするように思えますが、"01" しか抽出されないのは何故でしょう?

理由は以下のとおりです。

自動生成される TableAdapter のコードは、nchar(5) の場合、パラメータの追加部分は以下のようになります。

this._commandCollection[0].Parameters.Add(
  new global::System.Data.SqlClient.SqlParameter(
    "@FieldA", global::System.Data.SqlDbType.NChar, 5, 
    global::System.Data.ParameterDirection.Input, 0, 0, 
    "FieldA", global::System.Data.DataRowVersion.Current, 
    false, null, "", "", ""
  )
);

この ... SqlDbType.NChar, 5, ... というところが問題のようです。SqlParameter.Size に 5 が設定されるので、FieldA = "01" の場合、クエリは LIKE '01   %'(01 と % の間に 3 文字空白)となって、"01"(実際は 01 の後に空白 3 文字あり) 以外はマッチしないという���とのようです。

MSDN ライブラリの SqlParameter.Size プロパティ の解説には "固定長データ型では、Size の値は無視されます。" と書いてあったんですが・・・

解決策は以下のとおりです。

  1. FieldA の型を変更できるのであれば、nchar(5) を nvarchar(5) に変更して、型付 DataSet + TableAdapter を作り直す。
  2. FieldA の型を変更できなければ、ウィザードベースで GetData メソッドを作るのは諦めて、自力でコードを書いて TableAdapter を拡張する。

上記 2 ですが、パラメータの追加の部分を以下のようにすると期待通りの結果になります。自力で書くのは面倒だからといって、自動生成されたコードを書き直すと、思わぬところで不具合を生じる可能性がありますので止めた方がいいです。

command1.Parameters.Add(
  new SqlParameter("@FieldA", SqlDbType.NChar));
command1.Parameters["@FieldA"].Value = "01";

または、

command1.Parameters.AddWithValue("@FieldA", "01");

なお、先の記事 SqlParameter の Size 指定 で、長さ(SqlParameter.Size)の指定は 0 が良いと書きましたが、それは今回のケースでも当てはまっていました。以下のようにしても OK です。

command1.Parameters.Add(
  new SqlParameter("@FieldA", SqlDbType.NChar, 0, "FieldA"));
command1.Parameters["@FieldA"].Value = "01";

------------ 2010/9/4 追記 ------------

パターン文字列の生成に使う型(パラメータ設定のデータ型)と比較対象文字列の型(テーブル定義のデータ型)を合わせる必要はないということに気がつきました。

パラメータ設定で SqlDbType.NChar を SqlDbType.NVarChar に変更しても OK です。すなわち以下のようにすれば、nvarchar(5) で文字列を組み立てるので、パターン文字列は '01%' となるはずです。

command1.Parameters.Add(
  new SqlParameter("@FieldA", SqlDbType.NVarChar, 5, "FieldA"));
command1.Parameters["@FieldA"].Value = "01";

要するに、パラメータ設定で、SqlDbType.NChar とすると MSDN ライブラリの LIKE (Transact-SQL) の解説のセクションにある 2 つの SQL の前者、SqlDbType.NVarChar とすると後者のようになるということのようです。

Tags: , ,

ADO.NET

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  July 2020  >>
MoTuWeThFrSaSu
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

View posts in large calendar