In my project, I use SQL (SQLite since its android) to save my data.
I encountered some odd problem:
In my application, I have three tabs and in order to know which data belong to which tab I have in my chart a column for each tab. the number in the column (Integer) represent if and how many of that data suppose to be in this tab.
So, when the tab initialized, it reads from the chart, and using the relevant column, it can tell which data needs to be read and how many.
When I retrieve data from the server (the database of the server is not related to the problem at hand), I check if the data is new or that I already have a similar one in my SQL DB. If its new, I add it to the SQL chart and put 1 in the relevant column. If it already exists, it checks if the data in the SQL is updated (it update the data if necessary) and add 1 to the relevant column (the same column it puts 1 in it in case the data is not in the SQL DB).
now here's my problem:
when it reads from the SQL DB to see the number in the column, so it can add 1 to it and then update the chart, it always return 1 regardless the actual number that in the column in that moment (I know its not really 1 because when I read from the DB in the same column from other places in my app it does read the actual number).
Since in other places in my app it doesn't happen I tried to see what is the difference between the places but I did not find anything wrong (the only difference is that I used WritableDatabase instead of just ReadableDatabase in that time but that should not be an issue as far as I know, at least not in such case).
the code where the problem occurs (the problem is with COL_CART):
writable = db.getWritableDatabase();
Cursor cursor = writable.query(
ShopContract.ShopChart.TABLE_NAME,
new String[]{ShopContract.ShopChart.COL_NAME, ShopContract.ShopChart.COL_PRICE, ShopContract.ShopChart.COL_PIC, ShopContract.ShopChart.COL_CART},
ShopContract.ShopChart.COL_PROD_ID + " = '" + product.getProd_id() +"'",
null,
null,
null,
null
);
if(cursor.moveToFirst()){
//check if the data match, if not, replace.
if(!cursor.getString(cursor.getColumnIndex(ShopContract.ShopChart.COL_NAME)).equals(product.getName())){
ContentValues values = new ContentValues();
values.put(ShopContract.ShopChart.COL_NAME,product.getName());
update(values);
}
if(cursor.getDouble(cursor.getColumnIndex(ShopContract.ShopChart.COL_PRICE)) != product.getPrice()){
ContentValues values = new ContentValues();
values.put(ShopContract.ShopChart.COL_PRICE,product.getPrice());
update(values);
}
if(!cursor.getString(cursor.getColumnIndex(ShopContract.ShopChart.COL_PIC)).equals(product.getPicture())){
ContentValues values = new ContentValues();
values.put(ShopContract.ShopChart.COL_PIC,product.getPicture());
update(values);
}
//check how many there are already in cart (already in the cursor) and update it to be ++
Log.d(TAG, "run: the number in col_cart is: " + cursor.getInt(cursor.getColumnIndex(ShopContract.ShopChart.COL_CART)));
int inCart = cursor.getInt(cursor.getColumnIndex(ShopContract.ShopChart.COL_CART)) + 1; // because of the new product we just added
Log.d(TAG, "run: inCart = " + inCart);
ContentValues values = new ContentValues();
values.put(ShopContract.ShopChart.COL_CART,inCart);
Log.d(TAG, "run: change cart");
update(values);
edit:
here is the update method code (the writable is being initialized in the code above):
private SQLiteDatabase writable;
private void update(ContentValues values){
writable.update(ShopContract.ShopChart.TABLE_NAME, values, ShopContract.ShopChart.COL_PROD_ID + " = '" + product.getProd_id() +"'",null);
}
Related
I am a novice user,I am trying to update a record with some fields and nothing special. I noticed! that this may be answered a lot of times but none of the proposed answers is working and I dont know where to check in my code to find the solution. I have the following :
public int updateUser(User user) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(U_ID, user.getId());
values.put(U_NAME, user.getName());
values.put(U_EMAIL, user.getEmail());
values.put(U_ZIP, user.getZip());
values.put(U_CREATED_AT, user.getCreated_at());
int res = db.update("login_user", values, "U_ID" + "=?", new String[] {String.valueOf(user.getId())});
return res;
}
I have tried
int res = db.update("login_user", values, "U_ID" + " = ?", new String[] {String.valueOf(user.getId())});
int res = db.update("login_user", values, "U_ID" + "=?", new String[] {(UserId)});
int res = db.update(MYTABLE, values, U_ID + "=?", new String[] {String.valueOf(user.getId())});
I increased my Database version to make it empty I saved a new record so
My Data are not null, but I get as res=0 and not an expected res=1 and with no errors
What I am doing wrong and where to look?
herein lies your problem
I increased my Database version to make it empty
assuming your database is empty, then you should use the insert method instead. updating an empty table will not have any effects because the WHERE condition in the update statement will always return false.
db.insertWithOnConflict (String table,
null, //nullColumnHack
values,
SQLiteDatabase.CONFLICT_REPLACE);
this will create an entry if the userId is not found, or replace the existing userId row if there is a conflict
The response from the Sqlite Update method is the number of rows updated.
If the record exists in the DB then it will return 1 (or more)
If you've updated the DB number all the records are delted - so verify the record exists 1st.
If it's returning 0 then I suspect you don't have the record in the DB in the first place - so 0 records are updated.
If you're expecting 1 - then first check if the record exists, via ADB - see this answer on how to check the contents of your DB.
I've a situation in sqlite that make you an example: My table has two fields,"_id" and "_score" . I have a record with _id=1, _score=10. I want to update this row to 5 number more than the current value(10). in SQL i can do it simple like:
Update my_table set _score = _score + 5 where _id = 1
but in sqlite I have these that I don't know how can fix it to what I want :
ContentValues values = new ContentValues();
values.put("_score", my_value);
SQLiteDatabase db = SQLiteDatabase.openDatabase(DB_PATH + DB_NAME, null, SQLiteDatabase.OPEN_READWRITE);
int id = db.update(MY_TABLE,values,"_id = ?",new String[]{String.valueOf(my_id)});
and the Other problem is the returned value. I think in above example I give
id = 1
for 1 row effected. but I want to know that: Is there any way to retrieve the value of updated column(in my example I want to give "15"). Some thing like in SQL server that we fetch from
"##ROWCOUNT" or "##fetch_status"
Hope to describe it well. thanks.
Android's update() function can set only fixed values.
To execute your SQL statement, you have to use execSQL():
db.execSQL("Update my_table set _score = _score + 5 where _id = 1");
In SQLite, the UPDATE statement does not return any data (except the count of affected rows). To get the old or new data, you have to execute a SELECT separately.
For your first problem about updating the score value try this:
ContentValues values = new ContentValues();
values.put("_score", my_value);
SQLiteDatabase db = SQLiteDatabase.openDatabase(DB_PATH + DB_NAME, null, SQLiteDatabase.OPEN_READWRITE);
int id = db.update(my_table, values, _id + " = ?",
new String[] { String.valueOf(my_id) });
}
I'm new in android. I'm using SQLite database to store data. I have 2 column in this database which are containing unique data for each user.
The problem is when user will get an update, then the update will override the personal data. I want to update the database except 2 columns.
Here is my Initialize Database:
INSERT INTO WORDS (_id, unlocked,solved,score,word,letters,image,suggestion) VALUES (1,1,0,0,'example','example','pics/example.png','example');
As you can see, the 'solved' and 'score' has 0 values. I don't want to update these values. So I don't want to drop the entire database before the update, I would like to keep those 2 values, and make sure the update is not override those data.
Use like this
UPDATE table_name SET column_name1='value', column_name2 = 'value1' WHERE condition_column='value'
You can check it with weather DB table contains that user id or not, if not then do insert query as you are doing
If record found, just do updateQuery:
UPDATE WORDS set unlocked = 1, word = 'example',letters = 'example',image ='pics/example.png',suggestion='example' WHERE _id=1;
I have to say, you need to research, because this tag it's very trivial. So I recommend you to read more about.
this could be:
DatosSQLiteHelper usdbh = new DatosSQLiteHelper(this, "myDatabase", null, 1);
SQLiteDatabase db = usdbh.getWritableDatabase();
if(db != null)
{
//add all atributes for update
ContentValues registre = new ContentValues();
registro.put("myvar1", "value1");
registro.put("myvar2", "value2");
registro.put("myvar3", "value3");
int numcol = db.update("mytable", register, "_id_="+1, null);
if (numcol == 1)
{
Toast.makeText(this, "success", Toast.LENGTH_SHORT).show();
}
}
I would like to create a FIFO table in order to save only the most 50 recent infomations by deleting the oldest elements when a new infomation arrives. I can do it by manipulating ID in the table but I don't think it is the best solution. Any idea of doing it well?
Instead of checking for date time, sorting your items, and whatnot, you can just assume that the first row in your table is the last to be inserted.
In your Content Provider's insert(Uri uri, ContentValues cv), before doing your db.insert call, you can first query the number of items on that table using getCount() and delete the first row if count>50. Then proceed with your insert call.
You dont need to play with IDs in order to create a FIFO logic. The best would be to add another column as DATETIME in your table which automatically inserts current time-stamp that will help you to select records in ascending order with respect to this column. Your new column should be something like:
DateAdded DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
Make sure when ever you insert new record, you must do a COUNT check of total records in this table and if necessary delete the oldest record with respect to DateAdded. Moreover, you can make use of LIMIT and/or MAX in your select-query when it comes to delete the oldest record.
Add a datetime type column to your table if it doesn't contain it yet and set it to 'now' on each insert. Then on each insert select all with limit set to 50 sorted by date. Choose the last item and run a delete query to delete everything older than this last item.
is it must to use sqlite? can you use file handling? you can use simple Queue object and save it to file.
Here is what I did for a list of transactions, and it works okay. When inserting a new entry I check if the count is above 50, if so, I just delete the very last entry:
// Adding new transaction
public void addTransaction(Transaction transaction) {
SQLiteDatabase db = this.getWritableDatabase();
if(getTransactionsCount() > 50){
List<Transaction> allTransactions = getAllTransactions();
Transaction oldestTransaction = allTransactions.get(allTransactions.size()-1);
deleteTransaction(oldestTransaction);
}
ContentValues values = new ContentValues();
values.put(KEY_TRANSACTION_UID, transaction.getUID());
values.put(KEY_TRANSACTION_AMOUNT, transaction.getAmount());
values.put(KEY_TRANSACTION_IS_ADD, transaction.getIsAdd());
// Inserting Row
db.insert(TABLE_TRANSACTIONS, null, values);
db.close(); // Closing database connection
}
And getAllTransactions() returns the list in descending order (based on the id primary key):
// Getting All Transactions
public List<Transaction> getAllTransactions() {
List<Transaction> transactionList = new ArrayList<Transaction>();
// Select All Query
String selectQuery = "SELECT * FROM " + TABLE_TRANSACTIONS + " ORDER BY " + KEY_TRANSACTION_ID + " DESC";
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
// looping through all rows and adding to list
if (cursor.moveToFirst()) {
do {
Transaction transaction = new Transaction();
transaction.setID(Integer.parseInt(cursor.getString(0)));
transaction.setUID(cursor.getString(1));
transaction.setAmount(cursor.getString(2));
transaction.setIsAdd(cursor.getString(3));
// Adding contact to list
transactionList.add(transaction);
} while (cursor.moveToNext());
}
// return contact list
return transactionList;
}
Currently, I am using the following statement to create a table in an SQLite database on an Android device.
CREATE TABLE IF NOT EXISTS 'locations' (
'_id' INTEGER PRIMARY KEY AUTOINCREMENT, 'name' TEXT,
'latitude' REAL, 'longitude' REAL,
UNIQUE ( 'latitude', 'longitude' )
ON CONFLICT REPLACE );
The conflict-clause at the end causes that rows are dropped when new inserts are done that come with the same coordinates. The SQLite documentation contains further information about the conflict-clause.
Instead, I would like to keep the former rows and just update their columns. What is the most efficient way to do this in a Android/SQLite environment?
As a conflict-clause in the CREATE TABLE statement.
As an INSERT trigger.
As a conditional clause in the ContentProvider#insert method.
... any better you can think off
I would think it is more performant to handle such conflicts within the database. Also, I find it hard to rewrite the ContentProvider#insert method to consider the insert-update scenario. Here is code of the insert method:
public Uri insert(Uri uri, ContentValues values) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
long id = db.insert(DatabaseProperties.TABLE_NAME, null, values);
return ContentUris.withAppendedId(uri, id);
}
When data arrives from the backend all I do is inserting the data as follows.
getContentResolver.insert(CustomContract.Locations.CONTENT_URI, contentValues);
I have problems figuring out how to apply an alternative call to ContentProvider#update here. Additionally, this is not my favored solution anyways.
Edit:
#CommonsWare: I tried to implement your suggestion to use INSERT OR REPLACE. I came up with this ugly piece of code.
private static long insertOrReplace(SQLiteDatabase db, ContentValues values, String tableName) {
final String COMMA_SPACE = ", ";
StringBuilder columnsBuilder = new StringBuilder();
StringBuilder placeholdersBuilder = new StringBuilder();
List<Object> pureValues = new ArrayList<Object>(values.size());
Iterator<Entry<String, Object>> iterator = values.valueSet().iterator();
while (iterator.hasNext()) {
Entry<String, Object> pair = iterator.next();
String column = pair.getKey();
columnsBuilder.append(column).append(COMMA_SPACE);
placeholdersBuilder.append("?").append(COMMA_SPACE);
Object value = pair.getValue();
pureValues.add(value);
}
final String columns = columnsBuilder.substring(0, columnsBuilder.length() - COMMA_SPACE.length());
final String placeholders = placeholderBuilder.substring(0, placeholdersBuilder.length() - COMMA_SPACE.length());
db.execSQL("INSERT OR REPLACE INTO " + tableName + "(" + columns + ") VALUES (" + placeholders + ")", pureValues.toArray());
// The last insert id retrieved here is not safe. Some other inserts can happen inbetween.
Cursor cursor = db.rawQuery("SELECT * from SQLITE_SEQUENCE;", null);
long lastId = INVALID_LAST_ID;
if (cursor != null && cursor.getCount() > 0 && cursor.moveToFirst()) {
lastId = cursor.getLong(cursor.getColumnIndex("seq"));
}
cursor.close();
return lastId;
}
When I check the SQLite database, however, equal columns are still removed and inserted with new ids. I do not understand why this happens and thought the reason is my conflict-clause. But the documentation states the opposite.
The algorithm specified in the OR clause of an INSERT or UPDATE
overrides any algorithm specified in a CREATE TABLE. If no algorithm
is specified anywhere, the ABORT algorithm is used.
Another disadvantage of this attempt is that you loose the value of the id which is return by an insert statement. To compensate this, I finally found an option to ask for the last_insert_rowid. It is as explained in the posts of dtmilano and swiz. I am, however, not sure if this is safe since another insert can happen inbetween.
I can understand the perceived notion that it is best for performance to do all this logic in SQL, but perhaps the simplest (least code) solution is the best one in this case? Why not attempt the update first, and then use insertWithOnConflict() with CONFLICT_IGNORE to do the insert (if necessary) and get the row id you need:
public Uri insert(Uri uri, ContentValues values) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
String selection = "latitude=? AND longitude=?";
String[] selectionArgs = new String[] {values.getAsString("latitude"),
values.getAsString("longitude")};
//Do an update if the constraints match
db.update(DatabaseProperties.TABLE_NAME, values, selection, null);
//This will return the id of the newly inserted row if no conflict
//It will also return the offending row without modifying it if in conflict
long id = db.insertWithOnConflict(DatabaseProperties.TABLE_NAME, null, values, CONFLICT_IGNORE);
return ContentUris.withAppendedId(uri, id);
}
A simpler solution would be to check the return value of update() and only do the insert if the affected count was zero, but then there would be a case where you could not obtain the id of the existing row without an additional select. This form of insert will always return to you the correct id to pass back in the Uri, and won't modify the database more than necessary.
If you want to do a large number of these at once, you might look at the bulkInsert() method on your provider, where you can run multiple inserts inside a single transaction. In this case, since you don't need to return the id of the updated record, the "simpler" solution should work just fine:
public int bulkInsert(Uri uri, ContentValues[] values) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
String selection = "latitude=? AND longitude=?";
String[] selectionArgs = null;
int rowsAdded = 0;
long rowId;
db.beginTransaction();
try {
for (ContentValues cv : values) {
selectionArgs = new String[] {cv.getAsString("latitude"),
cv.getAsString("longitude")};
int affected = db.update(DatabaseProperties.TABLE_NAME,
cv, selection, selectionArgs);
if (affected == 0) {
rowId = db.insert(DatabaseProperties.TABLE_NAME, null, cv);
if (rowId > 0) rowsAdded++;
}
}
db.setTransactionSuccessful();
} catch (SQLException ex) {
Log.w(TAG, ex);
} finally {
db.endTransaction();
}
return rowsAdded;
}
In truth, the transaction code is what makes things faster by minimizing the number of times the database memory is written to the file, bulkInsert() just allows multiple ContentValues to be passed in with a single call to the provider.
One solution is to create a view for the locations table with a INSTEAD OF trigger on the view, then insert into the view. Here's what that would look like:
View:
CREATE VIEW locations_view AS SELECT * FROM locations;
Trigger:
CREATE TRIGGER update_location INSTEAD OF INSERT ON locations_view FOR EACH ROW
BEGIN
INSERT OR REPLACE INTO locations (_id, name, latitude, longitude) VALUES (
COALESCE(NEW._id,
(SELECT _id FROM locations WHERE latitude = NEW.latitude AND longitude = NEW.longitude)),
NEW.name,
NEW.latitude,
NEW.longitude
);
END;
Instead of inserting into the locations table, you insert into the locations_view view. The trigger will take care of providing the correct _id value by using the sub-select. If, for some reason, the insert already contains an _id the COALESCE will keep it and override an existing one in the table.
You'll probably want to check how much the sub-select affects performance and compare that to other possible changes you could make, but it does allow you keep this logic out of your code.
I tried some other solutions involving triggers on the table itself based on INSERT OR IGNORE, but it seems that BEFORE and AFTER triggers only trigger if it will actually insert into the table.
You might find this answer helpful, which is the basis for the trigger.
Edit: Due to BEFORE and AFTER triggers not firing when an insert is ignored (which could then have been updated instead), we need to rewrite the insert with an INSTEAD OF trigger. Unfortunately, those don't work with tables - we have to create a view to use it.
INSERT OR REPLACE works just like ON CONFLICT REPLACE. It will delete the row if the row with the unique column already exists and than it does an insert. It never does update.
I would recommend you stick with your current solution, you create table with ON CONFLICT clausule, but every time you insert a row and the constraint violation occurs, your new row will have new _id as origin row will be deleted.
Or you can create table without ON CONFLICT clausule and use INSERT OR REPLACE, you can use insertWithOnConflict() method for that, but it is available since API level 8, requires more coding and leads to the same solution as table with ON CONFLICT clausule.
If you still want to keep your origin row, it means you want to keep the same _id you will have to make two queries, first one for inserting a row, second to update a row if insertion failed (or vice versa). To preserve consistency, you have to execute queries in a transaction.
db.beginTransaction();
try {
long rowId = db.insert(table, null, values);
if (rowId == -1) {
// insertion failed
String whereClause = "latitude=? AND longitude=?";
String[] whereArgs = new String[] {values.getAsString("latitude"),
values.getAsString("longitude")};
db.update(table, values, whereClause, whereArgs);
// now you have to get rowId so you can return correct Uri from insert()
// method of your content provider, so another db.query() is required
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
Use insertWithOnConflict and set the last parameter (conflictAlgorithm) to CONFLICT_REPLACE.
Read more at the following links:
insertWithOnConflict documentation
CONFLICT_REPLACE flag
for me, none of the approaches are work if I don't have "_id"
you should first call update, if the affected rows are zero, then insert it with ignore:
String selection = MessageDetailTable.SMS_ID+" =?";
String[] selectionArgs = new String[] { String.valueOf(md.getSmsId())};
int affectedRows = db.update(MessageDetailTable.TABLE_NAME, values, selection,selectionArgs);
if(affectedRows<=0) {
long id = db.insertWithOnConflict(MessageDetailTable.TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_IGNORE);
}
Use INSERT OR REPLACE.
This is the correct way to do it.