121

I've already created sqlite tables for my app, but now I want to add a new table to the database.

I changed the DB version as below

private static final int DATABASE_VERSION = 2;

and Added string to create table

private static final String DATABASE_CREATE_color = 
   "CREATE TABLE IF NOT EXISTS files(color text, incident_id text)";

onCreate and onUpgrade as below:

@Override
    public void onCreate(SQLiteDatabase database) {
        database.execSQL(DATABASE_CREATE_incident);
        database.execSQL(DATABASE_CREATE_audio);
        database.execSQL(DATABASE_CREATE_video);
        database.execSQL(DATABASE_CREATE_image);

    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        //drop table and add new tables when version 2 released.
        db.execSQL(DATABASE_CREATE_color);

    }

But for some reason the new table is not being created. What am I doing wrong?

Jeff Axelrod
  • 27,676
  • 31
  • 147
  • 246
Jay Mayu
  • 17,023
  • 32
  • 114
  • 148
  • [This](https://stackoverflow.com/a/3424444/3681880) is another interesting solution, but so far the most robust version I've seen is [here](https://riggaroo.co.za/android-sqlite-database-use-onupgrade-correctly/). – Suragch Feb 26 '18 at 07:35

6 Answers6

289

1. About onCreate() and onUpgrade()

onCreate(..) is called whenever the app is freshly installed. onUpgrade is called whenever the app is upgraded and launched and the database version is not the same.

2. Incrementing the db version

You need a constructor like:

MyOpenHelper(Context context) {
   super(context, "dbname", null, 2); // 2 is the database version
}

IMPORTANT: Incrementing the app version alone is not enough for onUpgrade to be called!

3. Don't forget your new users!

Don't forget to add

database.execSQL(DATABASE_CREATE_color);

to your onCreate() method as well or newly installed apps will lack the table.

4. How to deal with multiple database changes over time

When you have successive app upgrades, several of which have database upgrades, you want to be sure to check oldVersion:

onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
   switch(oldVersion) {
   case 1:
       db.execSQL(DATABASE_CREATE_color);
       // we want both updates, so no break statement here...
   case 2:
       db.execSQL(DATABASE_CREATE_someothertable); 
   }
}

This way when a user upgrades from version 1 to version 3, they get both updates. When a user upgrades from version 2 to 3, they just get the revision 3 update... After all, you can't count on 100% of your user base to upgrade each time you release an update. Sometimes they skip an update or 12 :)

5. Keeping your revision numbers under control while developing

And finally... calling

adb uninstall <yourpackagename>

totally uninstalls the app. When you install again, you are guaranteed to hit onCreate which keeps you from having to keep incrementing the database version into the stratosphere as you develop...

Mike Cluck
  • 31,869
  • 13
  • 80
  • 91
