Обновить базу данных SQLite с одной версии на другую?
Я получаю ошибку от Logcat
говоря, что определенный столбец (по моему SQLiteOpenHelper
подкласс) не существует. Я думал, что могу обновить базу данных, изменив DATABASE_CREATE
строка. Но, очевидно, нет, так как я могу (шаг за шагом) обновить базу данных SQLite с версии 1 до версии 2?
Я извиняюсь, если вопрос кажется "нубистским", но я все еще изучаю Android.
@ Pentium10 Вот что я делаю в onUpgrade:
private static final int DATABASE_VERSION = 1;
....
switch (upgradeVersion) {
case 1:
db.execSQL("ALTER TABLE task ADD body TEXT");
upgradeVersion = 2;
break;
}
...
3 ответа
Хорошо, прежде чем столкнуться с более серьезными проблемами, вы должны знать, что SQLite ограничен командой ALTER TABLE, она позволяет add
а также rename
только нет удаления / удаления, что делается с воссозданием стола.
Вы всегда должны иметь под рукой новый запрос на создание таблицы и использовать его для обновления и передачи любых существующих данных. Обратите внимание: что методы onUpgrade запускают один для вашего вспомогательного объекта sqlite, и вам нужно обработать все таблицы в нем.
Итак, что рекомендуется на Upgrade:
- BeginTransaction
- запустить создание таблицы с
if not exists
(мы делаем обновление, поэтому таблица может еще не существовать, она не сможет изменить и удалить) - внести в список существующие столбцы
List<String> columns = DBUtils.GetColumns(db, TableName);
- резервная таблица (
ALTER table " + TableName + " RENAME TO 'temp_" + TableName
) - создать новую таблицу (новейшая схема создания таблицы)
- получить пересечение с новыми столбцами, на этот раз столбцы взяты из обновленной таблицы (
columns.retainAll(DBUtils.GetColumns(db, TableName));
) - восстановить данные (
String cols = StringUtils.join(columns, ","); db.execSQL(String.format( "INSERT INTO %s (%s) SELECT %s from temp_%s", TableName, cols, cols, TableName));
) - удалить резервную копию таблицы (
DROP table 'temp_" + TableName
) - setTransactionSuccessful
,
public static List<String> GetColumns(SQLiteDatabase db, String tableName) {
List<String> ar = null;
Cursor c = null;
try {
c = db.rawQuery("select * from " + tableName + " limit 1", null);
if (c != null) {
ar = new ArrayList<String>(Arrays.asList(c.getColumnNames()));
}
} catch (Exception e) {
Log.v(tableName, e.getMessage(), e);
e.printStackTrace();
} finally {
if (c != null)
c.close();
}
return ar;
}
public static String join(List<String> list, String delim) {
StringBuilder buf = new StringBuilder();
int num = list.size();
for (int i = 0; i < num; i++) {
if (i != 0)
buf.append(delim);
buf.append((String) list.get(i));
}
return buf.toString();
}
Разве что-то вроде следующего не будет легче в подавляющем большинстве случаев? Просто добавьте новый столбец для каждого обновления версии:
private static final String DATABASE_ALTER_TEAM_1 = "ALTER TABLE "
+ TABLE_TEAM + " ADD COLUMN " + COLUMN_COACH + " string;";
private static final String DATABASE_ALTER_TEAM_2 = "ALTER TABLE "
+ TABLE_TEAM + " ADD COLUMN " + COLUMN_STADIUM + " string;";
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < 2) {
db.execSQL(DATABASE_ALTER_TEAM_1);
}
if (oldVersion < 3) {
db.execSQL(DATABASE_ALTER_TEAM_2);
}
}
Подробнее об этом читайте в этом блоге.
Вот как я обновляю свою базу данных.
В предыдущей версии моего приложения gameType
столбец не существует. В новой версии это так.
void upgradeDatabase() throws IOException {
try {
String column = DatabaseConstants.GAME_TYPE_COLUMN_NAME; // gameType
String table = DatabaseConstants.RECORDS_TABLE;
String query = String.format("SELECT %s FROM %s LIMIT 1", column, table);
database.rawQuery(query, null);
return;
}
catch (Exception e) {
// Column doesn't exist. User had old version of app installed, so upgrade database.
}
// Save all old data
String query = "SELECT * FROM " + DatabaseConstants.USERS_TABLE;
Cursor c = database.rawQuery(query, null);
List<List<Object>> values1 = new ArrayList<List<Object>>();
if (c.moveToFirst()) {
while (!c.isAfterLast()) {
List<Object> record = new ArrayList<Object>();
record.add(c.getInt(0));
record.add(c.getString(1));
values1.add(record);
c.moveToNext();
}
}
c.close();
query = "SELECT * FROM " + DatabaseConstants.RECORDS_TABLE;
c = database.rawQuery(query, null);
List<List<Object>> values2 = new ArrayList<List<Object>>();
if (c.moveToFirst()) {
while (!c.isAfterLast()) {
List<Object> record = new ArrayList<Object>();
record.add(c.getInt(0));
record.add(c.getInt(1));
record.add(c.getInt(2));
record.add(c.getInt(3));
values2.add(record);
c.moveToNext();
}
}
c.close();
// Copy empty database with new schema
copyDatabase();
// Restore all old data
for (List<Object> record : values1) {
ContentValues cv = new ContentValues();
cv.put(DatabaseConstants.ID_COLUMN_NAME, (Integer) record.get(0));
cv.put(DatabaseConstants.USERNAME_COLUMN_NAME, record.get(1).toString());
database.insert(DatabaseConstants.USERS_TABLE, null, cv);
}
for (List<Object> record : values2) {
ContentValues cv = new ContentValues();
cv.put(DatabaseConstants.USER_ID_COLUMN_NAME, (Integer) record.get(0));
cv.put(DatabaseConstants.GAME_TYPE_COLUMN_NAME, GameType.CLASSIC.name());
cv.put(DatabaseConstants.WINS_COLUMN_NAME, (Integer) record.get(1));
cv.put(DatabaseConstants.LOSSES_COLUMN_NAME, (Integer) record.get(2));
cv.put(DatabaseConstants.TIES_COLUMN_NAME, (Integer) record.get(3));
database.insert(DatabaseConstants.RECORDS_TABLE, null, cv);
}
}
Вот код для копирования файла базы данных. База данных изначально пуста, и я создал ее вне своего приложения. (Я использовал программу под названием Navicat для SQLite.)
public DatabaseHelper(Context context) {
super(context, DatabaseConstants.DATABASE_NAME, null, 1);
this.context = context;
databasePath = context.getDatabasePath(DatabaseConstants.DATABASE_NAME).getPath();
}
void copyDatabase() throws IOException {
InputStream is = context.getAssets().open(DatabaseConstants.DATABASE_NAME); // data.db
OutputStream os = new FileOutputStream(databasePath);
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) > 0) {
os.write(buffer, 0, length);
}
// Close the streams.
os.flush();
os.close();
is.close();
}