Android のデータベースでテーブルの結合に困ること


Android で利用できるデータベースは SQLite3 ですが、クエリの結果はCursorという抽象化されたインタフェースで取り扱っています。
SQLiteDatabase で直接 SQL を実行するインタフェースもありますが、SELECT 文だけは特別扱いされ、返り値がCursorとなるため、ほぼ確実にクエリ結果を取り扱うにはCursorとつきあうことになります。

ここで、あるデータベースに以下のような複数のテーブルがあったと仮定します。

  • table_1
_id name description
1 foo first entry
2 bar second entry
3 baz another entry
  • table_2
_id age description
1 13 hogehoge
2 14 fugafuga
3 16 piyopiyo

これらのテーブルから、_idを元に結合したクエリを投げてみます。


SELECT * FROM table_1, table_2 WHERE table_1._id = table_2._id;

これによって得られるCursorで、クエリ結果のセットにアクセスするには、以下のようにします。


Cursor cursor = // query...

if (cursor == null) 
    return;

try {
    while (cursor.moveToNext()) {
        long id = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
        String name = cursor.getString(cursor.getColumnIndex("name"));
        int age = cursor.getInt(cursor.getColumnIndex("age"));
        String description = cursor.getString(cursor.getColumnIndex("description"));

        // do something with the result...
    }
} finally {
    cursor.close();
}

Cursor#getColumnIndex(String)を使って、カラム名に対応する、1行分のデータを格納しているコレクションのインデックスを取得し、そのインデックスを元に、結果セットからデータを取り出します。

さて、table_1table_2にはそれぞれ、descriptionという名前のカラムが存在します。
Cursorを経由したアクセスでは、どちらのものかを区別するようなコードの書き方はしていませんが、結果自体は取得できているようです。

実のところ、テーブルを結合した際、同じ名前のカラムが存在した時、それぞれテーブルごとにカラムを絞り込む機能はCursorにはありません。
内部的には、Mapのようなデータ構造でクエリ結果のセットを管理しています。つまり、カラム名の衝突はMapにおけるキーの衝突と同じ結果をもたらすことになります。よって、データ自体は存在し取得もできていますが、取得できるデータは、Cursorが持つ結果セットに最後に追加された(実質上書きされた)データになります。

このような事態を避けるには、カラム名を衝突しないように作るか、ある程度正規化しないで、最初から結合した状態にしておくか、クエリを別々に分けるかのいずれかの手段で対処します。