Jonathan Schneider
  • 26,852
  • 13
  • 75
  • 99
  • 5
    Regarding #4: Wouldn't it be a better idea to use the `oldVersion` argument passed? If any upgrade statements are repeatable, you may end up repeating them on a mostly-up-to-date database. If one of the statements is to truncate a table, that would be very bad. – Greyson Nov 15 '11 at 09:04
  • 3
    @Greyson: Great point! Honestly, I feel a bit dumb for never really thinking about it. Sometimes I think we get in the habit of using the arguments we want and ignoring the rest! – Jonathan Schneider Nov 15 '11 at 09:07
  • I noted the point regarding 'onCreate()' thanks your answer is helpful too. – Jay Mayu Nov 15 '11 at 10:27
  • I do not see how simply leaving the breaks out of the switch statement helps if a user is jumping multiple versions at once (v2.0 to v5.0). Should you not wrap it with some kind of loop like: while (oldVersion + 1 <= newVersion) – John Dec 12 '14 at 16:54
  • As long as the versions are in order, if the user's old version is 2, the code starts at case 2 and simply keeps falling through each successive case in order thereafter. – Jonathan Schneider Dec 12 '14 at 21:41
  • What I need to do when database name is changed? – Nayan Jan 01 '15 at 15:59
  • 1
    You control the database, why would you change the name? – Jonathan Schneider Jan 01 '15 at 21:27
  • I was going to raise the same point as @John. Surely you only want to upgrade to newVersion and not to the latest version as yours will do? Or will newVersion always be the latest? – Stuart Robertson Mar 12 '15 at 15:08
  • 4
    `newVersion` is kinda useless, as you always set the current database version anyway in the constructor (see part 2) and it will always match. The key idea here is that you don't want to just upgrade from wherever the user is straight to `newVersion` without passing through every other incremental upgrade in between. – Jonathan Schneider Mar 12 '15 at 16:55
  • hello @jkschneider nice Explanation i have a question suppose developer has update more time DB version to 3 and added one more table now any new user install app that app have DB version 2 than case 1 will be never executed what to do with that? because new user have old DB version is 2, any help with this ? how to create new table in version 1 ( case 1) in your example – MilapTank Jul 06 '15 at 12:25
  • @milapTank - create logic is distinct from update. if a new user starts at version 2, they will never execute case 1 or 2 – Jonathan Schneider Jul 06 '15 at 17:26
  • 1
    1 way that you can get around having to uninstall app is just to name the debug app -debug. This way if you actually use your app and want to keep around any saved data but also test some new features you can do so by keeping them totally separate. – Nick.D Oct 27 '15 at 05:40
  • @jkschneider. for point 4, still I have to create the new table in onCreate for new users? thanks – trocchietto Jul 09 '16 at 19:39
  • 1
    Yes, `onUpgrade` is only called when the app is launched and the database version is not the same. `onCreate` is still necessary for new users. – Jonathan Schneider Jul 10 '16 at 04:36
  • @jkschneider when i use method 4, it returns me the error that the table already exists. My current version is 1, and i recently updated to v2. So to fix it i had to do a prefix-increment of oldVersion in the switch case so it will jump to case 2 instead of executing case 1 which i already have installed. May I know why is that so? – Zhi Kai Jul 15 '16 at 01:42
  • 1
    @kai if you could put the before (v1) and after (v2) code in a gist, I'm sure we could spot what is wrong. – Jonathan Schneider Jul 15 '16 at 22:14
  • @jkschneider https://gist.github.com/AZhiKai/da7a48ddfd147b0d55f3374cb5f1d344 (v1) and https://gist.github.com/AZhiKai/9fd1f77dc5f95205421c52b69c65f07f (v2) – Zhi Kai Jul 16 '16 at 12:14
  • 2
    @kai The `CREATE_READINGS` logic should never be in onUpgrade, since it was in the `onCreate` method of your first version. Think of the cases in the `onUpgrade` switch as "I am upgrading FROM `oldVersion`". You wouldn't create the readings table if you were upgrading from version 1, as it should already exist. Hopefully this makes sense... – Jonathan Schneider Jul 17 '16 at 05:25
  • This answer is superior to many others around Stack Overflow. However, it could run into some problems if the variable names in `onUpgrade` get changed. See [this article](https://riggaroo.co.za/android-sqlite-database-use-onupgrade-correctly/) for details. – Suragch Feb 26 '18 at 07:24
  • @jkschneider, how about If I am using the [SQLiteAssetHelper](https://github.com/jgilfelt/android-sqlite-asset-helper)? Can you help with this as the `onCreate()` method is final and can't be overriden by the lib! – blueware Mar 21 '19 at 13:19
  • 1
    @blueware Wish I could help, but it's been years since I've done Android work. – Jonathan Schneider Mar 21 '19 at 23:08
  • Ah, switch with a fall through. What a tricky solution, but it works. – EpicPandaForce Mar 30 '19 at 06:54
  • the oldVersion needs to be incremented in each case for it to pass the upcoming cases. eg. if oldVersion is 1 and newVersion is 3. oldVersion should increment at case 2 to pass case 3. @jkschneider – Beulah Ana May 24 '19 at 13:03
  • @BeulahAna that is not true. without break statements, execution keeps falling through subsequent cases whether or not the tested variable would have matched that case. – Jonathan Schneider May 26 '19 at 23:07
8

Your code looks correct. My suggestion is that the database already thinks it's upgraded. If you executed the project after incrementing the version number, but before adding the execSQL call, the database on your test device/emulator may already believe it's at version 2.

A quick way to verify this would be to change the version number to 3 -- if it upgrades after that, you know it was just because your device believed it was already upgraded.

Greyson
  • 3,598
  • 1
  • 21
  • 22
  • Then, as expected, your code was fine; just not when it was run incrementally. Remember to add the table creation to `onCreate()` like jkschneider pointed out. – Greyson Nov 15 '11 at 09:08
2

You can use SQLiteOpenHelper's onUpgrade method. In the onUpgrade method, you get the oldVersion as one of the parameters.

In the onUpgrade use a switch and in each of the cases use the version number to keep track of the current version of database.

It's best that you loop over from oldVersion to newVersion, incrementing version by 1 at a time and then upgrade the database step by step. This is very helpful when someone with database version 1 upgrades the app after a long time, to a version using database version 7 and the app starts crashing because of certain incompatible changes.

Then the updates in the database will be done step-wise, covering all possible cases, i.e. incorporating the changes in the database done for each new version and thereby preventing your application from crashing.

For example:

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    switch (oldVersion) {
    case 1:
        String sql = "ALTER TABLE " + TABLE_SECRET + " ADD COLUMN " + "name_of_column_to_be_added" + " INTEGER";
        db.execSQL(sql);
        break;

    case 2:
        String sql = "SOME_QUERY";
        db.execSQL(sql);
        break;
    }

}
Peter Hall
  • 53,120
  • 14
  • 139
  • 204
Vijesh Jat
  • 131
  • 4
  • If you remove those break statements you won't need a loop – Tash Pemhiwa Jun 23 '17 at 12:10
  • but oldVersion has to incremented in each case to pass the next case @TashPemhiwa – Beulah Ana May 24 '19 at 13:06
  • The reason a switch statement requires a break is that it is possible to run multiple cases at once - and this will be the case even if the case condition is not met, @BeulahAna – Tash Pemhiwa May 29 '19 at 09:15
  • If you add break and some db have old or recent version then your query can be failed so break is not required example alter table if some column already alter in some db version then your query may be failed as per loss sequence of db version – Neeraj Singh Jul 25 '19 at 11:09
2

@jkschneider's answer is right. However there is a better approach.

Write the needed changes in an sql file for each update as described in the link https://riggaroo.co.za/android-sqlite-database-use-onupgrade-correctly/

from_1_to_2.sql

ALTER TABLE books ADD COLUMN book_rating INTEGER;

from_2_to_3.sql

ALTER TABLE books RENAME TO book_information;

from_3_to_4.sql

ALTER TABLE book_information ADD COLUMN calculated_pages_times_rating INTEGER;
UPDATE book_information SET calculated_pages_times_rating = (book_pages * book_rating) ;

These .sql files will be executed in onUpgrade() method according to the version of the database.

DatabaseHelper.java

public class DatabaseHelper extends SQLiteOpenHelper {

    private static final int DATABASE_VERSION = 4;

    private static final String DATABASE_NAME = "database.db";
    private static final String TAG = DatabaseHelper.class.getName();

    private static DatabaseHelper mInstance = null;
    private final Context context;

    private DatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        this.context = context;
    }

    public static synchronized DatabaseHelper getInstance(Context ctx) {
        if (mInstance == null) {
            mInstance = new DatabaseHelper(ctx.getApplicationContext());
        }
        return mInstance;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(BookEntry.SQL_CREATE_BOOK_ENTRY_TABLE);
        // The rest of your create scripts go here.

    }


    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        Log.e(TAG, "Updating table from " + oldVersion + " to " + newVersion);
        // You will not need to modify this unless you need to do some android specific things.
        // When upgrading the database, all you need to do is add a file to the assets folder and name it:
        // from_1_to_2.sql with the version that you are upgrading to as the last version.
        try {
            for (int i = oldVersion; i < newVersion; ++i) {
                String migrationName = String.format("from_%d_to_%d.sql", i, (i + 1));
                Log.d(TAG, "Looking for migration file: " + migrationName);
                readAndExecuteSQLScript(db, context, migrationName);
            }
        } catch (Exception exception) {
            Log.e(TAG, "Exception running upgrade script:", exception);
        }

    }

    @Override
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

    private void readAndExecuteSQLScript(SQLiteDatabase db, Context ctx, String fileName) {
        if (TextUtils.isEmpty(fileName)) {
            Log.d(TAG, "SQL script file name is empty");
            return;
        }

        Log.d(TAG, "Script found. Executing...");
        AssetManager assetManager = ctx.getAssets();
        BufferedReader reader = null;

        try {
            InputStream is = assetManager.open(fileName);
            InputStreamReader isr = new InputStreamReader(is);
            reader = new BufferedReader(isr);
            executeSQLScript(db, reader);
        } catch (IOException e) {
            Log.e(TAG, "IOException:", e);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    Log.e(TAG, "IOException:", e);
                }
            }
        }

    }

    private void executeSQLScript(SQLiteDatabase db, BufferedReader reader) throws IOException {
        String line;
        StringBuilder statement = new StringBuilder();
        while ((line = reader.readLine()) != null) {
            statement.append(line);
            statement.append("\n");
            if (line.endsWith(";")) {
                db.execSQL(statement.toString());
                statement = new StringBuilder();
            }
        }
    }
}

