Android SQLite query super slow - android

I have a database with one table and about 350.000 rows in there. When I try to query them it's super slow on my Nexus4. The cursor.moveToFirst() method call takes about 500ms. If I run the exact same query on the same database in my terminal using sqlite3 it takes about 10ms. So I suppose that my Cursor is not using my index...
String table = "table";
String[] columns = {
"col1",
"col2"
};
String predicate = "col1 like '?' and col2 = ?";
String[] values = {
value1+"%%",
value2
};
String orderBy = "col3 ASC";
String limit = "5";
Cursor c = this.mDatabase.query(table, columns, predicate, values, null, null, orderBy, limit);
if (c != null) {
long start = System.currentTimeMillis();
c.moveToFirst();
long duration = System.currentTimeMillis() - start;
// The duration here is super slow
....
}
The database has an index on col1 and col2. And like I said... when executing the sql in the command line it's super fast... about 10ms vs 500 on my phone...
Any ideas on how I can improve the speed on that query? Is seems as if the index isn't used?!?
Thanks

Related

SQLite multiple query - best design approach to improve performance

i'm new to programming with DB and not an expert in android programming so bear with me!
I have a DB with 2 tables (A and B) where I get a list of ID from the table A (1 to 100 rows) and get rows from table B for each id I got from table A giving me a total of 400 to 800 rows from table B.
This approach is not ideal for my app as it take 4 to 10+ seconds to process where I would ideally want less than 1s.
I'm trying to understand what would be best in a case like this.
Would having less row but more content in each help?
the DB is aprox 15mb Would loading it all in the background be better (I guess not as it would mean 5 min+ of loading)?
what is most expensive / have the worst performance: queries, cursor iteration, loading data from a field?
I have no specific index with my DB, would generating some help? If so how can I do that?
I currently have the bellow code collecting my data:
Long start = System.currentTimeMillis();
while (cursorTableA.moveToNext()) {
long id = cursorTableA.getLong(0);
int paragraphNunber = cursorTableA.getInt(1);
boolean isPoetry = (cursorTableA.getInt(2) != 0);
Paragraph mParagraph = new Paragraph(id, paragraphNunber,isPoetry);
// GET WORDS
String selectionWords = DbContract.WordsEntry.CONNECTED_PARAGRAPH_ID+ " = ?";
String[] selectionWordsArgs = new String[]{ Long.toString(paragraphNunber) };
String sortOrder = DbContract.WordsEntry.WORD_NUMBER+ " ASC";
Cursor cursorTableB = db.query(
DbContract.WordsEntry.TABLE_NAME,
DbContract.WordsEntry.DEFAULT_QUERY_COLUMNS_TO_RETURN,
selectionWords, selectionWordsArgs, null, null, sortOrder
);
while (cursorTableB.moveToNext()) {
String word = cursorTableB.getString(0);
String thesaurusRef = cursorTableB.getString(1);
String note = cursorTableB.getString(2);
mParagraph.addWord(new Word(word,thesaurusRef,note));
}
cursorTableB.close();
long finish = System.currentTimeMillis();
timeElapsed = finish- start;
System.out.println("DB Query => timeElapsed(s): "+(timeElapsed/1000)+" timeElapsed(ms): "+timeElapsed);
}
I should add that my DB is only used in read only, I copy it on first execution to the data/data/.../databases folder I never write in it.
I suggest you use a JOIN of your two tables and get just one cursor to iterate instead of get two and nest them. Try something like this:
final String MY_QUERY = "SELECT * FROM table_a a INNER JOIN table_b b ON a.id = b.other_id";
Cursor cursorTable = db.rawQuery(MY_QUERY, null);
while (cursorTable.moveToNext()) {
String id = cursorTable.getString(0);
String paragraphNunber = cursorTable.getString(1);
boolean isPoetry = (cursorTable.getInt(2) != 0);
String word = cursorTable.getString(3);
String thesaurusRef = cursorTable.getString(4);
String note = cursorTable.getString(5);
}
cursorTableB.close();
long finish = System.currentTimeMillis();
timeElapsed = finish- start;
System.out.println("DB Query => timeElapsed(s): "+(timeElapsed/1000)+"
timeElapsed(ms): "+timeElapsed);
Of course you will have to handle the logic of the creation of the object Paragraph as you will now have a cursor with as many rows as in tableB and "paragraph_id".

Can't extract integer from cursor after preforming sqlite query in Android

I'm working on an small android app that maintains a small database of tools which I lend out to other people.
As part of the app, I am incorporating an sqllite database, where I am having a bit of trouble performing queries and working with cursors once the queries have been executed.
The code in question is as follows:
String COLUMN_NAME = "toolName";
String[] columns = { COLUMN_NAME };
String selection = COLUMN_NAME + " =?";
String[] selectionArgs = {tool};
Cursor cursor = mToolDb.query(ToolStatisticContract.ToolStatisticEntry.TABLE_NAME, columns,
selection, selectionArgs, null, null, null, null);
return Integer.parseInt(cursor.getString(3));
The contract for the database is as follows:
public class ToolStatisticContract {
public static final class ToolStatisticEntry implements BaseColumns {
public static final String TABLE_NAME = "tooltable";
public static final String COLUMN_TOOL_NAME = "toolName";
public static final String COLUMN_LIFESPAN = "lifespan";
public static final String COLUMN_USAGE = "usageTime";
}
}
I am essentially trying to extract out the value from COLUMN_USAGE, which seems to be producing errors with regards to parsing the value to an integer. The value in the COLUMN is actually an integer typecasted as a String from a previous segment of code, so I'm fairly certain the error is encompasssed with the code snippets above.
Thanks again in advance for all your help!
The code in question is as follows
The net SQL statement is something like:
SELECT toolName FROM tooltable WHERE toolName = ?
And there is no column with index 3, since you are only returning 1 column.
You need to:
Have usageTime in your column list (COLUMNS)
Move the Cursor to a valid row (as it initially is positioned before the first row)
Pass getInteger() the value that lines up with COLUMNS to retrieve usageTime
You could use the following. This uses null instead of columns, which will get all columns (i.e. resolves to SELECT * FROM table). It checks that a row has been returned and only then does it try to get the data. It also closes the cursor (you should close a cursor when done with it). It uses cursor.getInt() to get the integer value rather than convert it from a string to int. It assumes that you'll only get 1 row (if no rows then 0 will be returned).
int returnvalue = 0;
String COLUMN_NAME = "toolName";
String[] columns = { COLUMN_NAME };
String selection = COLUMN_NAME + " =?";
String[] selectionArgs = {tool};
Cursor cursor = mToolDb.query(ToolStatisticContract.ToolStatisticEntry.TABLE_NAME, null,
selection, selectionArgs, null, null, null, null);
if (cursor.getCount() > 0) {
cursor.moveToFirst();
returnvalue = cursor.getInt(2);
//or returnvalue = Integer.parseInt(cursor.getString(2));
}
cursor.close();
return returnvalue;
Note! I haven't checked this just coded it from memory, so apologies for the odd mistake.
To do the above using specific columns then you could use:-
String COLUMN_NAME = "toolName";
String[] columns = { COLUMN_USAGE };
String selection = COLUMN_NAME + " =?";
String[] selectionArgs = {tool};
Cursor cursor = mToolDb.query(ToolStatisticContract.ToolStatisticEntry.TABLE_NAME, columns,
selection, selectionArgs, null, null, null, null);
In which case the column index would be 0 (that is the index is according to the column's in the cursor). However it might be better to use, the following which gets the column index according to the column's name:-
cursor.getInt(cursor.getColumnIndex(COLUMN_USAGE);
The easiest way to read a single value from the database is to use a helper function that allows you to avoid having to handle cursor objects:
String query = "SELECT usageTime FROM tooltable WHERE toolName = ?";
String[] selectionArgs = { tool };
long returnvalue = DatabaseUtils.longForQuery(mToolDb, query, selectionArgs);

Android SqlLite check if two values exist in 2 different columns

I am developing an application where the user inputs title and the date. I want to prevent the duplicated titles being inputted on the same day in to database. I am checking if the title exists on the selected date. However my query seems not to work and i don't know why, the application just crashes.Is this query correct? Can someone help?
public boolean checkExist(String title, String date) {
SQLiteDatabase db = this.getWritableDatabase();
Cursor c = db.rawQuery("SELECT * FROM "+TABLE_NAME+" WHERE "+TITLE+"=?" +"AND" + DATE+"=?", new String[] {title,date});
boolean exists = c.moveToFirst();
c.close();
return exists;
}
One issue that you have is that c.moveToFirst will always fail if a match does not exist as you are trying to move to a row in an empty cursor.
The resolution is to not use c.moveToFirst and instead get the count of the rows and then set the return value accordingly.
e.g.
public boolean checkExist(String title, String date) {
SQLiteDatabase db = this.getWritableDatabase();
Cursor c = db.rawQuery("SELECT * FROM "+TABLE_NAME+" WHERE "+TITLE+"=?" +"AND" + DATE+"=?", new String[] {title,date});
boolean exists = c.getCount() > 0;
c.close();
return exists;
}
The second issue is that the query itself is wrong as you do not have spaces either side of the AND keyword. That is instead of
Cursor c = db.rawQuery("SELECT * FROM "+TABLE_NAME+" WHERE "+TITLE+"=?" +"AND" + DATE+"=?", new String[] {title,date});
You should have
Cursor c = db.rawQuery("SELECT * FROM "+TABLE_NAME+" WHERE "+TITLE+"=?" +" AND " + DATE+"=?", new String[] {title,date});
Personally, I setup constants for SQL keywords that include the space and then use these. So I'd have something along the lines of +TITLE+"=?" + SQLAND + DATE+"=?". Where SQLAND would be defined along the lines of String SQLAND=" AND ";
PS look at Cricket_007's answer, the code is neater/better it's easier to read.
Your spacing is off. TITLE+"=?" +"AND" + DATE becomes TITLE=?ANDDATE=?
I would suggest this. See DatabaseUtils.queryNumEntries
public boolean checkExist(String title, String date) {
SQLiteDatabase db = getReadableDatabase();
String[] args = new String[] {title,date};
String filter = String.format("%s=? AND %s=?", TITLE, DATE);
return DatabaseUtils.queryNumEntries(db, TABLE_NAME, filter, args) > 0;
}
you should be using c.getCount() instead of c.moveToFirst()
if the value is greater than 0, then it exists

Update table using rawQuery() method does not work

I tried the following SQLite query:
int idServizo = 150;
String whereClause = id_servizio+" = '"+idServizio+" ' ";
ContentValues cv = new ContentValues();
cv.put("sync", 1);
int r = dbManager.updateTable("myTable", cv, whereClause);
Where fields sync and id_servizio are both integer.
The method updateTable is:
public int updateTable(String table, ContentValues values, String whereClause){
int r = mDb.update(table, values, whereClause, null);
return r;
}
// mDb is SQLiteDatabase object
All this works good.
But if I try this with the rawQuery() method:
public Cursor RawQuery(String sqlQuery, String[] columns){
return mDb.rawQuery(sqlQuery, columns);
}
The table is not updated! even if no error occurs.
int idServizo = 150;
String updateQuery ="UPDATE myTable SET sync = 1 WHERE id_servizio = "+idServizio;
dbManager.RawQuery(updateQuery, null);
Why does this not work?
This is because when a rawQuery is executed cursor is returned. Without the call to cursor.moveToFirst() and cursor.close() the database won't get updated.
int idServizo = 150;
String updateQuery ="UPDATE myTable SET sync = 1 WHERE id_servizio = "+idServizio;
Cursor c= dbManager.rawQuery(updateQuery, null);
c.moveToFirst();
c.close();
I dont know the need to call moveToFirst() but this works fine and the database gets updated.
Problem solved.
http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html
Can't works because rawQuery runs the provided SQL and returns a Cursor over the result set.
If I want to return a table I have to use rawQuery, otherwise no!
Increase the value of a record in android/sqlite database
You should use db.execSQL() instead db.rawQuery().
Instead of doing this:
Cursor c= dbManager.RawQuery(updateQuery, null);
c.moveToFirst();
c.close();
You just need this:
dbManager.execSQL(updateQuery, null);
----------------------------------------------------------------------------
Posting answer because sometimes many people (like me) not reading comments.
Most popular answer is not correct but Yaqub Ahmad's comment is correct.
Answer from CommonsWare explained in this answer:
rawQuery() is for SQL statements that return a result set. Use
execSQL() for SQL statements, like INSERT, that do not return a result
set.
----------------------------------------------------------------------------
Documentation for execSQL:
public void execSQL (String sql)
Execute a single SQL statement that is NOT a SELECT or any other SQL statement that returns data.
Documentation for rawQuery:
public Cursor rawQuery (String sql,
String[] selectionArgs)
Runs the provided SQL and returns a Cursor over the result set.
Your update call formats the ID as string, while the rawQuery call formats is as number.
Assuming that the ID in the table indeed is a string, use:
String updateQuery = "UPDATE myTable SET sync = 1 WHERE id_servizio = '" + idServizio + "'";

problem with sql query in android application,

I have a database which has a column of dates in milliseconds. I'm trying to perform a query which allows me to retrieve only the dates that are greater than the current system time and in ascending order.
I've tried this query, but it causes my app to force close. I'm not sure whether the problem is the where clause part or the orderby clause part or both.
Here is what I done, your help would be most appreciative.
long lowestDate = 0;
long currentTime = System.currentTimeMillis();
String CT = Long.toString(currentTime);
String[]c = {CT};
mDb = mDbHelper.getReadableDatabase();
String[] dates = {KEY_DT};
Cursor getDate = mDb.query(DATABASE_TABLE, dates, dates + "> ?", c, null, null, dates + "ASC");
getDate.moveToFirst();
while(getDate.isAfterLast() == false)
{
lowestDate = getDate.getLong(0);
getDate.moveToNext();
}
It seems you are using query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)
selection argument description:
A filter declaring which rows to return, formatted as an SQL WHERE clause (excluding the WHERE itself). Passing null will return all rows for the given table.
You should correct that. I think that's an issue, because you are calling toString() method on dates variable, which is an Array.

Categories

Resources