I am applying a batch of ContentProviderOperations on my provider:
ContentProviderResult[] result = resolver.applyBatch(...)
Everything works as expected the data is being inserted into the DB, but if I want to extract the id(s) the last element of the uri which should be the id is always null.
Is this happening because I have set the _id of the table to autoincrement (in other words would it work if I am not autoincrementing the id and fill it with a manually uid from my code).
If not, can anyone tell me whats causing this behavior.
Update: This is the String for creating the table:
private static final String CREATE_TABLE_WORKFLOWSTATES =
"CREATE TABLE " + Tables.WORKFLOWSTATES + " ("
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ WorkflowStatesColumns.NAME + " TEXT NOT NULL,"
+ "UNIQUE ("+ WorkflowStatesColumns.NAME + ") ON CONFLICT IGNORE)";
and if i view the table I can see the columns _id and name, the inserted data that is visible shows that the autoincrement works properly.
Update 2: This is how I have build the ContentProviderOperation:
this is my ArrayList with ContentProviderOperations (CollectionUtils is a custom class in order to instantiate the Arraylist):
ArrayList<ContentProviderOperation> batch = CollectionUtils.newArrayList();
and this is the actual operation:
batch.add(ContentProviderOperation
.newInsert(InvoiceContract.addCallerIsSyncAdapterParameter(WorkflowStates.CONTENT_URI))
.withValue(WorkflowStates.NAME, task.getWFS()).build());
In case of insertion (and I guess that you insert a row into your database), the result[i].uri contains URI of a newly inserted row, where i is the index of the corresponding operation in operations array. Try to print this URI and you'll see if it corresponds to real id.
If one of your operation is UPDATE or DELETE the corresponding result URI will be null, but result[i].count will contain the number of updated/deleted rows.
UPDATE
I guess the problem is that you have a unique index on NAME. Maybe, you have a row already inserted with the same name?
UPDATE2
The problem was that the insert function in the ContentProvider did not return the id correctly.
Related
We have a requirement where some fields in a table need to have the same value as their ID. Unfortunately, we currently have to insert a new record and then, if needed, run another update to set the duplicate field (ID_2) value to equal the ID.
Here is the Android Sqlite code:
mDb.beginTransaction();
// ... setting various fields here ...
ContentValues contentValues = new ContentValues();
contentValues.put(NAME, obj.getName());
// now insert the record
long objId = mDb.insert(TABLE_NAME, null, contentValues);
obj.setId(objId);
// id2 needs to be the same as id:
obj.setId2(objId);
// but we need to persist it so we update it in a SECOND call
StringBuilder query = new StringBuilder();
query.append("update " + TABLE_NAME);
query.append(" set " + ID_2 + "=" + objId);
query.append(" where " + ID + "=" + objId);
mDb.execSQL(query.toString());
mDb.setTransactionSuccessful();
As you can see, we are making a second call to set ID_2 to the same value of ID. Is there any way to set it at INSERT time and avoid the second call to the DB?
Update:
The ID is defined as follows:
ID + " INTEGER PRIMARY KEY NOT NULL ," +
The algorithm used for autoincrementing columns is documented, so you could implement it manually in your code, and then use the new value for the INSERT.
This is quite a ugly hack, but it may be possible :
with id_table as (
select coalesce(max(seq), 0) + 1 as id_column
from sqlite_sequence
where name = 'MY_TABLE'
)
insert into MY_TABLE(ID_1, ID_2, SOME, OTHER, COLUMNS)
select id_column, id_column, 'SOME', 'OTHER', 'VALUES'
from id_table
It only works if the table ID is an AUTOINCREMENT, and is therefore managed via the documented sqlite_sequence table.
I also have no idea what happen in case of concurrent executions.
You could use an AFTER INSERT TRIGGER e.g.
Create your table (at least for this example) so that ID_2 is defined as INTEGER DEFAULT -1 (0 or any negative value would be ok)
e.g. CREATE TABLE IF NOT EXISTS triggertest (_id INTEGER PRIMARY KEY ,name TEXT ,id_2 INTEGER DEFAULT -1);
Then you could use something like (perhaps when straight after the table is created, perhaps create it just before it's to be used and drop it after done with it ) :-
CREATE TRIGGER triggertesting001
AFTER INSERT ON triggertest
BEGIN
UPDATE triggertest SET id_2 = `_id`
WHERE id_2 = -1;
END;
Drop using DROP TRIGGER IF EXISTS triggertesting001;
Example usage (testing):-
INSERT INTO triggertest (name) VALUES('Fred');
INSERT INTO triggertest (name) VALUES('Bert');
INSERT INTO triggertest (name) VALUES('Harry');
Result 1 :-
Result 2 (trigger dropped inserts run again ):-
Result 3 (created trigger) same as above.
Result 4 (ran inserts for 3rd time) catch up i.e. 6 rows updated id_2 with _id.
I'd strongly suggest reading SQL As Understood By SQLite - CREATE TRIGGER
Alternative solution
An alternative approach could be to simply use :-
Before starting transaction, retrieve mynextid from table described below
ContentValues contentValues = new ContentValues();
contentValues.put(ID,mynextid);
contentvalues.put(ID_2,mynextid++);
contentValues.put(NAME, obj.getName());
Then at end of the transactions update/store the value of mynextid in a simple single column, single row table.
i.e. you are managing the id's (not too dissimilar to how SQLite manages id's when 'AUTOINCREMENT' is specified)
I have a database with table Cities, which has two columns id and is_default declared as integer. I would like to update all cities the way that only one city has value 1 set in is_default column at the same time. My database is wrapped by ContentProvider so I want to use ContentResolver for this. Here's my code:
ContentValues contentValues = new ContentValues();
contentValues.put(DatabaseHelper.COLUMN_DEFAULT, "CASE WHEN " + DatabaseHelper.COLUMN_ID + " != " + id + " THEN 0 ELSE 1 END");
getContentResolver().update(BenefitsProvider.CITY_CONTENT_URI, contentValues, null, null);
Where id is an id of a city, which I would like to make the default one. The issue is that this query doesn't change the is_default column value at all. I'm sure that ContentProvider code is fine and it works properly with all other update cases.
Is it possible to have only one city at the same time with is_default column value being 1 using this approach? Is there any other way I can achieve this?
I have an app that allows the user to save some chosen rows from a temporary table. The user is able to name the new table.
I am successfully creating a table using the name the user has input, and putting all the chosen rows from the temporary table into the new table.
However, if the table name they enter already exists, I want to notify them via Toast and have them choose another name. I am still learning sqlite - is there a way to do this?
In my head I am using some sort of if statement to check if the table exists, and then executing code, however half of it is in sqlite and half is in java. I'm not sure the correct way to do this. Any suggestions are greatly appreciated!
private void createTable() {
dbHandler.getWritableDatabase().execSQL("CREATE TABLE IF NOT EXISTS " + favoriteName + " ( _id INTEGER PRIMARY KEY AUTOINCREMENT , exercise TEXT , bodypart TEXT , equip TEXT );");
dbHandler.getWritableDatabase().execSQL("INSERT INTO " + favoriteName + " SELECT * FROM randomlypicked");
Try
Cursor cursor = dbHandler.getReadableDatabase().rawQuery("select DISTINCT tbl_name from sqlite_master where tbl_name = '"+tableName +"'", null);
if(cursor!=null) {
if(cursor.getCount()>0) { //table already exists
//show toast
cursor.close();
return;
}
cursor.close();
}
//create table and insert normally
I have a problem to create a table. If I try to get a value from the second column, android writes a empty space in the toast. But if I try to get a value from the first column, android writes the value of the column correctly. The query functions to write the first column and to write the second column are equal. So I think the Creation of the Table is the problem. But look yourself:
public SQLiteDatabase tabelleerstellen(){
SQLiteDatabase leveldatabase = openOrCreateDatabase("leveldata.db",SQLiteDatabase.CREATE_IF_NECESSARY, null);
leveldatabase.setVersion(1);
final String CREATE_TABLE_LEVEL =
"CREATE TABLE IF NOT EXISTS tbl_level ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "ME1 TEXT, "
+ "ME2 TEXT, "
+ "ME3 TEXT, "
+ "ME4 TEXT, "
+ "ME5 TEXT, "
+ "ME6 TEXT, "
+ "ME8 TEXT, "
+ "GESCHAFFT INTEGER);";
leveldatabase.execSQL(CREATE_TABLE_LEVEL);
return leveldatabase;
}
public void tester(SQLiteDatabase leveldata){
ContentValues cursortester = new ContentValues();
cursortester.put("ME2","25");
leveldata.insert("tbl_level",null,cursortester);
String[] testerpr = {"ME2"};
Cursor testerprüfen = leveldata.query("tbl_level",testerpr,null,null, null, null,null,null);
testerprüfen.moveToFirst();
String dada = testerprüfen.getString(testerprüfen.getColumnIndex("ME2"));
Toast testertoast = Toast.makeText(getApplicationContext(),dada,Toast.LENGTH_SHORT);
testertoast.show();
}
Please check the following things:
Please make sure the table is up to date .. so try to call DROP TABLE IF EXISTS tbl_level; and recreate the table.
If you run a test make sure the table is completely empty ... so delete everything at the beggining of the test.
If the table can contain elements during the test then make sure you check the last inserted element. Please note that calling testerprüfen.moveToFirst(); moves the cursor to the first row in the table so checking that row every time is even if the table contains 50 elements is not a good thing. In this case you either use a sorting option in your query of uese while (testerprüfen != null && testerprüfen.moveToNext()) {// Your code here}
All in all I think your problem is that you already inserted more that one element in the able but you always check only the first element (with testerprüfen.moveToFirst();). Please not that there is a cursor.moveToLast() method that you can also call. This method moves the cursor to the last row in the table.
This is my database
db.execSQL("CREATE TABLE " + DATABASE_TABLE + " (" +
KEY_ROWID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
KEY_CATEGORY + " TEXT NOT NULL, " +
KEY_DATE + " TEXT, " +
KEY_PRICE + " LONG, " +
KEY_DETAILS + " TEXT NOT NULL);"
);
and this is the method for deleting all data
public void deleteall() {
// TODO Auto-generated method stub
ourDatabase.delete(DATABASE_TABLE, null, null);
}
and this is the method for deleting a particular data
public void deletentry(long l) {
// TODO Auto-generated method stub
ourDatabase.delete(DATABASE_TABLE, KEY_ROWID + " = " + l,null);
}
Here using the data I deleted but incremented row-id remains there, which I want to reset to 1 as the data is deleted also if I delete a particular data the row-id changes it's value in a sequence manner there should be no gap in between row-id.
I don't think autoincrement is your best option. If your goal is to create a simple numbered table with no gaps, it may be much easier to handle this using a table with a column for you to manually keep up with your ID's.
If you created a table like that, you could then write helper methods in your code for the following operations: addColumn, removeColumn, emptyTable.
Your addColumn method would query the table and determine the max(ID) then add 1 and use that number for the next entry.
Your removeColumn method could remove the entry by ID, then use that ID to resequence everything above it. Or, if order is not important, it could take the last row and re-id it to fill in the gap.
Your emptyTable method could remove all entries.
Update
Maybe this can get you started. The methods would need to be defined in your program. You would have to put the code inside them and then set them up to be called.
For example:
public void addColumn(String category, long date, String details) {
//code here would need to determine the max of ID and add one
//to it. the sql below would retrieve max, i dont know the sql lite code
//off the top of my head.
//SELECT MAX(ID) FROM DATABASE_TABLE;
int newID = max + 1;
//add row to the database using newID
}
public void removeColumn(int id) {
//remove column from database
//DELETE FROM DATABASE_TABLE WHERE ID = id;
//change last entry to use id
//UPDATE DATABASE_TABLE SET ID = id WHERE id = (SELECT MAX(ID) FROM DATABASE_TABLE);
}
public void emptyTable() {
//DELETE FROM DATABASE_TABLE;
}
To call these methods, you would call them like any other java method in your class:
addColumn(12, 'text', (long)100, 'text');
removeColumn(10);
emptyTable();
SQLite keeps the largest ROWID in the special SQLITE_SEQUENCE table. You can delete that table as:
db.delete("SQLITE_SEQUENCE","NAME = ?",new String[]{TABLE_NAME});
Unfortunately, SQLite AUTOINCREMENT is not guaranteed to work as you want. Quoting the docs:
If no ROWID is specified on the insert, or if the specified ROWID has
a value of NULL, then an appropriate ROWID is created automatically.
The usual algorithm is to give the newly created row a ROWID that is
one larger than the largest ROWID in the table prior to the insert. If
the table is initially empty, then a ROWID of 1 is used. If the
largest ROWID is equal to the largest possible integer
(9223372036854775807) then the database engine starts picking positive
candidate ROWIDs at random until it finds one that is not previously
used. If no unused ROWID can be found after a reasonable number of
attempts, the insert operation fails with an SQLITE_FULL error. If no
negative ROWID values are inserted explicitly, then automatically
generated ROWID values will always be greater than zero.
If you need this specific behaviour you described, the only solution would be for you to manually control the KEY_ROWID for your table, making sure you properly account for inserts and deletes.
You can also delete your table from SQLite_Sequence using
sqlDb.execSQL("DELETE FROM SQLITE_SEQUENCE WHERE NAME = 'YOUR_TABLE_NAME'");