Slow retrieval of data in SQLITE takes a long using ContentProvider - java

I have an application in Android (running 4.0.3) that stores a lot of data in Table A. Table A resides in SQLite Database. I am using a ContentProvider as an abstraction layer above the database.
Lots of data here means almost 80,000 records per month. Table A is structured like this:
String SQL_CREATE_TABLE = "CREATE TABLE IF NOT EXISTS " + TABLE_A + " ( " +
COLUMN_ID + " INTEGER PRIMARY KEY NOT NULL" + "," +
COLUMN_GROUPNO + " INTEGER NOT NULL DEFAULT(0)" + "," +
COLUMN_TIMESTAMP + " DATETIME UNIQUE NOT NULL" + "," +
COLUMN_TAG + " TEXT" + "," +
COLUMN_VALUE + " REAL NOT NULL" + "," +
COLUMN_DEVICEID + " TEXT NOT NULL" + "," +
COLUMN_NEW + " NUMERIC NOT NULL DEFAULT(1)" + " )";
Here is the index statement:
String SQL_CREATE_INDEX_TIMESTAMP = "CREATE INDEX IF NOT EXISTS " + TABLE_A +
"_" + COLUMN_TIMESTAMP + " ON " + TABLE_A + " (" +
COLUMN_TIMESTAMP + ") ";
I have defined the columns as well as the table name as String Constants.
I am already experiencing significant slow down when retrieving this data from Table A. The problem is that when I retrieve data from this table, I first put it in an ArrayList and then I display it. Obviously, this is possibly the wrong way of doing things. I am trying to find a better way to approach this problem using a ContentProvider. But this is not the problem that bothers me.
The problem is for some reason, it takes a lot longer to retrieve data from other tables which have only upto 12 records maximum. I see this delay increase as the number of records in Table A increase. This does not make any sense. I can understand the delay if I retrieve data from Table A, but why the delay in retrieving data from other tables.
To clarify, I do not experience this delay if Table A is empty or has less than 3000 records.
What could be the problem?
EDIT: 09/14/2012 9:53 AM
To clarify, I am using a ContentProvider to manage the database. To query the data, I am using the context.getContentResolver().query method.
My query code in the ContentProvider:
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
final SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
final SQLiteDatabase db = dbHelper.getReadableDatabase();
String tableName = getTableName(uri);
queryBuilder.setTables(tableName);
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}

So this is embarrassing:
Apparently, there was one line of code somewhere during the Application Load that was retrieving data from Table A. That was what was slowing down the application as a whole.
In any case, I still have to figure out how to optimize loading data from Table A using a ContentProvider.

Related

Issue deleting row from SQL Table

