I've been looking on this site for a while but have not found the answer. I am trying to do a bulk update on data that I know is already in the table. I have one column that needs to be set when a certain condition comes back for the row ID. Here is the single method but I want to make this more efficient and do it as a bulk. Our database is not in a Provider so I just using a Helper class.
public void markUnavailable(int myId) {
SQLiteDatabase db = this.getWritableDatabase();
String sql = "UPDATE " + MYTABLE + " SET " + Col.IS_AVAILABLE + "= 0"+ " WHERE " + Col.MY_ID + "=" + myId;
db.execSQL(sql);
db.close();
}
I would like to pass in an array of myIds to do the bulk Update. I can't do a Insert or Replace statement because I don't have access to all the column data and don't want to pass this through due to too many codes changes.
public void markUnavailable(int[] myId) {
// ????
/// need some way to loop through and update in bulk
}
Try UPDATE tablename SET column=0 WHERE ID IN (...), where ... is a comma-delimited list of ID values.
I'm not an Android developer, but according to good database practices, you should:
public void markUnavailable(int[] myId) {
SQLiteDatabase db = this.getWritableDatabase();
db.beginTransaction();
SQLiteStatement upd=db.compileStatement("UPDATE "+MYTABLE+" SET "+Col.IS_AVAILABLE+"=0 WHERE "+Col.MY_ID+"=?";
for (int i = 0; i < myId.length; i++) {
upd.bindLong(1, myId[i]);
upd.execute();
}
db.endTransaction();
}
Android has SQLiteDatabase.update would be very usefull in this case, but String [] whereArgs would not deal well with your int[] myId.
The fastest way to do a bulk update would be to do it as a single transaction,by using begin and end transactions. Also if the size of the database is large it will be a good idea to make myID as the primary key of the table as it will significantly increase the speed of the speed in fetching the rows for update when the WHERE clause is used.[It is said that indexing can reduce the speed of update and insert but when the where clause is used,indexing has always increased my speed by huge margins.
public void markUnavailable(int[] myId) {
SQLiteDatabase db = this.getWritableDatabase();
db.beginTransaction();
SQLiteStatement upd=db.compileStatement("UPDATE "+MYTABLE+" SET "+Col.IS_AVAILABLE+"=0 WHERE "+Col.MY_ID+"=?");
for (int i = 0; i < myId.length; i++) {
upd.clearBindings();
upd.bindLong(1, myId[i]); // this matches the first "?"
upd.execute();
}
db.setTransactionSucessful();
db.endTransaction();
}
Related
My app is using an external SQLite database. The database is created using DB Browser for SQLite software. I am using the following method to query my table with the column ENGLISH (same as en_word). However, problem is the query is slow when my database become large.
public static final String ENGLISH = "en_word";
public static final String TABLE_NAME = "words";
String sql = "SELECT * FROM " + TABLE_NAME +
" WHERE " + ENGLISH + " LIKE ? ORDER BY LENGTH(" + ENGLISH + ") LIMIT 100";
SQLiteDatabase db = initializer.getReadableDatabase();
Cursor cursor = null;
try {
cursor = db.rawQuery(sql, new String[]{"%" + englishWord.trim() + "%"});
List<Bean> wordList = new ArrayList<Bean>();
while(cursor.moveToNext()) {
String english = cursor.getString(1);
String mal = cursor.getString(2);
wordList.add(new Bean(english, mal));
}
return wordList;
} catch (SQLiteException exception) {
exception.printStackTrace();
return null;
} finally {
if (cursor != null)
cursor.close();
}
I tried to create index using DB Browser for SQLite.
CREATE INDEX `kindx` ON `words` ( `en_word` )
However, do I need to modify my code so that my app will query the database using this index? If so, how to do that?
The problem is that SQLite, like most relational databases, can use an index when the parameter to a 'like' clause ends with a wildcard, it cannot use an index when the parameter begins with a wildcard.
So, for this type of query, the index will not be used, and you wind up with a full table scan. This is why it is slower with a large number of rows.
You are actually attempting to do what is known as "full text search", which is not really possible to do efficiently without database features to support it directly.
I have not tried it, but I see that SQLite does have full-text search capabilities, and that it is supported on Android. See Full text search example in Android for an example.
I am new in Android Development.
Currently, working with SQLite Database in Android.
My problem is that I have a large amount of data which I have to store in SQLite Database in Android.
There are 2 tables: One having 14927 rows and the other one has 9903 rows.
Currently the database in sql. And I have copy these data in excel sheet but don't understand how can I import these data in SQLite Database.
I go through the following link:
Inserting large amount of data into android sqlite database?
Here, the solution is posted regarding CSV File. But want to know other ways of doing this.
Please let me know what is the best way to import such a large data in Android.
Please help me. Thanks in advance.
Do Like This
SQLiteDatabase sd;
sd.beginTransaction();
for (int i = 0; i < data.size(); i++) {
ContentValues values = new ContentValues();
values.put(DBAdapter.Column1, "HP");
values.put(DBAdapter.Column2, "qw");
values.put(DBAdapter.Column3, "5280");
values.put(DBAdapter.Column4, "345, 546");
sd.insert(DBAdapter.TABLE, null, values);
sd.insertWithOnConflict(tableName, null, values, SQLiteDatabase.CONFLICT_IGNORE);
}
sd.setTransactionSuccessful();
sd.endTransaction();
Try this
SQLiteDatabase db = Your_DATABASE;
db.beginTransaction();
db.openDatabase();
for (int i = 0; i < array.size(); i++)
{
String sql = ( "INSERT INTO " + Table_NAME
+ "(" + COLUMN_1 + ","
+ COLUMN_2 + ","
+ COLUMN_3 + ","
+ COLUMN_4 + ","
+ ") values (?,?,?,?)");
SQLiteStatement insert = db.compileStatement(sql);
}
db.setTransactionSuccessful();
db.endTransaction();
db.closeDatabase();
Use DatabaseUtils.InsertHelper. In this article you will find an example of how to use it and other ways to speed up insertions. The example is as follows:
import android.database.DatabaseUtils.InsertHelper;
//...
private class DatabaseHelper extends SQLiteOpenHelper {
#Override
public void onCreate(SQLiteDatabase db) {
// Create a single InsertHelper to handle this set of insertions.
InsertHelper ih = new InsertHelper(db, "TableName");
// Get the numeric indexes for each of the columns that we're updating
final int greekColumn = ih.getColumnIndex("Greek");
final int ionicColumn = ih.getColumnIndex("Ionic");
//...
final int romanColumn = ih.getColumnIndex("Roman");
try {
while (moreRowsToInsert) {
// ... Create the data for this row (not shown) ...
// Get the InsertHelper ready to insert a single row
ih.prepareForInsert();
// Add the data for each column
ih.bind(greekColumn, greekData);
ih.bind(ionicColumn, ionicData);
//...
ih.bind(romanColumn, romanData);
// Insert the row into the database.
ih.execute();
}
}
finally {
ih.close();
}
}
//...
}
I have large number of strings, approximately 15,000 that I stored in a SQLite database using the following code:
void addKey(String key, String value, String table) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(KEY_KEY, key); // Contact Name
values.put(KEY_VALUE, value); // Contact Phone
// Inserting Row
db.insert(table, null, values);
db.close(); // Closing database connection
}
And then i search through that database using the following method in order to pick out any strings that match the key im looking for:
public String searchKeyString(String key, String table){
String rtn = "";
Log.d("searchKeyString",table);
// Select All Query
String selectQuery = "SELECT * FROM " + table;
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
// looping through all rows and adding to list
if (cursor.moveToFirst()) {
do {
Log.d("searchKeyString","searching");
if(cursor.getString(1).equals(key))
rtn = rtn + "," + cursor.getString(2);
} while (cursor.moveToNext());
}
cursor.close();
db.close();
Log.d("searchKeyString","finish search");
return rtn;
}
The goal is to do this in real time as the user is typing on the keep board so response time is key and the way it stands now it takes over a second to run through the search.
I considered reading all of the items into an array list initially and sorting through that which might be faster, but i thought an array list of that size might cause memory issues. What is the best way to search through these entries in my database?
A couple of things you can do...
Change the return to a StringBuilder until the end.
Only use a readable version of the database (that's probably not making much difference though)
Do not get a new instance of the database every time, keep it opened until you don't need it anymore
Query for only what you need with the "WHERE" argument in the SQL query.
See the code below with some changes:
// move this somewhere else in your Activity or such
SQLiteDatabase db = this.getReadableDatabase();
public String searchKeyString(String key, String table){
StringBuilder rtn = new StringBuilder();
Log.d("searchKeyString",table);
// Select All Query
String selectQuery = "SELECT * FROM " + table + " WHERE KEY_KEY=?";
Cursor cursor = db.rawQuery(selectQuery, new String[] {key});
// you can change it to
// db.rawQuery("SELECT * FROM "+table+" WHERE KEY_KEY LIKE ?", new String[] {key+"%"});
// if you want to get everything starting with that key value
// looping through all rows and adding to list
if (cursor.moveToFirst()) {
do {
Log.d("searchKeyString","searching");
rtn.append(",").append(cursor.getString(2));
} while (cursor.moveToNext());
}
cursor.close();
Log.d("searchKeyString","finish search");
return rtn.toString();
}
Note even if you want this to happen in "real-time" for the user, you will still need to move this to a separate Thread or ASyncTask or you are going to run into problems....
You should consider using SELECT * FROM your-table LIMIT 50, for example. And you can put two buttons "Back", "Next" on your view. If every page has max 50 items, the user is at page 1, and he taps "Next", then you can use this query:
SELECT * FROM your-table LIMIT 50 OFFSET 50
If your table contains most of text-data, and you want to integrate search deeply into your app, consider using virtual table with FTS.
Let sqlite do the hard lifting.
First off, add an index to the field you're searching for, if you don't have one already. Secondly, don't do a SELECT all with manual table scan, but rather use a query in the form
SELECT column_value
FROM my_table
WHERE column_key LIKE "ABC%"
This returns the least amount of data, and the sql engine uses the index.
i dunno about better but maybe it'd be faster to make queries for the selected strings one by one.
public String searchKeyString(String key, String table){
String rtn = "";
Log.d("searchKeyString",table);
// Select All Query
String selectQuery = "SELECT * FROM " + table + "WHERE column_1 = " + key;
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
// looping through all rows and adding to list
if (cursor.moveToFirst()) {
rtn = rtn + "," + cursor.getString(2);
}
cursor.close();
db.close();
Log.d("searchKeyString","finish search");
return rtn;
}
EDIT:
Well i dunno how those custom keyboard apps do it, but those AutoCompleteTextViews are hooked up to adapters. you could just as easily make a cursorAdapter and hook your auto-complete view to it.
http://www.outofwhatbox.com/blog/2010/11/android-autocompletetextview-sqlite-and-dependent-fields/
http://www.opgenorth.net/blog/2011/09/06/using-autocompletetextview-and-simplecursoradapter-2/
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.
I am getting inconsistent results between two methods of reading the columns in an Android SQLite database.
First, this is part of a database upgrade routine as per the accepted answer here: Upgrade SQLite database from one version to another?
The technique involves moving the current table away with a temporary name, creating a new table with the new schema, and then copying relevant data from the old table into the new one before deleting the old temporary table.
The particular problem I have is when I remove a column from the schema. So, a particular column exists in the old version of the table, but not the new one.
That answer suggests using a method like this to list the columns in the table:
/**
* Returns a list of the table's column names.
*/
private List<String> getColumns(SQLiteDatabase db, final String tableName) {
List<String> ar = null;
Cursor c = null;
try {
c = db.rawQuery("SELECT * FROM " + tableName + " LIMIT 1", null);
if (c != null) {
ar = new ArrayList<String>(Arrays.asList(c.getColumnNames()));
}
} finally {
if (c != null)
c.close();
}
return ar;
}
That works fine on the old table, before I move it away with a temporary name and replace it. When I run the same query again later, on the newly-created empty table, it still lists the old table schema with the name of the column which no longer exists. It looks as if it's reusing stale cached results for that query.
If I read the columns a different way, using this instead, then it returns the new column list as expected:
private void listColumns(SQLiteDatabase db, final String tableName) {
final String query = "PRAGMA table_info(" + tableName + ");";
Cursor c = db.rawQuery(query, null);
while (c.moveToNext()) {
Log.v("MyApp", "Column: " + c.getString(1));
}
c.close();
}
The complete sequence is:
final String tempTableName = "temp_" + tableName;
table.addToDb(db); // ensure it exists to start with
// get column names of existing table
final List<String> columns = getColumns(db, tableName);
// backup table
db.execSQL("ALTER TABLE " + tableName + " RENAME TO " + tempTableName);
// create new table
table.addToDb(db);
// delete old columns which aren't in the new schema
columns.retainAll(getColumns(db, tableName));
// restore data from old into new table
String columnList = TextUtils.join(",", columns);
db.execSQL(String.format("INSERT INTO %s (%s) SELECT %s from %s", tableName, columnList, columnList,
tempTableName));
// remove backup
db.execSQL(DROP_TABLE + tempTableName);
What's the reason for the different results?
I assume you have done something similar to this:
ALTER TABLE "main"."mytable" RENAME TO "newtable";
CREATE TABLE "main"."mytable" ("key1" text PRIMARY KEY,"key2" text,"key3" text);
INSERT INTO "main"."mytable" SELECT "key1","key2","key3" FROM "main"."newtable";
DROP TABLE "main"."newtable";
If you have, please share the equivalent code, just to rule out any errors with this part.
I never got to the bottom of this. I just ended up using the second method I mentioned, which doesn't exhibit the problem.