• スポンサードリンク

ContentResolver経由のクエリーをGROUP BYした〜い

Android

先日のMediaStoreのサムネイルイメージ表示用のAdpterでも少し書きましたが、ContentResolver経由のクエリーをGROUP BY(どれかのカラムの値でグループ化)するには一工夫必要となります。
というのも、ContentResolver経由でクエリーを投げるには次の2つのメソッドしか無いからです。

GROUP BY出来そうな引数はありません。ちなみにHAVINGも無いですけど。

そこで、Androidのソースではどうしているかを調べてみると、
selectionとして、”1) GROUP BY (2”を引き渡すようにしていることが多いようです。
クエリーがContentResolverからContentProviderへ引き渡されて実行される際に、WHERE句の部分がが”WHERE(selection)”のように展開されることから、

となることを期待しての方法です。
ここで、最初の1はtrueの意味で、全ての行を選択することになります。もし、特定の行を選択するのであれば、ここに、SELECT句を記入する必要があります。
また、最後の2はカラムの番号です。この場合であれば2番めのカラムで、この番号はGROUP BYしたいカラムの番号にする必要があります。

先日のMediaStorePhotoAdapterでも同じ方法を用いて、BUCKET_IDでGROUP BY出来るように実装しています。

問題発覚

ところがですね、上のようにWHERE句を展開しない端末があるようなのです。手持ちの端末ではNEC MEDIAS WP N-06Cがダメでした。
具体的にN-06Cでどのように展開されたかというと、”WHERE(selection)AND(is_drm=0)”となっているようで、

となってしまうのです。SQLとしてはエラーにはなりませんが予期した結果になりませんでした。
色々試してみましたが、クエリーでは対応不可でした。せめて”WHERE(is_drm=0)AND(selection)”のように、前に挿入しててくれたら問題ないのに。でも元々がトリッキーな方法なので愚痴しか言えない・・・とは言え、1つ有るという事は、他にもあるかもしれないしこれから先に発売される端末でもダメなのがでてくる可能性があるということですね。
アプリによってはGROUP BYを諦めるとか、あるいは非対応って宣言するのも一つですが、全部の機種で確認したり、毎回フォローしたりは現実的ではないので、どげんかせにゃいかんですね。

対応策

どうしてもGROUP BYしたい場合には一旦全部を読み込んで自前でGROUP BY相当の処理を実装する必要があります。
よくある手は、クエリーの結果をGROUP BY処理してから、BaseAdapterやArrayAdapterなりを継承してオブジェクトのリストまたは配列として保持したAdapterを作る方法です(定番なのでソースは省略ね)。
でも自分的にはちょっとムムムな方法です。そこでレファレンスを眺めていると・・・MatrixCursorを見つけました。
インメモリでテーブル(Cursor)を保持する方法です。これならGROUP BYしている時もしていない時もCursorとしてアクセスできるので実装を遮蔽できます。

ということで、BUCKET_IDでGROUP BYするときの処理はこんな感じ。
(先日のMediaStorePhotoAdapterのMyAsyncQueryHandler#onQueryComplete部分です)

GROUP BYしない時(mDisplayType == DISPLAY_IMAGE)は特に何もせずに、そのまま#swapCursorに引き渡します。Cursorのclose処理を省略したければ#changeCursorを使うことも出来ます。
一方、GROUP BYする時(mDisplayType == DISPLAY_BUCKET)は、HashMapを使って重複を省き各グループの先頭のレコードのみを残すようにします(6〜17行)。続いてMatrixCursorに必要なレコードを挿入していきます。

MatrixCursorを使用する一番のメリットは元のCursorと同じ構造のレコードを構築できることですね。なので他の場所でアクセスする際にMatrixCursorなのかSQLiteQursorなのかそれとも他のCursorなのかを考える必要がありません。

デメリットは、結局のところインメモリの処理なのでメモリ使用量が増えるのと、一旦全レコードを読み込まないとだめなのでレコード数が極端に多いと処理速度に影響が出るってところですね。
今回の場合はMediaStoreのイメージのGROUP BYなので、元のイメージがせいぜい数百〜数千件、グループの数はその10分の1とか数分の1程度という予測のもと、MatrixCursorを使いました。もし、データ件数が数万〜数十万件とかになるのであれば、携帯やタブレットで扱うデータ件数ではないですが、自前でSQLiteへキャッシュしてとかを考えないといけないかもしれません。それもこれもContentResolver#queryにGROUP BYに使う引数が無いからなんですけど・・・余計な一手間が必要になるのはアドレス帳のデータを取得する時とかも同じですよね。

ということで、イレギュラーな機種でもGROUP BY出来るようになりました。\(^o^)/
いや〜色んな所にレアな罠を仕掛けてくれるもんです。他の人やアプリではどうしてんだろうなぁ?
お疲れ様でした。

« »

  • スポンサードリンク