An example project is provided in the same link also : https://github.com/riggaroo/AndroidDatabaseUpgrades

oiyio
  • 5,219
  • 4
  • 42
  • 54
  • 1
    I was just about to come here and write the same advice. I'm glad you already did it. People should definitely read the article you linked to. This is also what [Android SQLiteAssetHelper](https://github.com/jgilfelt/android-sqlite-asset-helper) recommends for upgrades. It is also what [CL.](https://stackoverflow.com/users/11654/cl) (*the* SQLite expert here on Stack Overflow) [recommends](https://stackoverflow.com/a/42361543/3681880). – Suragch Feb 26 '18 at 07:17
  • This comment what I was looking for. The sql scripts, +1 – blueware Mar 21 '19 at 13:25
1

Handling database versions is very important part of application development. I assume that you already have class AppDbHelper extending SQLiteOpenHelper. When you extend it you will need to implement onCreate and onUpgrade method.

  1. When onCreate and onUpgrade methods called

    • onCreate called when app newly installed.
    • onUpgrade called when app updated.
  2. Organizing Database versions I manage versions in a class methods. Create implementation of interface Migration. E.g. For first version create MigrationV1 class, second version create MigrationV1ToV2 (these are my naming convention)


    public interface Migration {
        void run(SQLiteDatabase db);//create tables, alter tables
    }

