[SQLite]SELECT文でプレースホルダを複数使用する方法を教えてください。

3,111 views
Skip to first unread message

中村 千尋

unread,
Jul 22, 2011, 3:23:11 AM7/22/11
to 日本Androidの会
初めて投稿させて頂きます。中村と申します。
長文失礼致します。


●問題
 SQLiteDatabase.query()のwhere句に複数のプレースホルダをバインドさせて
 SELECT文を発行しようとしているのですが、うまくレコードが取得できません。
 DBにレコードが存在するのに検出されず、SQLエラーも発生しないため
 対処方法がわからず困っております。


●質問
①query()にて複数のプレースホルダを使用してレコードを検出することは可能でしょうか?
 可能なようでしたら、適切な方法をご教授頂きたく存じます。
 (下記「●現在の状況」に実装方法を記載致しました)

②コマンドラインにて直接SQL文を発行してレコードを取得できるか確認しようとしましたが、
 select * from member_tbl where country = ?1 and sex = ?2, (?1 =
JPN , ?2 = MALE), (?1 = USA , ?2 = FEMALE);
 のように記述したところエラーとなってしまいました。
 SQLiteにてSELECT文に複数プレースホルダを使用する方法をご教授ください。


●現在の状況
SQLiteDatabase.query()にてSELECT文を発行する際、
第3引数のwhere句に複数のプレースホルダを設定し、
第4引数のString[]にそれぞれのプレースホルダへの値をセットして
レコードを取得したいのですが、エラーこそ発生しないものの、
常にレコード数が0になってしまいます。


具体的には、
---------------------------------------------------------------------------
■テーブル「member_tbl」のカラム内容
String id|String name|String country|String sex|String group_name

■テーブル「member_tbl」内のデータ
1|Hirotaka|JPN|MALE|GRP_A
2|Andrea|USA|FEMALE|GRP_A
3|John|USA|MALE|GRP_A
4|Yuriko|JPN|FEMALE|GRP_B
5|Kim|KOR|MALE|GRP_B
6|Shnitzer|GER|MALE|GRP_B