I'm trying to remove a row from an SQL table using this code below. However, whenever I call this method I get this following error:
android.database.sqlite.SQLiteException: no such column: Plumber (code 1): , while compiling: DELETE FROM service WHERE name = Plumber
public boolean deleteService(String name){
SQLiteDatabase db = this.getWritableDatabase();
boolean result = false;
String query = "SELECT * FROM "
+ TABLE_SERVICE
+ " WHERE "
+ COLUMN_NAME
+ " = \""
+ name
+ "\""
;
Cursor cursor = db.rawQuery(query, null);
if(cursor.moveToFirst()){
String nameStr = cursor.getString(0);
db.delete(TABLE_SERVICE, COLUMN_NAME + " = " + nameStr, null);
cursor.close();
result = true;
}
db.close();
return result;
}
This is my table
public void onCreate(SQLiteDatabase db){
String CREATE_USERS_TABLE = "CREATE TABLE " +
TABLE_SERVICE + "("
+
COLUMN_NAME + " TEXT," +
COLUMN_RATE + " TEXT," +
COLUMN_CATEGORY + " TEXT," + COLUMN_SUBCATEGORY + " TEXT)";
db.execSQL(CREATE_USERS_TABLE);
}
First you're fetching all the rows that in COLUMN_NAME have the value name.
Next you want to delete the 1st of these rows (maybe it's the only one?) because nameStr gets the value of the 1st column which is COLUMN_NAME.
Why are you doing this?
Just execute this statement:
int number = db.delete(TABLE_SERVICE, COLUMN_NAME + " = '" + name + "'", null);
if number gets the value 0 then no rows were deleted, else it gets the number of deleted rows.
delete deletes rows not columns.
If you want to get rid of a column, you need to drop it. The SQL syntax is:
alter table table_service drop column <column_name>;
I don't know how to express this in java with the methods that you are using.
Ensure that your SQL syntax is correct, and that "Plumber" is a string with double quotes. By my experience, these errors are usually caused by an incorrect column or name.
Use this format:
DELETE FROM table_name WHERE condition(s)
SQLite browser can also help you visualize your database.

Inserting Content Values in multiple tables in Sqlite Android Application

I am brand new to multiple tables in an SQLite database and am trying to find out what the best practices are for inserting values into multiple tables. My main question is do I need to Create another ContentValues object for inserting the values into a second table? I am really stumped on how to perform the insert(). Here is what I am trying so far.
Here are the two tables and schema
/* Creating a common attributes table here. */
private static final String CREATE_COMMON_ATTRIBUTES_TABLE = "create table "
+ COMMON_ATTRIBUTES_TABLE + "(" + DBColCons.UID_COMMON_ATTRIBUTES + " integer" +
" primary key autoincrement, " + DBColCons.GPS_POINT+ " integer not null, "
+ DBColCons.EXISTING_GRADE_GPS_POINT+ " integer not null, "
+ DBColCons.COVER+ " real not null, "+ DBColCons.NOTES+ " text, "
+ DBColCons.DATE+ " text)";
/* Creating a weld table here */
private static final String CREATE_WELD_TABLE = " create table " +WELD_TABLE+ "("
+ DBColCons.UID_WELD + " integer primary key, " + DBColCons.WELD_TYPE +
" text, " + DBColCons.WELD_ID + " text, " + DBColCons.DOWNSTREAM_JOINT +
" text, " + DBColCons.UPSTREAM_JOINT + " text, " + DBColCons.HEAT_AHEAD +
" text, " + DBColCons.LENGTH_AHEAD + " real, " + DBColCons.WALL_CHANGE +
" text, " + DBColCons.WELD_WALL_THICKNESS + " text, "
+ DBColCons.WELDER_INITIALS + " text, foreign key("+DBColCons.WELD_ID+") references" +
"("+DBColCons.GPS_POINT+"))";
Here is the method I am wanting to use for the insert() with some class getters() for the Weld class, which I am passing in as a parameter.
public boolean insertWeld(Weld weld) {
/* Get a writable copy of the database */
SQLiteDatabase db = this.getWritableDatabase();
/* Content values to insert with Weld class setters */
ContentValues contentValuesWeld = new ContentValues();
try {
contentValuesWeld.put(DBColCons.GPS_POINT, weld.getGpsPoint());
contentValuesWeld.put(DBColCons.WELD_TYPE, weld.getWeldType());
contentValuesWeld.put(DBColCons.WELD_ID, weld.getWeldId());
contentValuesWeld.put(DBColCons.DOWNSTREAM_JOINT, weld.getDownstreamJoint());
contentValuesWeld.put(DBColCons.UPSTREAM_JOINT, weld.getUpstreamJoint());
contentValuesWeld.put(DBColCons.HEAT_AHEAD, weld.getHeatAhead());
contentValuesWeld.put(DBColCons.LENGTH_AHEAD, weld.getLengthAhead());
contentValuesWeld.put(DBColCons.EXISTING_GRADE_GPS_POINT, weld.getExistingGradePoint());
contentValuesWeld.put(DBColCons.COVER, weld.getCover());
contentValuesWeld.put(DBColCons.WALL_CHANGE, weld.getWallChange());
contentValuesWeld.put(DBColCons.WELD_WALL_THICKNESS, weld.getWeldWallThickness());
contentValuesWeld.put(DBColCons.WELDER_INITIALS, weld.getWelderInitials());
contentValuesWeld.put(DBColCons.NOTES, weld.getNotes());
/* adding the date in here to the row. */
contentValuesWeld.put(DBColCons.DATE, String.valueOf(mStrDate));
/* Inserting into the weld table */
db.insertWithOnConflict(WELD_TABLE, DBColCons.WELDER_INITIALS, contentValuesWeld,
SQLiteDatabase.CONFLICT_NONE);
return true;
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
The values for DBColCons.GPS_POINT,DBColCons.EXISTING_GRADE_GPS_POINT,DBColCons.GPS_COVER and DBColCons.NOTES are what I want to insert into the Common_Attributes_Table. This is where I am really confused. Do I need to create a separate ContentValues object for those specific values and insert them into the desired table with a separate db.insert() method along with the one I am already using with the insert on the WELD_TABLE?
Help I am lost in this train wreck. Ha.
Thank you all.
You need to call insert() (or insertWithConflict()) for each table you are inserting values into. Unless the values are the same, this implies you will need another ContentValues per table.
If you intend for these inserts to be committed as a single atomic operation, consider using a transaction.
SQLiteDatabase db = ...;
db.beginTransaction();
try {
// do your inserts/etc. here
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}

Cursors, SQLite, and queries

I have a sqlitedatabase I'm implementing within my app and I keep receiving an IllegalStateException when launching.
09-21 22:48:26.749 15762-16277/nz.co.exium.panther E/AndroidRuntime﹕ FATAL EXCEPTION: SyncAdapterThread-2
java.lang.IllegalStateException: Couldn't read row 0, col 4 from CursorWindow. Make sure the Cursor is initialized correctly before accessing data from it.
at android.database.CursorWindow.nativeGetString(Native Method)
at android.database.CursorWindow.getString(CursorWindow.java:438)
at android.database.AbstractWindowedCursor.getString(AbstractWindowedCursor.java:51)
at android.database.CursorWrapper.getString(CursorWrapper.java:114)
at nz.co.exium.panther.sync.PantherSyncAdapter.getRegistrationId(PantherSyncAdapter.java:269)
at nz.co.exium.panther.sync.PantherSyncAdapter.onPerformSync(PantherSyncAdapter.java:64)
at android.content.AbstractThreadedSyncAdapter$SyncThread.run(AbstractThreadedSyncAdapter.java:254)
I believe this is caused by the query returning a fairly empty table (except for _ID that is autoincremented) and then when I attempt to get a String from the cursor.
String registrationID = "";
Uri uri = CustomerEntry.CONTENT_URI;
Cursor cursor = mContext.getContentResolver().query(uri, CUSTOMER_PROJECTION, null, null, null);
if (cursor.moveToFirst()) {
registrationID = cursor.getString(INDEX_CUST_REGID);
}
SQL create table call:
final String SQL_CREATE_CUSTOMER_TABLE = "CREATE TABLE " + CustomerEntry.TABLE_NAME + " (" +
CustomerEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
CustomerEntry.COLUMN_CUST_NAME + " TEXT NOT NULL, " +
CustomerEntry.COLUMN_CUST_EMAIL + " TEXT NOT NULL, " +
CustomerEntry.COLUMN_CUST_QRHASH + " TEXT NOT NULL," +
CustomerEntry.COLUMN_CUST_REGID + " TEXT NOT NULL)";
In this case, the INDEX_CUST_REGID is a final static int related to the position in the String[] projection, 3rd position in this case. It makes sense this would throw but is there a method or way to query the SyncAdapter for a specific column like the CUST_REGID or a method to check if the Column requested was returned before attempting a cursor.getString()?
This also might solve another problem I have with saving the Reg ID before knowing the EMAIL, NAME, QRHASH. Can't insert just one column without the rest having a value as well (NOT NULL), even though it would be worthless data and overwritten asap.
Method attempting to store Reg ID.
private void storeRegistrationID(Context context, String regID) {
//DB insert regid
ContentValues cValues = new ContentValues();
cValues.put(CustomerEntry.COLUMN_CUST_NAME, "");
cValues.put(CustomerEntry.COLUMN_CUST_EMAIL, "");
cValues.put(CustomerEntry.COLUMN_CUST_QRHASH, "");
cValues.put(CustomerEntry.COLUMN_CUST_REGID, regID);
mContext.getContentResolver().insert(CustomerEntry.CONTENT_URI, cValues);
}
projection as requested:
String[] CUSTOMER_PROJECTION = new String[]{
CustomerEntry._ID,
CustomerEntry.COLUMN_CUST_NAME,
CustomerEntry.COLUMN_CUST_EMAIL,
CustomerEntry.COLUMN_CUST_QRHASH,
CustomerEntry.COLUMN_CUST_REGID
};
Are you certain the table was actually created? There's a semi-colon mising from the end of your SQL_CREATE_CUSTOMER_TABLE text string - although I'm not sure if it is mandatory to run the sql for creating the table. I would suggest running it on the Eclipse emulator, then from the DDMS perspective, take a copy of the database somewhere where you can open it with the SQLite Manager plugin for Firefox - this will show you the database tables.

Raw Query in Android SQLite - INSERT OR IGNORE

I'm having an issue inputting information into a Sqlite database on the app I'm creating. I was using the help of the Cursor before. I am used to MySQL although clearly not 'used to' that well.
I am trying to add to the database from a file. I had this working before but it would be added with the Cursor. I was then told that in order to make it so I could add new information to the file and have the app ONLY add the new information into the database I should use INSERT OR IGNORE.
Is this the correct syntax? I currently am not having any information inserted for whatever reason...
ourDatabase.rawQuery("INSERT OR IGNORE INTO " + DATABASE_TABLE + " ("+KEY_CLASS+",
" + KEY_QUESTION+ ") VALUES ('" + qclass + "', '" + question + "');", null);
This is my database:
"CREATE TABLE " + DATABASE_TABLE + " (" +
KEY_ROWID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
KEY_CLASS + " TEXT NOT NULL, " +
KEY_QUESTION + " TEXT NOT NULL UNIQUE);
Thanks for the help in advance!
Your query seems right but try the one below anyway
ContentValues insertValues = new ContentValues();
insertValues.put(KEY_CLASS, qclass);
insertValues.put(KEY_QUESTION, question);
yourDbName.insertWithOnConflict(DATABASE_TABLE, null, insertValues, SQLiteDatabase.CONFLICT_IGNORE);
KEY_QUESTION + " TEXT NOT NULL UNIQUE);";

How To Test If Cursor Is Empty in a SQLiteDatabase Query

I have an SQL table which is created by the following code:
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE_NAME + " (" + _ID
+ " INTEGER PRIMARY KEY AUTOINCREMENT, " + SUBJECT
+ " TEXT NOT NULL," + TOPIC + " TEXT NOT NULL, "
+ LECTURENUMBER + " TEXT NOT NULL, " + PAGENUMBER
+ " TEXT NOT NULL, " + DATE + " TEXT NOT NULL, " + _DATA
+ " TEXT NOT NULL);");
}
I query the table as follows:
String sql = "SELECT " + _ID + "," + SUBJECT + " FROM " + TABLE_NAME
+ " GROUP BY " + SUBJECT + ";";
Cursor cursor = subjects.getReadableDatabase().rawQuery(sql, null);
The problem is I have to start an Activity A if the cursor is empty(i.e. the table is storing no values) and Activity B if the cursor is not empty(i.e. table is filled).
I am unable to find a method which can tell me if the table is empty or not.
I Have tried to used Log as follows:
private void showSubjectsOnList() {
String sql = "SELECT " + _ID + "," + SUBJECT + " FROM " + TABLE_NAME
+ " GROUP BY " + SUBJECT + ";";
Cursor cursor = subjects.getReadableDatabase().rawQuery(sql, null);
Log.d("Events",Integer.toString(cursor.getCount()));
if(cursor.isNull(0)!=false){
cursor.close();
subjects.close();
startActivity(new Intent(this,OpenScreen.class));
}
}
But the LOG shows 1, if the table is empty...and again 1, if table has 1 entry....it shows 2, if table has two entries and so on.
Can you suggest some method of solving my problem of starting different activities based on if cursor is empty or not.
What about testing the cursor like this, and then doing what you've said:
if(cursor!=null && cursor.getCount()>0)
getCount ()
Returns the numbers of rows in the cursor
http://developer.android.com/reference/android/database/Cursor.html#getCount()
The easiest and cleanest way to test for an empty cursor is the following code:
if ( cursor.moveToFirst() ) {
// start activity a
} else {
// start activity b
}
Per the docs, the method returns false if the cursor is empty:
http://developer.android.com/reference/android/database/Cursor.html#moveToFirst%28%29
public abstract boolean moveToFirst ()
Added in API level 1 Move the cursor to the first row.
This method will return false if the cursor is empty.
Returns whether the move succeeded.
You just need to use getCount().
If your sql is correct but doesn't return any row you will have a NOT null cursor object but without a rows and getCount() will return 0.
Deleted records remain in SQLite as null records, but getCount() counts only not null records. If your table has some records that are null, some of not null records will have _Id numbers bigger than result of getCount(). To reach them, you can iterate cursor ( using for() loop ) double the number of times than result of getCount() and use the cursor to fill record_Id numbers into an array. Lets say resulting array is { 1, 2, 5, 6, 7, 8, 9, 11, 12, 14 }.
That means records 3, 4, 10, 13, are null records and your table has 14 record all together, not 10 that you got from getCount().
Remember:
getCount() returns number of not null records ,
cursor returns _Id numbers of not null records,
_Id numbers "missed" by cursor are _Id numbers of null records,
must reach sufficiently further than getCount() to get them all.
My suggestion would be using a ListActivity.
Those are Activity's which are meant to display items in a ListView. You can simply use a SimpleCursorAdapter to populate them (also illustrated in the ListActivitys JavaDoc page).
They also offer a setEmptyView()-method, which can be used to display a View (might be a TextView) which informs the user that there are no records yet and how he can create one.
An example on how to do that can be found here.
I believe your problem is you're not creating a proper query.
You should use the SQLiteDatabase query.
Cursor c = db.query(TABLE_NAME, null,
null, null, null, null, null);
You then can use c.getCount() to determine if the table has anything.

Categories

Resources