Example migration:

public class MigrationV1ToV2 implements Migration{
      public void run(SQLiteDatabase db){
        //create new tables
        //alter existing tables(add column, add/remove constraint)
        //etc.
     }
   }
  1. Using Migration classes

onCreate: Since onCreate will be called when application freshly installed, we also need to execute all migrations(database version updates). So onCreate will looks like this:

public void onCreate(SQLiteDatabase db){
        Migration mV1=new MigrationV1();
       //put your first database schema in this class
        mV1.run(db);
        Migration mV1ToV2=new MigrationV1ToV2();
        mV1ToV2.run(db);
        //other migration if any
  }

onUpgrade: This method will be called when application is already installed and it is updated to new application version. If application contains any database changes then put all database changes in new Migration class and increment database version.

For example, lets say user has installed application which has database version 1, and now database version is updated to 2(all schema updates kept in MigrationV1ToV2). Now when application upgraded, we need to upgrade database by applying database schema changes in MigrationV1ToV2 like this:

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    if (oldVersion < 2) {
        //means old version is 1
        Migration migration = new MigrationV1ToV2();
        migration.run(db);
    }
    if (oldVersion < 3) {
        //means old version is 2
    }
}

Note: All upgrades (mentioned in onUpgrade) in to database schema should be executed in onCreate

VIjay J
  • 736
  • 7
  • 14
0

in first db version ,table is as below:

   String createUserTable = "CREATE TABLE IF NOT EXISTS "+ user + " (" +
           USER_ID + " INTEGER PRIMARY KEY, " +
           USER_NAME + " TEXT NOT NULL, " +
           USER_FAMILY + " TEXT NOT NULL, " +
           USER_LIVING_STATUS + " INTEGER DEFAULT 0);";   

in second version on database we added a new column by name of color , that's why upgrade codes should be like below.

@Override   
public void onUpgrade(SQLiteDatabase mydb, int oldVersion, int newVersion) {
   
       if (newVersion > oldVersion) {
     
       /******************************** delete the last table and create new table with new columns  and copy    the former to new one *********************************/
       
       // here we added color column to new table
       mydb.execSQL("CREATE TABLE IF NOT EXISTS user_tmp ("
               + "user_id integer, user_name text, user_family text, user_color text,user_living_status integer);");
       mydb.execSQL("INSERT INTO user_tmp(user_id,user_name,user_family,user_living_status) SELECT * FROM user");
       mydb.execSQL("drop table user;");
       mydb.execSQL("ALTER TABLE user_tmp RENAME TO user");
       
   }
       }
Mosayeb Masoumi
  • 481
  • 4
  • 10