■query()呼び出しのソース
String[] all_Col = new String[]{"id", "name", "country", "sex",
"group_name"};
String[] seekRec = new String[]{"?1 = JPN , ?2 = MALE", "?1 = USA , ?2
= FEMALE"};
String where = "country = ?1 and sex = ?2";

Cursor c = db.query("member_tbl", all_Col, where, seekRec, null, null,
null, null );
Log.v("TEST", "c.getCount() = [" + c.getCount() + "]");
---------------------------------------------------------------------------
のように実装しています。

期待値としては
HirotakaとAndreaが検出されてc.getCount()の結果が2となる認識なのですが、
うまく検出されず、常に検出結果が0件になってしまいます。
(※ログにて「c.getCount() = [0]」と出力される)

SQL文としては問題ないようなのですが(SQLエラー等は発生しない)、
適切なSELECT文になっていないため「合致するレコードが無い」と
判断されているのではないかと思っています。


データベース自体初心者のため、認識誤り等ございましたら申し訳ありません。
適切な方法をご存知の方がおられましたら、
上記の質問①②についてご教授頂けます様お願い致します。

以上、よろしくお願い致します。

OBATA Akio

unread,
Jul 22, 2011, 5:44:24 AM7/22/11
to android-g...@googlegroups.com
On Fri, 22 Jul 2011 16:23:11 +0900, 中村 千尋 <wow.nkm...@bee-u.com> wrote:

> ●質問
> ①query()にて複数のプレースホルダを使用してレコードを検出することは可能でしょうか?
>  可能なようでしたら、適切な方法をご教授頂きたく存じます。
>  (下記「●現在の状況」に実装方法を記載致しました)

ここでいう「複数のプレースホルダ」というのはどっちの意味でしょうか?
1.1つのSQLにプレースホルダを複数置く方法
2. 1つのプレースホルダに複数の条件を当てはめる方法

1. は可能だと思いますが、2. はできないと思います。

> ②コマンドラインにて直接SQL文を発行してレコードを取得できるか確認しようとしましたが、
>  select * from member_tbl where country = ?1 and sex = ?2, (?1 =
> JPN , ?2 = MALE), (?1 = USA , ?2 = FEMALE);
>  のように記述したところエラーとなってしまいました。
>  SQLiteにてSELECT文に複数プレースホルダを使用する方法をご教授ください。

コマンドラインでプレースホルダを使用する方法は分かりません。
API 経由ではできますけれど。

これだと、?1 に "?1 = JPN , ?2 = MALE" が、?2 に ""?1 = USA , ?2 = FEMALE" が
設定されるので、条件に合致するレコードはないことになります。

期待した結果を得るには、
String[] seekRec = new String[]{"JPN", "MALE", "USA", "FEMALE"};
String where = "(country = ? and sex = ?) or (country = ? and sex = ?)";
でしょうが、条件の country と sex の組み合わせの数が可変で不定なら、
もうちょっと工夫がいりますね。

--
OBATA Akio / oba...@users.sourceforge.net

中村 千尋

unread,
Jul 24, 2011, 9:54:24 PM7/24/11
to 日本Androidの会
>OBATA様
ご返信ありがとうございます。

> ここでいう「複数のプレースホルダ」というのはどっちの意味でしょうか?
> 1.1つのSQLにプレースホルダを複数置く方法
> 2. 1つのプレースホルダに複数の条件を当てはめる方法
>
> 1. は可能だと思いますが、2. はできないと思います。

わかりにくい書き方をしてしまってすみません。
1のつもりでした。

> これだと、?1 に "?1 = JPN , ?2 = MALE" が、?2 に ""?1 = USA , ?2 = FEMALE" が
> 設定されるので、条件に合致するレコードはないことになります。

そうなんですね。
プレースホルダに番号をつけることで、SQL文中の任意の場所を
指定できるものと勘違いしておりました。


> 期待した結果を得るには、
> String[] seekRec = new String[]{"JPN", "MALE", "USA", "FEMALE"};
> String where = "(country = ? and sex = ?) or (country = ? and sex = ?)";
> でしょうが、条件の country と sex の組み合わせの数が可変で不定なら、
> もうちょっと工夫がいりますね。

例文をシンプルにするため条件を「country」と「sex」の2カラムとしましたが、
実際には常に3カラムを指定して検索したいと考えています。

一つのSQLで検索する組み合わせのパターン数が固定ではないため、
> String where = "(country = ? and sex = ?) or (country = ? and sex = ?)";
のようにした場合に"(country = ? and sex = ?)"の繰り返しが増えて
SQLが長くなりすぎないか検討する必要があると思いました。

いまさらですが、質問の背景としまして、
rawQuery()をループに組み込んでコールし続けたところ
『Reached MAX size for compiled-sql statement cache for database.
<中略>Please change your sql statements to use '?' for bindargs,
instead of using actual values.』
というエラーが返却されたため、エラー回避方法の一つとして
複数プレースホルダの利用を検討しております。

あるいは『compiled-sql statement cache』というのを
任意のタイミングでクリーンできればいいのでしょうが、
どうもそういったメソッドや方法は用意されてないようでした。

まずはご教授頂いた方法で複数の"?"へのバインドを
実現できるか試してみます。ご助言ありがとうございます。



On 7月22日, 午後6:44, "OBATA Akio" <oba...@users.sourceforge.net> wrote:

nagamatu

unread,
Jul 24, 2011, 10:19:54 PM7/24/11
to 日本Androidの会
> あるいは『compiled-sql statement cache』というのを
> 任意のタイミングでクリーンできればいいのでしょうが、
> どうもそういったメソッドや方法は用意されてないようでした。

ソースコードを見てみると、

257 * for each instance of this class, a cache is maintained to
store
258 * the compiled query statement ids returned by sqlite
database.

http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=core/java/android/database/sqlite/SQLiteDatabase.java;h=a0e9d0248769ae9e0442da8e5c2dbfd52e45a0b9;hb=HEAD#l256

とのコメントから、SQLiteDatabaseの instance毎に cacheを管理していますので instanceを生成し直す事で
cacheを擬似的にクリアすることが出来るでしょう。

268 * this cache has an upper limit of mMaxSqlCacheSize
(settable by calling the method
269 * (@link setMaxCacheSize(int)}). its default is 0 - i.e., no
caching by default because
270 * most of the apps don't use "?" syntax in their sql,
caching is not useful for them.

とコメントに書かれているにもかかわらず mMaxSqlCacheSizeの値をデフォルトで 250に設定するのは非道だと考えます。

--

OBATA Akio

unread,
Jul 24, 2011, 10:56:18 PM7/24/11
to android-g...@googlegroups.com
On Mon, 25 Jul 2011 10:54:24 +0900, 中村 千尋 <wow.nkm...@bee-u.com> wrote:

> いまさらですが、質問の背景としまして、
> rawQuery()をループに組み込んでコールし続けたところ
> 『Reached MAX size for compiled-sql statement cache for database.
> <中略>Please change your sql statements to use '?' for bindargs,
> instead of using actual values.』
> というエラーが返却されたため、エラー回避方法の一つとして
> 複数プレースホルダの利用を検討しております。

よくわからないのですが、「複数プレースホルダ」というか、
このループで試したときには、プレースホルダを利用してなかったということでしょうか?
query の sql はプレースホルダを使って、ループの間、文字列を固定して、
ループの中で、selectionArgs を 動的に組み立てれば、起きなさそうですけれども?
条件が場合によって変わる部分が見えないので、具体的なソースは出せませんが。

kobadroid

unread,
Jul 25, 2011, 3:17:18 AM7/25/11
to android-g...@googlegroups.com
KobadroIDと申します。


>rawQuery()をループに組み込んでコールし続けたところ
>『Reached MAX size for compiled-sql statement cache for database.
> <中略>Please change your sql statements to use '?' for bindargs,
> instead of using actual values.』

SQLiteDatabaseのsetMaxSqlCacheSize()でキャッシュサイズを
拡大できませんか?(試してないですが

SQLiteDatabaseのソースを見ると250まで拡大できるようです
SDKガイドも参考になるかと思います。
   
2011年7月25日10:54 中村 千尋 <wow.nkm...@bee-u.com>:

--
このメールは Google グループのグループ「日本Androidの会」の登録者に送られています。
このグループに投稿するには、android-g...@googlegroups.com にメールを送信してください。
このグループから退会するには、android-group-j...@googlegroups.com にメールを送信してください。
詳細については、http://groups.google.com/group/android-group-japan?hl=ja からこのグループにアクセスしてください。




--
KobadroID Web
http://kobadroid.web.fc2.com/

kobadroid

unread,
Jul 25, 2011, 3:39:47 AM7/25/11
to android-g...@googlegroups.com
補足です

ちょっと自分の環境で試したところ、setMaxSqlCacheSize()は
android3.0にしないと出てこないようですね。ご注意くださいませ。
自分の環境がおかしいだけかもしれませんが。

当該エラーはSQLiteDatabase.addToCompiledQueriesで
発生するようです。

--
KobadroID Web
http://kobadroid.web.fc2.com/  SerifViewにエフェクト機能付けました

2011年7月25日16:17 kobadroid <uu5...@gmail.com>:

中村 千尋

unread,
Jul 25, 2011, 5:52:27 AM7/25/11
to 日本Androidの会
★皆様
返信が遅れてしまい申し訳ありません。
ご助言ありがとうございます。
すみません、SDKバージョンの記載がもれておりました。
使用しているSDKのバージョンは2.1(Eclair)です。


★nagamatu様
> とのコメントから、SQLiteDatabaseの instance毎に cacheを管理していますので instanceを生成し直す事で
> cacheを擬似的にクリアすることが出来るでしょう。

現在の実装では

①DB操作を纏めたクラスのコンストラクタをコールし、クラスのobjectを生成
②上記コンストラクタ内でSQLiteDatabaseのインスタンスを生成
③DB関連の操作が全て終わるまで(=アプリが終了するまで)
SQLiteDatabaseのインスタンスを保持し続ける

としており、アプリが終了するまで①のコンストラクタで生成した
SQLiteDatabaseのインスタンスを保持し続ける形になっています。

 ①でのSQLiteDatabaseインスタンス生成をやめ、
 DB操作クラスの各メソッド内でインスタンスを生成し、
 メソッドを終了するときにそのインスタンスを初期化する

ようにすれば、cacheを擬似的にクリアすることが出来るはず
という認識で合っていますでしょうか?

> とコメントに書かれているにもかかわらず mMaxSqlCacheSizeの値をデフォルトで 250に設定するのは非道だと考えます。
すみません、知識不足のためおっしゃっていることが理解しきれていないかも知れないのですが、
「SQLiteDatabaseにはmMaxSqlCacheSizeというパラメータがあり、
 その値を最大にすればcacheサイズが増えるので現在の問題は解決するかもしれないが、
 必要ない時も常にキャッシュサイズを多めに保持するのはメモリを圧迫するのでよろしくない。
 必要なときにSQLiteDatabaseのインスタンスを生成し直す方がベター」
とアドバイスを頂いた認識で合っていますでしょうか?

ご記載頂いた内容を基に考え、
①のコンストラクタで生成したSQLiteDatabaseのインスタンスを
使い続けているため、それまでに発行した全てのSQL文がcacheに残ってしまっており、
『compiled-sql statement cache』エラーが発生しているのではないかと思いました。
ご指摘頂きましたとおり、『使用する前にSQLiteDatabaseのインスタンスを生成し直す』
方法を検討してみます。
ご教示ありがとうございます。



★OBATA様
> このループで試したときには、プレースホルダを利用してなかったということでしょうか?
> query の sql はプレースホルダを使って、ループの間、文字列を固定して、
> ループの中で、selectionArgs を 動的に組み立てれば、起きなさそうですけれども?

はい、プレースホルダを使用せず、レコードを一件取得するメソッドを
取得したいレコードの分だけ呼び元のループ処理からコールしておりました。

ループからコールするのではなく、countryとsexをString[]の要素に持つ
ArrayList<String[]>を引数にselectionArgsを作成する方法も試してみたのですが、
前回のメールにてご指摘頂いたように、プレースホルダへのバインド方法を誤っておりましたので、
条件に合致するレコードが見つからなかったようです。

使用する直前にSQLiteDatabaseのインスタンスを生成し、
プレースホルダを正しく使用する方法で確認してみます。

★KobadroID様
> SQLiteDatabaseのsetMaxSqlCacheSize()でキャッシュサイズを
> 拡大できませんか?(試してないですが
> SQLiteDatabaseのソースを見ると250まで拡大できるようです
> SDKガイドも参考になるかと思います。
> android3.0にしないと出てこないようですね。ご注意くださいませ。

アドバイスありがとうございます。
自分の記載不足で申し訳ないのですが、開発環境のAndroid OSバージョンは
Eclairですので、setMaxSqlCacheSize()は使用できないようです。
(setMaxSqlCacheSize()はARIリファレンスでAPI Level 11となっておりました)

> 当該エラーはSQLiteDatabase.addToCompiledQueriesで
> 発生するようです。

ありがとうございます。
ソースコードの方も確認してみます。

以上、よろしくお願い致します。

中村 千尋

unread,
Aug 2, 2011, 6:21:49 AM8/2/11
to 日本Androidの会
★皆様
返信が遅れてしまい申し訳ありません。
「Reached MAX size for compiled-sql statement cache for database.~」
エラーの回避に関しまして、ご助言頂いた内容を元に以下の対応を行いました。

結論としましては、現在も問題解決できておらず、試行錯誤を続けている状態です。
今後も調査を続けるつもりですが、取り急ぎ一時報告させて頂きます。
ご助言下さったOBATA様、nagamatu様、KobadroID様、ありがとうございました。



■SQLiteDatabaseインスタンスのこまめな生成/クローズによる
 compiled-sql statement cacheの都度開放

 自作のDB操作クラスの各メソッドにて、DBを操作する直前に
 getReadableDatabase()等でSQLiteDatabaseのインスタンスを生成し、
 使い終わったらSQLiteDatabase.close()をコールすることで、
 compiled-sql statement cacheの都度開放を試みました。

 以前は自作のDB操作クラスを生成する際にコンストラクタ内で
 SQLiteDatabaseのインスタンスを生成し、アプリが終了する時に
 SQLiteDatabase.close()をコールしていました。
 nagamatu様のご助言を元に上記の対応を行いましたが、
 問題解決に至りませんでした。



■SQLiteDatabase.query()のwhere句に複数のプレースホルダをセットし、
 where句の各値をバインドさせる

 query()、rqwQuery()をコールする際にプレースホルダを使用し、SQLをバインド。
 その際、以下のようにコーディングすることで目的のレコードを取得できることを
 確認しました。(以下はrawQuery()の例)

  String sqlStr = "select * from memberTbl where country = ? and sex
= ?";
  Cursor c = db_ro.rawQuery(sqlStr, new String[]{ "JPN", "MALE" } );

  ↓
  sqlStrの一つ目の「?」に"JPN"、二つ目の「?」に"MALE"がセットされ、
  該当するレコードが正常に検出されることを確認しました。

 しかしながら、上記の処理を組み込んだメソッドを作成し、
 呼び元メソッドのループ内でコールしたところ、依然として
 「Reached MAX size for compiled-sql statement cache for database.~」
 エラーが出力されてしまい、問題解決に至りませんでした。

大垣憲俊

unread,
Aug 2, 2011, 8:28:07 AM8/2/11
to 日本Androidの会
中村さん

大垣です。

>呼び元メソッドのループ内でコールしたところ、依然として

ループ内でコールする、というのが、ちょっと引っかかりました。
(なぜループ? ループするからエラーになるのでは? )
ひとまず、提示されたデータを使って、サンプルを作ってみました。

以下、ページの右上あたりにある [Downloads]ボタンで一括ダウンロードできます。

https://github.com/NoritoshiOgaki/ogakisoft-android-examples/tree/master/ListViewWithSqlite

ご参考まで。

中村 千尋

unread,
Aug 3, 2011, 8:05:45 AM8/3/11
to 日本Androidの会
To:大垣様

サンプルを作って下さりありがとうございます。

すみません、作業が立て込んでおり
頂いたコードをちゃんと確認できておりませんが、
以下のファイルを参考にさせて頂きたいと思います。

ListViewWithSqlite\src\ogakisoft\android\example
└MyContentProvider.java

以上、よろしくお願い致します。



On 8月2日, 午後9:28, 大垣憲俊 <noritoshi.og...@gmail.com> wrote:
> 中村さん
>
> 大垣です。
>
> >呼び元メソッドのループ内でコールしたところ、依然として
>
> ループ内でコールする、というのが、ちょっと引っかかりました。
> (なぜループ? ループするからエラーになるのでは? )
> ひとまず、提示されたデータを使って、サンプルを作ってみました。
>
> 以下、ページの右上あたりにある [Downloads]ボタンで一括ダウンロードできます。
>
> https://github.com/NoritoshiOgaki/ogakisoft-android-examples/tree/mas...
>
> ご参考まで。
Reply all
Reply to author
Forward
0 new messages