2012年1月27日金曜日

AndroidのSQLiteで別のデータベースへ値を移動する方法

 

先日ZENBOOK買ったのでその簡易レビューでもしようかなーなんて思ってましたが、一向にお勧め感がでないままだったので長いことほったらかしにしてました。んで別の記事ネタができたのでとりあえずいってみます。

※2012/01/28追記 記事内にコメントを追記しました。

■どゆこと?

簡単に言うと、「あにすと」で通常SQLiteのDB保管しているツイート情報を別のデータベースファイルに簡単に移動する方法ないかなーってことで探した→あった→まとめたです。

■参考

sqlite - openbooth <http://openbooth.org/tags/sqlite>

SQLite – 異なるDB間でテーブルをコピー « karak <http://www.karak.jp/sql/sqlite-copy-table.html>

[SQLiteメモ][perlメモ]SQLiteで一つのコネクションで複数のデータベースファイルを扱うには? - KUMA TYPE <http://blog.kumacchi.com/2010/03/sqliteperlsqlite.html>

how to connect an sqlite database (sqlite firefox extension ) to android application, for android 2.1 to query and insert to the database - Stack Overflow <http://stackoverflow.com/questions/5001124/how-to-connect-an-sqlite-database-sqlite-firefox-extension-to-android-applica>

■いうまでもないですがのSQLiteの簡単な特徴

AndroidでデータベースといえばSQLiteです。
SQLiteを利用するにはSQLiteOpenHelperというクラスを継承して実装します。
詳しい説明はテクブさんの記事を見てください。

データを簡単に保存する方法(SQLite編) « Tech Booster <http://techbooster.jpn.org/andriod/application/567/>

そして、SQLiteOpenHelper(もしくはSQLite)の特徴はというと

・1ファイル単位で1データベースとして管理される
・SQLiteOpenHelperのコンストラクタでデータベース名とバージョン番号を指定する
・データベースが存在しない状態でオープンしようとするとonCreateが呼ばれ、その中でテーブル作成を行なう。
・データベースとバージョンが更新された場合はonUpgradeが呼ばれ、テーブルの再構成を行なう。
・複数件登録などを行なう場合はトランザクションを設定しないと相当遅い。

て感じでしょうか。

■「あにすと」の保存番組制限数の話

拙作「あにすと」は30分番組を1800件のツイートまで保存しています。
1ツイート200byteぐらいのデータだとすると、単純計算で350KB。
10番組だと18000件で約3MB程度まで膨れます。
で、いろいろあった経験上( http://miquniqu.blogspot.com/2011/06/sqlite.html )AndroidのSQLiteが大量件数の扱いに弱い気がするので、1データベースでの制限として10番組程度にしておこうかなといった状況で作ってます。

■それとデータの移動とどういう関係が

アプリ更新してると、だんだんデータベースの構成変えたいなーとか思うんですが、元のデータをばっさり消してしまっていいものか、「あの夏で待ってる」の実況ツイート残しておきたいぞ(私欲)、ちゃんと作るとonUpgradeの実装めんどいぞ、じゃぁ別のデータベースへ移動してしまえ、でも別のデータベースへ移動するためにそのまま実装すると

[単純な方法]
SQLiteOpenHelperMAIN へ登録
 データベースMAINを作成(初回のみ)
 データベースMAINにテーブルA作成(初回のみ)
 データベースMAINのテーブルAにデータ投入

SQLiteOpenHelperMAINを検索
 データベースMAINのテーブルAからデータ取得
SQLiteOpenHelperSUBへ登録
 データベースSUBを作成(初回のみ)
 データベースSUBにテーブルA作成(初回のみ)
 データベースSUBのテーブルAにデータ投入

SQLiteOpenHelperMAINを削除
 データベースMAINのテーブルAからデータを削除

こんな感じになってややこしいし遅いしダサすぎとか言われそうどうしようーださいー。

■「ATTACH DATABASE」で簡単便利

というわけで、「ATTACH DATABASE」でできました。
以下はSQLiteOpenHelperを継承したクラスの抜粋です。

  1:   public synchronized static boolean copyMain2Sub(Context context, int PID) {
  2:     MainDBHelper helper = new MainDBHelper(context);
  3:     SQLiteDatabase db = helper.getWritableDatabase();
  4:     try {
  5: 
  6:       db.execSQL("attach database ? as sub_db", new String[] { context
  7:           .getDatabasePath(DB_NAME_SUB).getPath() });
  8: 
  9:       // トランザクション開始
 10:       db.beginTransaction();
 11: 
 12:       db.execSQL("INSERT OR IGNORE INTO sub_db.parent select * from parent");
 13:       db.execSQL("INSERT OR IGNORE INTO sub_db.child select * from child");
 14: 
 15:       // コミット
 16:       db.setTransactionSuccessful();
 17: 
 18:     } catch (Exception e) {
 19:       return false;
 20: 
 21:     } finally {
 22:       // トランザクション終了(エラー時はロールバック)
 23:       db.endTransaction();
 24: 
 25:       db.close();
 26:       helper.close();
 27:     }
 28:     return true;
 29: 
 30:   }

[解説]
MainDBHelperではmain_dbデータベースファイルにparentテーブルとchildテーブルを持ってます。
SubDBHelperではsub_dbデータベースファイルにparentテーブルとchildテーブルを持ってます。
各テーブルはPIDとかPID+CIDとかでUNIQUE INDEX張ってます。→重複するとエラーなります。
でもINSERT IGNORE INTOで重複検出から自動除外してます。(これは蛇足)


MainDBHelperでparent1件につき、child複数件を登録した状態で、sub_dbへ移動したいとなったとします。
その際に、MainDBHelper側からATTACH DATABASE sub_db情報 as sub_dbとすることで、エイリアス名.テーブル名でsub_dbのテーブルへアクセスができます。


ATTACHはトランザクションの外でしておく必要があるみたいです。たぶん。


これで、いちいちオブジェクトへマッピングして移送しなくてもよくなりました。簡単ですね!


■気をつけたいこと


sub_dbが物理的に存在しないと以下のようなエラーが発生します。

  1: android.database.sqlite.SQLiteException: no such table: sub_db.parent: INSERT OR IGNORE INTO sub_db.parent select * from parent

なので、ATTACHする前に、いったんSUB側のHelperにアクセスしてデータベースを作成しておきましょう。


■障害現象(※2012/01/28追記


上記の検証はXPERIA SO-01B(2.1)で行いましたが、ICONIA A500(3.2) で同様の操作を行ったところ、「no such table」が発生してしましました。
OSバージョンによるものか、メーカー差異によるものか、他の理由かは不明ですが、万能ではなさそうな感じです。ご利用にはご注意を~。
(3.x系だけならいいんですが。どうせ消えるバージョンだし、、、いや、個人的にはICONIA使えないと意味ないのでよくないか・・・。エラーになったらATTACH DATABASE使わない処理にしてみようかな。)


とりあえず、以下は確認結果です。


・機種/対象/結果
XPERIA SO-01B(2.1)/実機/OK
ICONIA A500(3.2) /実機/NG
emu(3.2)/エミュレータ/NG
emu(4.0.3)/エミュレータ/OK


さてどうしたものか。

0 件のコメント:

コメントを投稿