トップ回答者
パラメータを利用したLikeで欲しい結果が得られない

質問
-
◆OS/SP: Windows 10(64bit)
◆開発言語: Visual Basic .NET
◆Visual Studio のバージョン: 2017
◆DB:Azure SQL DataBase
Visual Studioのxsdで作成したFillについて質問させて下さい。
データベースに下記のテーブルを作成してあります。
Item_CD(nvarchar(6))
Touroku_Date(date)
Item_Name(nvarchar(5))
Kazu(int)
データは以下の通りです。
A00001,2019-01-01,商品壱,5
B00005,2019-01-03,商品弐,3
A00001,2019-02-01,商品三,10
C00301,2019-02-09,商品四,7
A00001,2019-03-01,商品五十五,9
Visual Studioでこのデータベースに接続・テーブルをプロジェクトに追加しました。
xsdに表示されたテーブルに以下のクエリを追加しました。
SELECT * FROM tbl_Item
WHERE (Item_CD LIKE @ICD)この状態で「TableAdapterクエリの構成ウィザード」のクエリビルダーから「クエリの実行」を行うと、パラメータに「%A00001%」と指定した場合3件が表示されます。
フォーム(Form1)に設置したTextBox1に検索したいItem_CDを入力してButtonを押すと上で作成したFillがDataGridViewに表示されるようにしました。
Item_CDの一部が入力された状態でも検索をさせたいので、引き渡すパラメータには
@ICD="%" & Form1.Textbox1.text & "%"
の形で渡しています。
ここで分からない所が出てきてしまうのですが、
【現象】
TextBox1に「A00001」を入力してButtonを押す→DataGridViewは0件
TextBox1に「00001」を入力してButtonを押す→DataGridViewは3件
TextBox1に「01」を入力してButtonを押す→DataGridViewは4件
となってしまいます。
(全桁のItem_CDを入れた時だけうまく検索されない状態)
xsdのクエリビルダでは結果が受け取れるのにFormで受け取れない原因が分かりません。
@ICD="%" & Form1.Textbox1.text & "%"
の行にブレークポイントを置いて@ICDの中も確認してみましたが、「%A00001%」となっています。
試しに同じデータ型を持つItem_Nameに対してパラメータを設定し、上と同様にテストしてみると全桁(5桁)の検索値入力(商品五十五)でも1件ちゃんとヒットしました。
また、違うプロジェクトで再度作成し直してみましたが全く同じ現象になりました。
何が原因でこうなってしまうのか、ご教示頂ければ幸いです。
- 編集済み chromeMAO 2019年12月9日 6:49 体裁修正
回答
-
> 変数icdにダイレクトに icd = "%A00001%" では検索されず、
デザイナで FillByICD メソッドの SelectCommand を見た場合に、
WHERE (Item_CD LIKE @ICD)
となっているのですよね。アプリ側からは % を加えないように変更し、SQL 側で加えるというのはどうでしょう。
WHERE Item_CD LIKE CONCAT( N'%', @ICD, N'%' )
検索データに % や _ が含まれる場合に備えるなら、こうかな…。
WHERE Item_CD LIKE CONCAT( N'%' , REPLACE(REPLACE(REPLACE( @ICD , N'\', N'\\') , N'%', N'\%') , N'_', N'\_') , N'%') ESCAPE N'\'
- 回答としてマーク chromeMAO 2019年12月10日 4:02
-
CONCAT のエラーを吐いているのは、実行時の SQL Server でしょうか? それとも開発時のVisual Studio でしょうか?
CONCAT が駄目なら、文字列連結演算子「+」はどうでしょうか。
TableAdapter の場合、SQL の内容が複雑になると、サーバーにとっては有効な SQL であっても、デザイナ側での解析が失敗することがあります。
開発環境で TableAdapter に SQL を追加できない場合、ソリューション エクスプローラーで *.xsd を右クリックして[ファイルを開くアプリケーションの選択...]で「XML (テキスト) エディター」を選択し、<CommandText> タグの内容を直接書き換えるという手もあります。(SQL 内に不等号を含める場合は実体参照で記述する必要があることに注意)
- 回答としてマーク chromeMAO 2019年12月10日 4:02
すべての返信
-
> フォーム(Form1)に設置したTextBox1に検索したいItem_CDを入力してButtonを押すと上で作成したFillがDataGridViewに表示されるようにしました。
「上で作成したFill」とは何ですか? SELECT クエリの WHERE 句の条件を変えて SqlDataAtapter.Fill メソッドで DataTable を作り直してそれを DataGridView にバインドしているのですか?
あと、以下のような話がありますが関係ないですか?
固定長パラメータの LIKE 比較
http://surferonwww.info/BlogEngine/post/2010/09/03/LIKE-comparison-with-fixed-length-parameter.aspx -
-
ご返答ありがとうございます。
お二方にまとめてご返信させて頂きます。
>「上で作成したFill」とは何ですか
大変失礼致しました。
TableAdapterに
SELECT * FROM tbl_Item WHERE (Item_CD LIKE @ICD)
というクエリを追加し、そのクエリをForm1のLoadイベントで呼び出し、DataGridView にバインドしています。Me.Tbl_ItemTableAdapter.FillByICD(Me.TestDataSet.tbl_Item, icd)
ご教示頂いた「固定長パラメータの LIKE 比較」を今拝見中です、遅くて申し訳ありません。
>可能性1:呼び出し側の処理で、@ICD パラメーターの長さ(たとえば SqlParameter.Size )が不十分
nvarchar(8))を持つカラムを作って試して見ました。
7桁目までの検索は正常に検索され、8桁全てを入力すると全く該当しないデータが1件表示される結果になりました。
クエリでパラメータを引き渡す際にパラメータの長さを意識したことが無かったので、やり方を調べて試して見ます。
>可能性2:照合順序の不一致(COLLATE)が発生している
DataBase側はJapanese_CI_ASであることが分かっているのですが、VS側にもあるのでしょうか?
調べてみます。
また、ESCAPE句についてもアドバイスありがとうございます。
必ずセットで実装する様に致します。
取り急ぎご返答させて頂きました。
- 編集済み chromeMAO 2019年12月9日 8:07 追記
-
クエリでパラメータを引き渡す際にパラメータの長さを意識したことが無かったので、やり方を調べて試して見ます。
DataSet デザイナで FillByICD を選択し、プロパティ一覧の Parameters コレクションの [...] ボタンから、@ICDパラメーターの 下記の 3 点を確認してみてください。
DbType = String (AnsiString、AnsiStringFixedLength、FixedLength は NG)
Provider = NVarChar (VarChar、Char、NChar は NG)
Size = 0 または十分に長い値(ここが 6 だと '%A00001%' を格納しきれない)DataBase側はJapanese_CI_ASであることが分かっているのですが、VS側にもあるのでしょうか?
DB 側の称号順序を確認したものです。Japanese_CI_AS なら問題無いですね。
-
ご返答ありがとうございます。
>抽出条件の変更の度に SQL Server にクエリを投げて…かなり無駄が省けると思います。
なるほどです。
クライアントに処理をさせるよりも、SQL Serverに任せた方が早いかと思っていました。
(該当のレコードだけをSQLが抽出しクライアント側に持ってくる為処理が早い、と思っていた)
Azure SQL Databaseは確かデータのReadWriteでも経費が発生した気がする(多分…)ので、是非この方法も調べてみたいと思います。
まずは本件の問題がこの方法で解決するかどうかの確認と、レコード数が多いテーブルを処理した時の時間等を確認してみたいと思います。
取り急ぎご返信致します。
-
SqlParameter.Sizeには問題はなさそうです。
>DB 側の称号順序を確認したものです。Japanese_CI_AS なら問題無いですね。
良かったです!
それでは次に、LIKE 検索時の照合順序指定を、『WHERE (Item_CD LIKE @ICD COLLATE Japanese_unicode_BIN2)』のように別のものに変更してみた場合、結果は変化するでしょうか。
また、COMPATIBILITY_LEVEL は何になっていますか?
大昔の事例なので現在お使いのバージョンに合致する情報だとは思いませんが、過去には、照合順序に関連した LIKE 検索の障害報告が幾つかあったようなので…。
-
ご返答ありがとうございます、返信が日をまたいでしまい申し訳ありません。
>照合順序指定を、『WHERE (Item_CD LIKE @ICD COLLATE Japanese_unicode_BIN2)』
こちらを確認致しました。
結果は変わらず、全桁の検索はヒットせず桁数を減らした場合は正常に検索されます。
以下構文で得たCOMPATIBILITY_LEVELは140でした。
SELECT database_id, collation_name, compatibility_level FROM sys.databases WHERE name = 'TestDB'
リンク先をこれから拝見させて頂きます。
また、ローカルのSQL Serverで同現象が発生するかを確認してみたいと思います。
-
> DataBase側はJapanese_CI_ASであることが分かっているのですが、VS側にもあるのでしょうか?
SQL Azure の照合順序のデフォルトは SQL_Latin1_General_CP1_CI_AS となっているそうですが(以下の記事参照)、上記は間違いないですか?
[SQL Azure] Unicode型列(NCHAR/NVARCHAR) に格納されるデータが “?” になる
https://blogs.msdn.microsoft.com/dsazurejp/2012/06/28/sql-azure-unicodencharnvarchar/> TextBox1に「A00001」を入力してButtonを押す→DataGridViewは0件
> TextBox1に「00001」を入力してButtonを押す→DataGridViewは3件
> TextBox1に「01」を入力してButtonを押す→DataGridViewは4件A が怪しいと疑ってます。ホントに ASCII コードの A ですか?
- 編集済み SurferOnWww 2019年12月10日 1:00 追記
-
ご返答ありがとうございます。
>SQL Azure の照合順序のデフォルトは SQL_Latin1_General_CP1_CI_AS となっているそうですが
はい、確かにDB作成時に照合順序をJapanese_CI_ASに変更しています。
また、
SELECT database_id, collation_name, compatibility_level FROM sys.databases WHERE name = 'TestDB'
を実行した時に「collaction_name=Japanese_CI_AS」と表示されます。
最初にご教示頂いた「固定長パラメータの LIKE 比較」は、今回の件ですとデータ型をnvarcharで指定しているので該当しないと考えました。
どこが悪いのか全く分からないのでいくつか試行してみました。
【試してみた事】
・Azure SQL DataBaseにある別のテーブル(Item_CDカラムがあるテーブル)を使い、検索してみた→同結果
・Azure SQL DataBaseではなくローカル環境のSQL Server2012にテーブルをコピーし、コピーしたテーブルにVSから接続して検索してみた→同結果
・現在の開発環境があるPCではなく別PCの開発環境で検索してみた→同結果
となってしまいました。
Dim icd as Strings icd="%" & Form1.Textbox1.text & "%" Me.Tbl_ItemTableAdapter.FillByICD(Me.TestDataSet.tbl_Item, icd)
という引き渡し方が問題なのでしょうか…(ESCAPE句は後で実装しようとしています)
ちなみに、
変数icdにダイレクトに icd = "%A00001%" では検索されず、
icd = "A00001" だと検索されます。
-
> 変数icdにダイレクトに icd = "%A00001%" では検索されず、
デザイナで FillByICD メソッドの SelectCommand を見た場合に、
WHERE (Item_CD LIKE @ICD)
となっているのですよね。アプリ側からは % を加えないように変更し、SQL 側で加えるというのはどうでしょう。
WHERE Item_CD LIKE CONCAT( N'%', @ICD, N'%' )
検索データに % や _ が含まれる場合に備えるなら、こうかな…。
WHERE Item_CD LIKE CONCAT( N'%' , REPLACE(REPLACE(REPLACE( @ICD , N'\', N'\\') , N'%', N'\%') , N'_', N'\_') , N'%') ESCAPE N'\'
- 回答としてマーク chromeMAO 2019年12月10日 4:02
-
> はい、確かにDB作成時に照合順序をJapanese_CI_ASに変更しています。
データベースの照合順序を変更しても、そのテーブルの中の列の照合順序は元のままで、変更されないですが、そこは大丈夫ですか?
詳しくは以下の記事を見てください。
SQL Server 列の照合順序
http://surferonwww.info/BlogEngine/post/2018/04/26/collation-of-column-in-sql-server.aspx -
実際にどのようなSQLが投げられているのか気になるところですね。
Profilerは使われていますか?
従来のProfilerは使用できません。Azure用として別途インストールする必要があります。
もし、使われていなければ、以下よりインストールし、実際にどのようなSQLが投げられているのか確かめてみて下さい。
今後の開発においても、Profilerは強い味方になってくれるはずです。SQL Profiler to Azure SQL Database
https://blogs.msmvps.com/kenlin/2019/01/02/2806/以下、手元で実行した例ですが、こんな感じで見れます。
★良い回答には質問者は回答済みマークを、閲覧者は投票を!
-
ご返答ありがとうございます。
>アプリ側からは % を加えないように変更し、SQL 側で加えるというのはどうでしょう。
こんな方法があるのですね!
試しに実装してみようとした所、CONCATでは「関数の引数カウントエラー」、CONCATENATEを使ってみると「CONCATENATEは組み込み関数名として認識されません」と出てしまう為現在原因を探っております。
CONCAT()はSQL Server 2012~と調べたのですがひょっとしてAzure SQL Databaseで使えないとかがあるのかもと確認中です。(SELECT @@VERSIONで見ると「Microsoft SQL Azure(RTM)-12.0.2000.8」となる)
-
照合順序とかは全く関係なかったですね。混乱の基になるようなことを書いてすみません。
SQL Server 2008 Express に質問に書いてあった同じスキーマのテーブルとレコードを作って SSMS で試してみましたが、問題が再現できました。なぜこうなるかはこれから調べてみます。(なんとなく文字数制限のような気がしてますが、今のところ原因不明です)
- 編集済み SurferOnWww 2019年12月10日 3:16 追記
-
CONCAT のエラーを吐いているのは、実行時の SQL Server でしょうか? それとも開発時のVisual Studio でしょうか?
CONCAT が駄目なら、文字列連結演算子「+」はどうでしょうか。
TableAdapter の場合、SQL の内容が複雑になると、サーバーにとっては有効な SQL であっても、デザイナ側での解析が失敗することがあります。
開発環境で TableAdapter に SQL を追加できない場合、ソリューション エクスプローラーで *.xsd を右クリックして[ファイルを開くアプリケーションの選択...]で「XML (テキスト) エディター」を選択し、<CommandText> タグの内容を直接書き換えるという手もあります。(SQL 内に不等号を含める場合は実体参照で記述する必要があることに注意)
- 回答としてマーク chromeMAO 2019年12月10日 4:02
-
魔界の仮面弁士様、SurferOnWww様、trapemiya様
ご返答ありがとうございます。
WHERE Item_CD LIKE N'%' + @ICD + N'%'
これで目的の挙動になる事を確認しました!
色々な角度からのご意見を皆様から頂き本当に勉強になり助かりました。ありがとうございます。
照合順序やCONCAT、Profiler等今回質問しなければ意識しない・分からない事でした。
CONCATは私のSQL Serverのバージョンでも動くとの事なので、動くように調べてみます。
Profilerは早速ダウンロードしたので、使い方を調べてみます。
しかしながら、
Item_CD LIKE @ICD
と同じ形式で実装してしまっている部分が多いので修正に時間が掛かりそうです…
でも、皆様のおかげで希望する動きが実装できました。
本当にありがとうございました。
-
上の私のレスで、
> なぜこうなるかはこれから調べてみます。(なんとなく文字数制限のような気がしてますが、今のところ原因不明です)
と書きましたが、それを以下に書いておきます。
TechNet の記事「第 4 回 アドホック クエリのパラメータ化」(今はリンク切れで読めません)に書いてあったことですが "SqlParameter クラスを利用すると、内部的には sp_executesql に変換されて実行される" とのことです。今回のケースでは以下のような形になると思います。
exec sp_executesql N'SELECT [Item_CD],[Touroku_Date],[Item_Name],[Kazu] FROM [TestDatabase].[dbo].[tbl_Item] WHERE [Item_CD] LIKE @ICD', N'@ICD nvarchar(6)', @ICD=N'%A00001%'
N'@ICD nvarchar(6)', がホントに 6 なのかどうかは確認が取れていませんが、6 の場合は質問者さんの結果、
> TextBox1に「A00001」を入力してButtonを押す→DataGridViewは0件
> TextBox1に「00001」を入力してButtonを押す→DataGridViewは3件
> TextBox1に「01」を入力してButtonを押す→DataGridViewは4件
が再現できます。
これが 7 なら期待通りの結果になります。
なので、N'@ICD nvarchar(6)', となっているのだろうと想像してますが、プロファイラが使えるなら確認してもらえるとありがたいです。 -
TableAdapter で CONCAT を使おうとしたら、当方でも同様のエラーになりました。(手元の SQL Server は 2012 未満のバージョンであったため、当然ではあるのですが)
ただし ODBC スカラー関数 の CONCAT 関数なら使えるので、引数を 2 つだけにして下記の構文にした場合には呼び出すことができました。「+ 演算子」の方がスマートなので、実際にこんな面倒な構文を使うことは無いと思いますが。
WHERE (Item_CD LIKE { fn CONCAT(N'%', { fn CONCAT(@ICD, N'%') } ) })
なお、VB 側で % を補う方法については、当方では問題を再現することができませんでした。TableAdapter 経由で問題なく呼び出せています。
Item_CD LIKE @ICD
ちなみにパラメーターの Size が 6 の場合には、下記の構文で A00001 な 3 レコードが拾われることになりました。もちろん Size = 50 ならば問題ありませんが。
'先頭 6 文字は "A00001" になる状態 Me.Tbl_ItemTableAdapter.FillByICD(Me.TestDataSet.tbl_Item, "A0000123")
なお Size が 50 だったとしても、プロファイル上は『N'@ICD nvarchar(8)',@ICD=N'%A00001%'』として処理されていました。もしかしたら Azure SQL だと nvarchar(6) になってしまうのかも。
そのほか過去の障害事例として、"%A00001%" ではヒットせず、"%%A00001%%" だとヒットするという現象を聞いたことがあります。(情報ソースは失念してしまい、対象データベースが SQL Server であったかどうかすら記憶に無いのですが)
- 編集済み 魔界の仮面弁士MVP 2019年12月10日 6:17
-
魔界の仮面弁士様
ご返信ありがとうございます。
>引数を 2 つだけにして下記の構文にした場合には呼び出すことができました。
わざわざご確認頂きありがとうございました。
私も2つまでは大丈夫なんだな、という所までは行ったのですがご教示頂いた様なWhere文を組み立てることが出来ませんでした…
>過去の障害事例として、"%A00001%" ではヒットせず、"%%A00001%%" だとヒットするという現象を聞いたことがあります。
ありがとうございます。
今Profilerを使って調べてみた結果、どうしてこういった現象が起きるかという理由は分かったような気がします。
これからSurferOnWww様への返信にまとめさせて頂きます。
-
SurferOnWww様、trapemiya様、魔界の仮面弁士様
大変遅くなりましたが、Profilerを利用してSQL Serverに投げられている実際のクエリを確認しました。
データセットデザイナー上でクエリを実行した時は、Where Item_CD LIKE "%A00001%" で実行しても以下の結果が得られました。
この時にSQL Server側に投げられていたのは下図の通りでした。(nvarchar(8)になっている)
このクエリにバインドさせたDataGridViewがあるForm側から同条件でクエリを動かすと0件の抽出となります。
この時にSQL Server側に投げられていたのは下図の通りでした。
そして再度パラメータのSizeを調べてみると、確かにSize=50だったのにSize=6になっていました…
以上の事から、検索されないのは文字数がオーバーしているので該当データが無い為、であるのが分かりました。
が、同じクエリを使っているのにデータセットデザイナ上でのクエリ実行はnvarchar(8)に勝手に変換されるのに対し、Formでのクエリ実行はnvarchar(6)のままなのでしょうか…
解決策はSize=50にするなどの手段があることが分かりましたが、すごく不思議です。
- 編集済み chromeMAO 2019年12月10日 6:46 図が見にくかったので注釈追加