It has been a while since I've dealt with SQL. Recently we wanted to see if having an SQLlite database in Android would provide a performance boost. The idea is to prefetch the day's TV guide and load it into an SQL database during the night. And then when the user starts the app, we would first look at the database to see if we already have the data. If so we would load that instead of going through a network request. There may also be a performance boost if the query was written properly. Another benefit over network request is that the REST resource is in JSON format and needs to be parsed, whereas translating from a database row into a POJO potentially is faster. It also takes us later into using CusorAdapter and load data on demand. For those reason it's worth trying it out.
By the way, great site to practice your SQL is http://www.w3schools.com/sql/trysql.asp
The first thing I need to do is store all my TV Program POJO into the database. And the first step is to create a table. To create a table, I execute an SQL statement such as this:
CREATE TABLE program (id INTEGER PRIMARY KEY, name TEXT NOT NULL, start_time NUMERIC, end_time NUMERIC)
It reads that we're going to be creating a new table with 4 fields: an ID, name, start and stop time. This table will have the name program. Each row in this table must have at least an ID and a display name.
In Android, we do this by creating a class that extends SQLiteOpenHelper and overriding the method
public void onCreate(SQLiteDatabase db);
In that function we can execute the query
db.execSQL(createTableQueryStr);
The SQLiteOpenHelper is a class that manage database creation and help with version control.
When you extends SQLiteOpenHelper, you are also required to implement onUpgrade method. Here, we'll simply delete the table and erase all previous data. The query is:
DROP TABLE IF EXISTS program
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ProgramsDbHelper extends SQLiteOpenHelper { | |
private static final String DATABASE_NAME = "programs.db"; | |
private static final int DATABASE_VERSION = 1; | |
public static final String TABLE_PROGRAM = "program"; | |
public static final String COLUMN_ID = "id"; | |
public static final String COLUMN_NAME = "name"; | |
public static final String COLUMN_START_TIME = "start_time"; | |
public static final String COLUMN_END_TIME = "end_time"; | |
public ProgramsDbHelper(Context context) { | |
super(context, DATABASE_NAME, null, DATABASE_VERSION); | |
} | |
@Override | |
public void onCreate(SQLiteDatabase db) { | |
StringBuilder createTableQuery = new StringBuilder(); | |
createTableQuery.append("CREATE TABLE ").append(TABLE_PROGRAM).append(" ("); | |
createTableQuery.append(COLUMN_ID).append(" INTEGER PRIMARY KEY, "); | |
createTableQuery.append(COLUMN_NAME).append(" TEXT NOT NULL, "); | |
createTableQuery.append(COLUMN_START_TIME).append(" NUMERIC, "); | |
createTableQuery.append(COLUMN_END_TIME).append(" NUMERIC"); | |
createTableQuery.append(")"); | |
db.execSQL(createTableQuery.toString()); | |
} | |
/** Uncomment this if we use foreign key constraint in any of our table | |
@Override | |
public void onConfigure(SQLiteDatabase db) { | |
super.onConfigure(db); | |
db.setForeignKeyConstraintsEnabled(true); | |
} | |
*/ | |
@Override | |
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { | |
db.execSQL("DROP TABLE IF EXISTS " + TABLE_PROGRAM); | |
onCreate(db); | |
} | |
} |
CREATE TABLE genre (program_id INTEGER, genre_name TEXT, FOREIGN KEY (program_id) REFERENCES program(id))
A database query always return a Cursor. It's the mechanism for which you retrieve the result. It doesn't load all the results in memory, only what the cursor is pointing at. This is good if you pass this cursor to your view so that you only need to get the data that is displayed in the UI, but it's not always possible depending on how the app is structured. So for convinence, I've created some method to translate between table row and POJO and vise versa.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Program { | |
public String id; | |
public String name; | |
public long start_time; | |
public long end_time; | |
public static Program toProgram(Cursor cursor) { | |
Program p = new Program(); | |
p.id = cursor.getString(cursor.getColumnIndex(ProgramsDbHelper.COLUMN_ID)); | |
p.name = cursor.getString(cursor.getColumnIndex(ProgramsDbHelper.COLUMN_NAME)); | |
p.start_time = cursor.getLong(cursor.getColumnIndex(ProgramsDbHelper.COLUMN_START_TIME)); | |
p.end_time = cursor.getLong(cursor.getColumnIndex(ProgramsDbHelper.COLUMN_END_TIME)); | |
return p; | |
} | |
public static ContentValues toDbValues(Program program) { | |
ContentValues values = new ContentValues(); | |
values.put(ProgramsDbHelper.COLUMN_ID, program.id); | |
values.put(ProgramsDbHelper.COLUMN_NAME, program.name); | |
values.put(ProgramsDbHelper.COLUMN_START_TIME, program.start_time); | |
values.put(ProgramsDbHelper.COLUMN_END_TIME, program.end_time); | |
return values; | |
} | |
} |
Note the use of the insertWithOnConflict method where if a row already exist, I'll replace the values instead of throwing an exception.
The other thing to note is that if you need to use the DISTINCT function of SQL to query all unique names for example. Let's say I want to know programs of my entire TV Guide during prime time during the week, but I don't want to see the evening news show up 5 times, I would do a query with DISTINCT. To do that in SQLiteDatabase in Android, you use the overloaded method for query with the boolean as first argument.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ProgramsDataSource { | |
private ProgramsDbHelper dbHelper; | |
private SQLiteDatabase database; | |
private String[] allColumns = { | |
ProgramsDbHelper.COLUMN_ID, | |
ProgramsDbHelper.COLUMN_NAME, | |
ProgramsDbHelper.COLUMN_START_TIME, | |
ProgramsDbHelper.COLUMN_END_TIME, | |
}; | |
public ProgramsDataSource(Context context) { | |
dbHelper = new ProgramsDbHelper(context); | |
} | |
public void open() throws SQLException { | |
database = dbHelper.getWritableDatabase(); | |
} | |
public void close() { | |
dbHelper.close(); | |
} | |
public synchronized void insertPrograms(List<Program> programs) { | |
try { | |
database.beginTransaction(); | |
for (Program program : programs) { | |
ContentValues values = ProgramsMapper.toDbValues(program); | |
database.insertWithOnConflict(ProgramsDbHelper.TABLE_PROGRAM, null, values, SQLiteDatabase.CONFLICT_REPLACE); | |
} | |
database.setTransactionSuccessful(); | |
} catch (Exception e) { | |
e.printStackTrace(System.err); | |
} finally { | |
database.endTransaction(); | |
} | |
} | |
public synchronized void removePrograms(long olderThan) { | |
try { | |
database.beginTransaction(); | |
//TODO: remove entry in genre table | |
database.delete(ProgramsDbHelper.TABLE_PROGRAM, ProgramsDbHelper.COLUMN_START_TIME + " < " + olderThan, null); | |
database.setTransactionSuccessful(); | |
} catch (Exception e) { | |
e.printStackTrace(System.err); | |
} finally { | |
database.endTransaction(); | |
} | |
} | |
public synchronized List<Program> getPrograms(long start_time, long end_time) { | |
assert(end_time > start_time); | |
StringBuilder selection = new StringBuilder(); | |
selection.append(ProgramsDbHelper.COLUMN_START_TIME).append(" < ").append(end_time); | |
selection.append(" AND "); | |
selection.append(ProgramsDbHelper.COLUMN_END_TIME).append(" > ").append(start_time); | |
Cursor cursor = database.query(ProgramsDbHelper.TABLE_PROGRAM, allColumns, | |
selection.toString(), null, null, null, ProgramsDbHelper.COLUMN_START_TIME ); | |
cursor.moveToFirst(); | |
List<Program> programs = new ArrayList<Program>(); | |
while (!cursor.isAfterLast()) { | |
Program program = ProgramsMapper.toProgram(cursor); | |
programs.add(program); | |
cursor.moveToNext(); | |
} | |
return programs; | |
} | |
} |
WebRep
currentVote
noRating
noWeight