I'm using Content Providers and Sync Adapters for my synchronization routine.
My routine receives a JSONObject and insert or update the entry.
In order to decide if we are going to update or insert we check if the entry exists in the database. This is where the sqlite error occurs.
06-03 10:58:21.239: INFO/Database(340): sqlite returned: error code = 17, msg = prepared statement aborts at 45: [SELECT * FROM table WHERE (id = ?) ORDER BY id]
I have done some research and found this discussion about the subject. From this discussion I understand that sqlite_exec() has to be called. How would I implement this in a Content Provider?
Edit
Insert / Update check
// Update or Insert
ContentValues cv = new ContentValues();
/* put info from json into cv */
if(mContentResolver.update(ClientsProvider.CONTENT_URI, cv, null, null) == 0) {
// add remote id of entry
cv.put("rid", o.optInt("id"));
mContentResolver.insert(ClientsProvider.CONTENT_URI, cv);
}
ContentProvider::update
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0;
switch(uriMatcher.match(uri)) {
case CLIENTS:
count = clientDB.update(TABLE_NAME, values, selection, selectionArgs);
break;
case CLIENT_ID:
count = clientDB.update(TABLE_NAME, values, ID + " = " + uri.getPathSegments().get(0) + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""), selectionArgs);
break;
default:
count = 0;
}
return count;
}
Problem is solved. I'm not sure why but after an emulator image wipe everything works exactly how its supposed to do. Thank you for your time Selvin!
Related
I want to insert data if that headline doesn't exist yet in the database.
This is the error I'm getting for the written query:
near "with": syntax error (code 1): , while compiling: SELECT * FROM movie WHERE headline=Albert Collen
Code:
public boolean Insert(Item item) {
SQLiteDatabase sqLiteDatabase = this.getWritableDatabase();
ContentValues contentValues = new ContentValues();
Cursor cursor = sqLiteDatabase.rawQuery("SELECT * FROM " + TABLE + " WHERE headline=" + item.getName() , null);
if (cursor.moveToFirst()) {
} else {
contentValues.put("name", item.getName());
long result = sqLiteDatabase.insert(TABLE, null, contentValues);
if (result == -1) {
return false;
} else {
return true;
}
}
cursor.close();
sqLiteDatabase.close();
return true;
}
You should use query parameters
rawQuery("SELECT * FROM movie WHERE headline = ?", new String[] {"Albert Collen"});
to avoid having to escape quotes and other chars.
First of all, result query is wrong. All the string constants are to be quoted, like this
SELECT * FROM movie WHERE headline='Albert Collen';
So, try to compose query like this, perhaps it will help
Cursor cursor = sqLiteDatabase.rawQuery("SELECT * FROM " + TABLE + " WHERE headline='" + item.getName() + "'" , null);
But concatenating a query is not a good idea because it makes at least SQL-injections possible.
For example, it can cause problems when item.getName() contains following line "'; drop table movies;"
Better option would be using bind query variables. Unfortunately, I'm not familiar with how to use java-android with sqlite, so it is better for you to check how to use such a queries in android
I'm not sure what I'm doing wrong, but I'm trying to update a single integer value in a column of a table to 1 from 0. When creating the database, I set all values of the column to zero using:
for (int i = 0; i < setups.length; i++) {
ContentValues values = new ContentValues();
values.put(JokeDbContract.TblJoke.COLUMN_NAME_SETUP, setups[i]);
values.put(JokeDbContract.TblJoke.COLUMN_NAME_PUNCHLINE, punchlines[i]);
values.put(JokeDbContract.TblJoke.COLUMN_NAME_USED, 0);
db.insert(JokeDbContract.TblJoke.TABLE_NAME, null, values);
}
Then, in the actual activity, I'm doing:
private void findNewJoke() {
JokeDb jokeDb = JokeDb.getInstance(this);
SQLiteDatabase theDb = jokeDb.getDB();
String selection = JokeDbContract.TblJoke.COLUMN_NAME_USED + "=" + 0;
// Query database for a joke that has not been used, update the fields
// theJoke and thePunchline appropriately
String[] columns = {JokeDbContract.TblJoke._ID,
JokeDbContract.TblJoke.COLUMN_NAME_PUNCHLINE,
JokeDbContract.TblJoke.COLUMN_NAME_SETUP,
JokeDbContract.TblJoke.COLUMN_NAME_USED};
Cursor c = theDb.query(JokeDbContract.TblJoke.TABLE_NAME, columns, selection,
null, null, null, null);
if (c.moveToFirst() == false) {
Toast.makeText(this, R.string.error_retrieving_joke, Toast.LENGTH_LONG).show();
Log.e(getString(R.string.app_name),"No jokes retreived from DB in JokeActivity.findNewJoke()!");
}
else {
ContentValues values = new ContentValues();
theSetup = c.getString(c.getColumnIndexOrThrow(JokeDbContract.TblJoke.COLUMN_NAME_SETUP));
thePunchline = c.getString(c.getColumnIndexOrThrow(JokeDbContract.TblJoke.COLUMN_NAME_PUNCHLINE));
String updateSelection = JokeDbContract.TblJoke.COLUMN_NAME_SETUP + "=" + theSetup;
values.put(JokeDbContract.TblJoke.COLUMN_NAME_USED, 1);
theDb.update(JokeDbContract.TblJoke.TABLE_NAME, values, updateSelection, null);
}
}
I'm getting an error on the update:
java.lang.RuntimeException: .... while compiling: UPDATE jokes SET used=?
WHERE setup=Why do programmers always mix up Halloween and Christmas?
It seems as though I'm not getting an actual value set for the used column. What the program ultimately does is cycle through jokes where used=0, then sets used to 1 when it has been viewed. So the query only pulls those jokes that aren't used yet. I have a feeling I'm missing something simple, one can hope.
I think you are having problems with quotation marks.
Example:
String updateSelection = JokeDbContract.TblJoke.COLUMN_NAME_SETUP + "=\"" + theSetup + "\"";
However, the recommended way to do this, would be:
theDb.update(JokeDbContract.TblJoke.TABLE_NAME, values, JokeDbContract.TblJoke.COLUMN_NAME_SETUP + " = ?", new String[] { theSetup });
It is better to use field = ?, because this helps sqlite cache queries (I believe).
This is what i am using for insert:
public long insert(String content, Date startAt, Date endAt) {
if (content == null || startAt == null) {
return 0;
}
ContentValues contentValues = new ContentValues();
contentValues.put(KEY_CONTENT, content);
contentValues.put(KEY_START_AT, startAt.getTime());
if (endAt == null) {
contentValues.putNull(KEY_END_AT);
} else {
contentValues.put(KEY_END_AT, endAt.getTime());
}
return sqLiteDatabase.insert(TABLE_NAME, null, contentValues);
}
now i want to create update method which will update last inserted row. How can i get last inserted row?
If you have an id attribute that works as a primary key, you can do a raw database query on SqlLite.
Cursor cc = this.mDb.rawQuery("SELECT *" + " FROM " + "<Your DATABASE_NAME> " +
"ORDER BY id " + "DESC LIMIT 1", null);
return cc;
Here,
1. It returns a cursor.
2. mDb is a SQLiteDatabase class instance.
3. ORDER BY id allows the query to sort by id number. As I said, if you have an id as primary key in your table, your latest entry will have the maximum id number.
4. DESC allows to sort by descending order.
5. LIMIT 1 allows to return only 1 row.
6. Always be careful when writing raw queries, white spaces inside the query can be a lot of pain when you do not handle them carefully.
For further queries you can see this tutorial. And obviously Divya's answer is also a good one.
You can use a cursor to retrieve rows and say :
cursor.moveToLast();
OR
cursor.moveToPosition(cursor.getCount() - 1);
When you insert a row in to your table the insert query returns the key of the last inserted row. You can now use this key to update this row.
for example
int newInsertedKey = sqLiteDatabase.insert(TABLE_NAME, null, contentValues);
update table_name set column_name = 'Change 2' where columnID = newInsertedKey
An efficient method would be to avoid anymore database queries to get the last updated row.
Maybe he should use something like this
public long getLastId() {
Cursor c = mDb.query(currentTableName, new String[] { "MAX(_id)" },
null, null, null, null, null, null);
try{
c.moveToFirst();
long id = c.getLong(0);
return id;
}catch(Exception e){
return 0;
}
}
where _id is column by which you identify rows
i'm creating a contentProvider , and i wish to be able to send it multiple DB records (contentValues) to be inserted or updated to a single table using a single batch operations .
how do i do that?
batchInsert is intended only for inserting , but wouldn't it mean that insertion of something that already exists won't do anything?
also , is there a way for the update operation to use a special constraint ? for example , i need to ignore the primary key and update based on 2 other fields that together are unique.
"batchInsert is intended only for inserting" : this is true BUT you can override it in your ContentProvider to perform an UPSERT (insert/update) depending on the URI passed to batchInsert.
The following is some working code that I currently use to perform bulk inserts on time-series data (admittedly, I just delete anything that gets in the way instead of updating, but you could easily change this to your own ends.).
Also note the use of the sql transaction; this speeds up the process immensely.
#Override
public int bulkInsert(Uri uri, ContentValues[] values) {
SQLiteDatabase sqlDB = database.getWritableDatabase();
switch (match(uri)) {
case ONEPROGRAMME:
String cid = uri.getLastPathSegment();
int insertCount = 0;
int len = values.length;
if (len > 0) {
long start = values[0].getAsLong(Programme.COLUMN_START);
long end = values[len - 1].getAsLong(Programme.COLUMN_END);
String where = Programme.COLUMN_CHANNEL + "=? AND " + Programme.COLUMN_START + ">=? AND "
+ Programme.COLUMN_END + "<=?";
String[] args = { cid, Long.toString(start), Long.toString(end) };
//TODO use a compiled statement ?
//SQLiteStatement stmt = sqlDB.compileStatement(INSERT)
sqlDB.beginTransaction();
try {
sqlDB.delete(tableName(PROGRAMME_TABLE), where, args);
for (ContentValues row : values) {
if (sqlDB.insert(tableName(PROGRAMME_TABLE), null, row) != -1L) {
insertCount++;
}
}
sqlDB.setTransactionSuccessful();
} finally {
sqlDB.endTransaction();
}
}
if (insertCount > 0)
getContext().getContentResolver().notifyChange(Resolver.PROGRAMME.uri, null);
return insertCount;
default:
throw new UnsupportedOperationException("Unsupported URI: " + uri);
}
}
working on a content provider and I'm having an issue with it. When I try to update a certain row in the SQLite database through the content provider, it updates the column in all the rows, not just the row I specify. I know the CP is working because I can access it, populate a listview with it, and change the content of column, but never just one column.
Here is the relevant update method
public int update(Uri url, ContentValues values, String where,
String[] whereArgs) {
SQLiteDatabase mDB = dbHelper.getWritableDatabase();
int count;
String segment = "";
switch (URL_MATCHER.match(url)) {
case ITEM:
count = mDB.update(TABLE_NAME, values, where, whereArgs);
break;
case ITEM__ID:
segment = url.getPathSegments().get(1);
count = mDB.update(TABLE_NAME, values,
"_id="
+ segment
+ (!TextUtils.isEmpty(where) ? " AND (" + where
+ ')' : ""), whereArgs);
break;
default:
throw new IllegalArgumentException("Unknown URL " + url);
}
getContext().getContentResolver().notifyChange(url, null);
return count;
}
and here is the code I use to (try to) update it.
ContentValues mUpdateValues = new ContentValues();
mUpdateValues.put(ContentProvider.HAS, "true");
mUpdateValues.put(ContentProvider.WANT, "false");
mRowsUpdated = getContentResolver().update(Uri.parse(ContentProvider._ID_FIELD_CONTENT_URI
+ rowId), mUpdateValues, null, null);
and here is the URI
URL_MATCHER.addURI(AUTHORITY, TABLE_NAME + "/#", ITEM__ID);
Thanks, any help would be appreciated.
EDIT I have also tried
mRowsUpdated = getContentResolver().update(
ContentProvider._ID_FIELD_CONTENT_URI, mUpdateValues,
null, null);
and
mRowsUpdated = getContentResolver().update(
ContentProvider.CONTENT_URI, mUpdateValues,
null, null);
You are not specifying a WHERE clause, which is what is used to update only specific rows. The default behavior of content providers is to update all the rows, unless you specify conditions.
From the docs:
developer.android.com/reference/android/content/ContentResolver.html
Parameters
uri The URI to modify.
values The new field values. The key is the column name for the field. A null value will remove an existing field value.
where A filter to apply to rows before updating, formatted as an SQL WHERE clause (excluding the WHERE itself).